Merge "Mark ab/6881855 as merged" into stage-aosp-master
diff --git a/.buckconfig b/.buckconfig
index 33c15ec..5c6c995 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -4,9 +4,20 @@
# required downstream.
allow_symlinks = allow
+ # Hide BUCK files under target/package/ from `buck build ...`. Otherwise:
+ # $ buck build ...
+ # //target/package/cxx-0.3.0/tests:ffi references non-existing file or directory 'target/package/cxx-0.3.0/tests/ffi/lib.rs'
+ ignore = target
+
[cxx]
cxxflags = -std=c++11
[rust]
default_edition = 2018
- rustc_flags = -Crelocation-model=dynamic-no-pic --cap-lints=allow
+ rustc_flags = \
+ -Clink-arg=-fuse-ld=lld \
+ -Crelocation-model=dynamic-no-pic \
+ --cap-lints=allow
+
+[defaults.rust_library]
+ type = check
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..93cfc05
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1 @@
+FROM dtolnay/devcontainer:latest
diff --git a/.devcontainer/README.md b/.devcontainer/README.md
new file mode 100644
index 0000000..b30aebc
--- /dev/null
+++ b/.devcontainer/README.md
@@ -0,0 +1,4 @@
+This directory contains the container setup used when developing CXX inside of
+GitHub [Codespaces].
+
+[Codespaces]: https://github.com/features/codespaces
diff --git a/.devcontainer/build.Dockerfile b/.devcontainer/build.Dockerfile
new file mode 100644
index 0000000..6085459
--- /dev/null
+++ b/.devcontainer/build.Dockerfile
@@ -0,0 +1,18 @@
+FROM mcr.microsoft.com/vscode/devcontainers/rust:1
+
+RUN apt-get update \
+ && export DEBIAN_FRONTEND=noninteractive \
+ && apt-get -y install --no-install-recommends openjdk-11-jdk lld \
+ && rustup default nightly 2>&1 \
+ && rustup component add rust-analyzer-preview rustfmt clippy 2>&1 \
+ && wget -q -O bin/install-bazel https://github.com/bazelbuild/bazel/releases/download/2.1.1/bazel-2.1.1-installer-linux-x86_64.sh \
+ && wget -q -O bin/buck https://jitpack.io/com/github/facebook/buck/a5f0342ae3/buck-a5f0342ae3-java11.pex \
+ && wget -q -O bin/buildifier https://github.com/bazelbuild/buildtools/releases/latest/download/buildifier \
+ && wget -q -O tmp/watchman.zip https://github.com/facebook/watchman/releases/download/v2020.09.21.00/watchman-v2020.09.21.00-linux.zip \
+ && chmod +x bin/install-bazel bin/buck bin/buildifier \
+ && bin/install-bazel \
+ && unzip tmp/watchman.zip -d tmp \
+ && mv tmp/watchman-v2020.09.21.00-linux/bin/watchman bin \
+ && mv tmp/watchman-v2020.09.21.00-linux/lib/* /usr/local/lib \
+ && mkdir -p /usr/local/var/run/watchman \
+ && rm tmp/watchman.zip
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..b8deba2
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,20 @@
+{
+ "name": "Rust",
+ "build": {
+ "dockerfile": "Dockerfile"
+ },
+ "runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
+ "settings": {
+ "terminal.integrated.shell.linux": "/bin/bash",
+ "lldb.executable": "/usr/bin/lldb",
+ "files.watcherExclude": {
+ "**/target/**": true
+ }
+ },
+ "extensions": [
+ "BazelBuild.vscode-bazel",
+ "matklad.rust-analyzer",
+ "ms-vscode.cpptools",
+ "vadimcn.vscode-lldb"
+ ]
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f0ca230..26b2722 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,16 +34,17 @@
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{matrix.rust}}
- - run: cargo run --manifest-path demo-rs/Cargo.toml
- - run: cargo test
-
- msrv:
- name: Rust 1.42.0
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: dtolnay/rust-toolchain@1.42.0
- - run: cargo run --manifest-path demo-rs/Cargo.toml
+ - name: Determine test suite subset
+ # Our Windows and macOS jobs are the longest running, so exclude the
+ # relatively slow compiletest from them to speed up end-to-end CI time,
+ # except during cron builds when no human is presumably waiting on the
+ # build. The extra coverage is not particularly valuable and we can
+ # still ensure the test is kept passing on the basis of the scheduled
+ # builds.
+ if: matrix.os && github.event_name != 'schedule'
+ run: echo '::set-env name=RUSTFLAGS::--cfg skip_ui_tests'
+ - run: cargo run --manifest-path demo/Cargo.toml
+ - run: cargo test --workspace --exclude cxx-test-suite
buck:
name: Buck
@@ -53,20 +54,22 @@
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-java@v1
with:
- java-version: 8
+ java-version: 11
java-package: jre
- name: Install Buck
run: |
mkdir bin
- wget -q -O bin/buck https://jitpack.io/com/github/facebook/buck/v2019.10.17.01/buck-v2019.10.17.01.pex
+ wget -q -O bin/buck https://jitpack.io/com/github/facebook/buck/a5f0342ae3/buck-a5f0342ae3-java11.pex # dev branch from 2020.10.11
chmod +x bin/buck
- echo ::add-path::bin
+ echo bin >> $GITHUB_PATH
+ - name: Install lld
+ run: sudo apt install lld
- name: Vendor dependencies
run: |
cp third-party/Cargo.lock .
cargo vendor --versioned-dirs --locked third-party/vendor
- run: buck build :cxx#check --verbose=0
- - run: buck run demo-rs --verbose=0
+ - run: buck run demo --verbose=0
- run: buck test ... --verbose=0
bazel:
@@ -79,10 +82,14 @@
wget -q -O install.sh https://github.com/bazelbuild/bazel/releases/download/2.1.1/bazel-2.1.1-installer-linux-x86_64.sh
chmod +x install.sh
./install.sh --user
- echo ::add-path::$HOME/bin
- - name: Vendor dependencies
- run: |
- cp third-party/Cargo.lock .
- cargo vendor --versioned-dirs --locked third-party/vendor
- - run: bazel run demo-rs --verbose_failures --noshow_progress
+ echo $HOME/bin >> $GITHUB_PATH
+ - run: bazel run demo --verbose_failures --noshow_progress
- run: bazel test ... --verbose_failures --noshow_progress
+
+ clippy:
+ name: Clippy
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: dtolnay/rust-toolchain@clippy
+ - run: cargo clippy --workspace --tests -- -Dclippy::all
diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml
new file mode 100644
index 0000000..73b8186
--- /dev/null
+++ b/.github/workflows/site.yml
@@ -0,0 +1,39 @@
+name: Deploy
+
+on:
+ push:
+ branches:
+ - master
+ paths:
+ - book/**
+
+jobs:
+ deploy:
+ name: Deploy
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Get mdBook
+ working-directory: book
+ run: |
+ export MDBOOK_VERSION="v0.4.4"
+ export MDBOOK_TARBALL="mdbook-${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz"
+ export MDBOOK_URL="https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/${MDBOOK_TARBALL}"
+ curl -Lf "${MDBOOK_URL}" | tar -xz
+
+ - name: Build
+ working-directory: book
+ run: ./mdbook build
+
+ - name: Push to gh-pages
+ working-directory: book/build
+ run: |
+ REV=$(git rev-parse --short HEAD)
+ git init
+ git remote add upstream https://x-access-token:${{secrets.GITHUB_TOKEN}}@github.com/dtolnay/cxx
+ git config user.name "CXX"
+ git config user.email "dtolnay+cxx@gmail.com"
+ git add -A .
+ git commit -qm "Website @ ${{github.repository}}@${REV}"
+ git push -q upstream HEAD:refs/heads/gh-pages --force
diff --git a/.gitignore b/.gitignore
index e7499e5..b036b6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@
/bazel-out
/bazel-testlogs
/buck-out
-/Cargo.lock
/expand.cc
/expand.rs
-/target
+Cargo.lock
+target
diff --git a/.vscode/README.md b/.vscode/README.md
new file mode 100644
index 0000000..5ed5b27
--- /dev/null
+++ b/.vscode/README.md
@@ -0,0 +1,4 @@
+VS Code actions and configuration. Applicable when developing CXX inside of
+GitHub [Codespaces].
+
+[Codespaces]: https://github.com/features/codespaces
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..244f5c4
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,17 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Run cxx demo",
+ "type": "lldb",
+ "request": "launch",
+ "cargo": {
+ "args": ["build", "--manifest-path", "demo/Cargo.toml"],
+ "filter": {
+ "name": "demo",
+ "kind": "bin"
+ }
+ }
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..8a1c2c1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "search.exclude": {
+ "**/target": true
+ }
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..44a2ab7
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,30 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Cargo test",
+ "type": "shell",
+ "command": "cargo test",
+ "group": "test"
+ },
+ {
+ "label": "Bazel test",
+ "type": "shell",
+ "command": "bazel test ...",
+ "group": "test",
+ "dependsOn": ["Vendor"]
+ },
+ {
+ "label": "Buck test",
+ "type": "shell",
+ "command": "buck test ...",
+ "group": "test",
+ "dependsOn": ["Vendor"]
+ },
+ {
+ "label": "Vendor",
+ "type": "shell",
+ "command": "cp third-party/Cargo.lock . && cargo vendor --versioned-dirs --locked third-party/vendor"
+ }
+ ]
+}
diff --git a/Android.bp b/Android.bp
index 9d57757..985b8e2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -26,9 +26,8 @@
cc_library_static {
host_supported: true,
- name: "libcxx-demo-cxx",
- srcs: ["demo-cxx/demo.cc"],
- local_include_dirs: ["demo-cxx"],
+ name: "libcxx-demo-blobstore",
+ srcs: ["demo/src/blobstore.cc"],
generated_headers: ["cxx-demo-bridge-header", "cxx-bridge-header"],
generated_sources: ["cxx-demo-bridge-code"],
}
diff --git a/BUCK b/BUCK
index 241d7b3..da6fcb2 100644
--- a/BUCK
+++ b/BUCK
@@ -1,12 +1,10 @@
rust_library(
name = "cxx",
- srcs = glob(["src/**"], exclude = ["src/symbols/**"]),
+ srcs = glob(["src/**"]),
visibility = ["PUBLIC"],
- rustc_flags = ["--cfg", "no_export_symbols"],
deps = [
":core",
":macro",
- "//third-party:link-cplusplus",
],
)
@@ -16,11 +14,10 @@
crate = "cxxbridge",
visibility = ["PUBLIC"],
deps = [
- "//third-party:anyhow",
+ "//third-party:clap",
"//third-party:codespan-reporting",
"//third-party:proc-macro2",
"//third-party:quote",
- "//third-party:structopt",
"//third-party:syn",
],
)
@@ -34,13 +31,6 @@
"cxx.h": "include/cxx.h",
},
exported_linker_flags = ["-lstdc++"],
- deps = [":symbols"],
-)
-
-rust_library(
- name = "symbols",
- srcs = glob(["src/macros/**", "src/symbols/**"]),
- crate_root = "src/symbols/symbols.rs",
)
rust_library(
@@ -60,7 +50,21 @@
srcs = glob(["gen/build/src/**"]),
visibility = ["PUBLIC"],
deps = [
- "//third-party:anyhow",
+ "//third-party:cc",
+ "//third-party:codespan-reporting",
+ "//third-party:lazy_static",
+ "//third-party:proc-macro2",
+ "//third-party:quote",
+ "//third-party:scratch",
+ "//third-party:syn",
+ ],
+)
+
+rust_library(
+ name = "lib",
+ srcs = glob(["gen/lib/src/**"]),
+ visibility = ["PUBLIC"],
+ deps = [
"//third-party:cc",
"//third-party:codespan-reporting",
"//third-party:proc-macro2",
diff --git a/BUILD b/BUILD
index 4d2b33a..24a1c8b 100644
--- a/BUILD
+++ b/BUILD
@@ -1,3 +1,4 @@
+load("@rules_cc//cc:defs.bzl", "cc_library")
load("//tools/bazel:rust.bzl", "rust_binary", "rust_library")
rust_library(
@@ -7,10 +8,7 @@
":cxxbridge-macro",
],
visibility = ["//visibility:public"],
- deps = [
- ":core-lib",
- "//third-party:link-cplusplus",
- ],
+ deps = [":core-lib"],
)
rust_binary(
@@ -19,11 +17,10 @@
data = ["gen/cmd/src/gen/include/cxx.h"],
visibility = ["//visibility:public"],
deps = [
- "//third-party:anyhow",
+ "//third-party:clap",
"//third-party:codespan-reporting",
"//third-party:proc-macro2",
"//third-party:quote",
- "//third-party:structopt",
"//third-party:syn",
],
)
@@ -59,7 +56,22 @@
data = ["gen/build/src/gen/include/cxx.h"],
visibility = ["//visibility:public"],
deps = [
- "//third-party:anyhow",
+ "//third-party:cc",
+ "//third-party:codespan-reporting",
+ "//third-party:lazy_static",
+ "//third-party:proc-macro2",
+ "//third-party:quote",
+ "//third-party:scratch",
+ "//third-party:syn",
+ ],
+)
+
+rust_library(
+ name = "lib",
+ srcs = glob(["gen/lib/src/**/*.rs"]),
+ data = ["gen/lib/src/gen/include/cxx.h"],
+ visibility = ["//visibility:public"],
+ deps = [
"//third-party:cc",
"//third-party:codespan-reporting",
"//third-party:proc-macro2",
diff --git a/Cargo.toml b/Cargo.toml
index 6a8c1a4..f0692ae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,38 +1,41 @@
[package]
name = "cxx"
-version = "0.3.4" # remember to update html_root_url
+version = "0.5.9" # remember to update html_root_url
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
-links = "cxxbridge03"
+links = "cxxbridge05"
license = "MIT OR Apache-2.0"
description = "Safe interop between Rust and C++"
repository = "https://github.com/dtolnay/cxx"
documentation = "https://docs.rs/cxx"
readme = "README.md"
-exclude = ["/demo-cxx", "/gen", "/syntax", "/third-party"]
+exclude = ["/demo", "/gen", "/syntax", "/third-party"]
keywords = ["ffi"]
categories = ["development-tools::ffi", "api-bindings"]
[features]
-default = [] # c++11
-"c++14" = []
-"c++17" = []
+default = ["cxxbridge-flags/default"] # c++11
+"c++14" = ["cxxbridge-flags/c++14"]
+"c++17" = ["cxxbridge-flags/c++17"]
+"c++20" = ["cxxbridge-flags/c++20"]
[dependencies]
-cxxbridge-macro = { version = "=0.3.4", path = "macro" }
+cxxbridge-macro = { version = "=0.5.9", path = "macro" }
link-cplusplus = "1.0"
[build-dependencies]
cc = "1.0.49"
+cxxbridge-flags = { version = "=0.5.9", path = "flags", default-features = false }
[dev-dependencies]
-cxx-build = { version = "=0.3.4", path = "gen/build" }
+cxx-build = { version = "=0.5.9", path = "gen/build" }
+cxx-gen = { version = "0.6", path = "gen/lib" }
cxx-test-suite = { version = "0", path = "tests/ffi" }
rustversion = "1.0"
-trybuild = { version = "1.0.27", features = ["diff"] }
+trybuild = { version = "1.0.33", features = ["diff"] }
[workspace]
-members = ["demo-rs", "gen/build", "gen/cmd", "macro", "tests/ffi"]
+members = ["demo", "flags", "gen/build", "gen/cmd", "gen/lib", "macro", "tests/ffi"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
diff --git a/README.md b/README.md
index 260c272..9c0289b 100644
--- a/README.md
+++ b/README.md
@@ -18,10 +18,13 @@
```toml
[dependencies]
-cxx = "0.3"
+cxx = "0.5"
+
+[build-dependencies]
+cxx-build = "0.5"
```
-*Compiler support: requires rustc 1.42+ and c++11 or newer*<br>
+*Compiler support: requires rustc 1.43+ and c++11 or newer*<br>
*[Release notes](https://github.com/dtolnay/cxx/releases)*
<br>
@@ -60,57 +63,62 @@
## Example
-A runnable version of this example is provided under the *demo-rs* directory of
-this repo (with the C++ side of the implementation in the *demo-cxx* directory).
-To try it out, jump into demo-rs and run `cargo run`.
+In this example we are writing a Rust application that wishes to take advantage
+of an existing C++ client for a large-file blobstore service. The blobstore
+supports a `put` operation for a discontiguous buffer upload. For example we
+might be uploading snapshots of a circular buffer which would tend to consist of
+2 chunks, or fragments of a file spread across memory for some other reason.
+
+A runnable version of this example is provided under the *demo* directory of
+this repo. To try it out, run `cargo run` from that directory.
```rust
#[cxx::bridge]
mod ffi {
// Any shared structs, whose fields will be visible to both languages.
- struct SharedThing {
- z: i32,
- y: Box<ThingR>,
- x: UniquePtr<ThingC>,
- }
-
- extern "C" {
- // One or more headers with the matching C++ declarations. Our code
- // generators don't read it but it gets #include'd and used in static
- // assertions to ensure our picture of the FFI boundary is accurate.
- include!("demo-cxx/demo.h");
-
- // Zero or more opaque types which both languages can pass around but
- // only C++ can see the fields.
- type ThingC;
-
- // Functions implemented in C++.
- fn make_demo(appname: &str) -> UniquePtr<ThingC>;
- fn get_name(thing: &ThingC) -> &CxxString;
- fn do_thing(state: SharedThing);
+ struct BlobMetadata {
+ size: usize,
+ tags: Vec<String>,
}
extern "Rust" {
// Zero or more opaque types which both languages can pass around but
// only Rust can see the fields.
- type ThingR;
+ type MultiBuf;
// Functions implemented in Rust.
- fn print_r(r: &ThingR);
+ fn next_chunk(buf: &mut MultiBuf) -> &[u8];
+ }
+
+ extern "C++" {
+ // One or more headers with the matching C++ declarations. Our code
+ // generators don't read it but it gets #include'd and used in static
+ // assertions to ensure our picture of the FFI boundary is accurate.
+ include!("demo/include/blobstore.h");
+
+ // Zero or more opaque types which both languages can pass around but
+ // only C++ can see the fields.
+ type BlobstoreClient;
+
+ // Functions implemented in C++.
+ fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
+ fn put(&self, parts: &mut MultiBuf) -> u64;
+ fn tag(&self, blobid: u64, tag: &str);
+ fn metadata(&self, blobid: u64) -> BlobMetadata;
}
}
```
-Now we simply provide C++ definitions of all the things in the `extern "C"`
-block and Rust definitions of all the things in the `extern "Rust"` block, and
-get to call back and forth safely.
+Now we simply provide Rust definitions of all the things in the `extern "Rust"`
+block and C++ definitions of all the things in the `extern "C++"` block, and get
+to call back and forth safely.
Here are links to the complete set of source files involved in the demo:
-- [demo-rs/src/main.rs](demo-rs/src/main.rs)
-- [demo-rs/build.rs](demo-rs/build.rs)
-- [demo-cxx/demo.h](demo-cxx/demo.h)
-- [demo-cxx/demo.cc](demo-cxx/demo.cc)
+- [demo/src/main.rs](demo/src/main.rs)
+- [demo/build.rs](demo/build.rs)
+- [demo/include/blobstore.h](demo/include/blobstore.h)
+- [demo/src/blobstore.cc](demo/src/blobstore.cc)
To look at the code generated in both languages for the example by the CXX code
generators:
@@ -118,10 +126,10 @@
```console
# run Rust code generator and print to stdout
# (requires https://github.com/dtolnay/cargo-expand)
-$ cargo expand --manifest-path demo-rs/Cargo.toml
+$ cargo expand --manifest-path demo/Cargo.toml
# run C++ code generator and print to stdout
-$ cargo run --manifest-path gen/cmd/Cargo.toml -- demo-rs/src/main.rs
+$ cargo run --manifest-path gen/cmd/Cargo.toml -- demo/src/main.rs
```
<br>
@@ -220,7 +228,7 @@
# Cargo.toml
[build-dependencies]
-cxx-build = "0.3"
+cxx-build = "0.5"
```
```rust
@@ -228,13 +236,13 @@
fn main() {
cxx_build::bridge("src/main.rs") // returns a cc::Build
- .file("../demo-cxx/demo.cc")
+ .file("src/demo.cc")
.flag_if_supported("-std=c++11")
.compile("cxxbridge-demo");
println!("cargo:rerun-if-changed=src/main.rs");
- println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
- println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
+ println!("cargo:rerun-if-changed=src/demo.cc");
+ println!("cargo:rerun-if-changed=include/demo.h");
}
```
@@ -245,7 +253,7 @@
For use in non-Cargo builds like Bazel or Buck, CXX provides an alternate way of
invoking the C++ code generator as a standalone command line tool. The tool is
packaged as the `cxxbridge-cmd` crate on crates.io or can be built from the
-*cmd* directory of this repo.
+*gen/cmd* directory of this repo.
```bash
$ cargo install cxxbridge-cmd
@@ -308,11 +316,11 @@
<tr><td>String</td><td>rust::String</td><td></td></tr>
<tr><td>&str</td><td>rust::Str</td><td></td></tr>
<tr><td>&[u8]</td><td>rust::Slice<uint8_t></td><td><sup><i>arbitrary &[T] not implemented yet</i></sup></td></tr>
-<tr><td><a href="https://docs.rs/cxx/0.3/cxx/struct.CxxString.html">CxxString</a></td><td>std::string</td><td><sup><i>cannot be passed by value</i></sup></td></tr>
+<tr><td><a href="https://docs.rs/cxx/0.5/cxx/struct.CxxString.html">CxxString</a></td><td>std::string</td><td><sup><i>cannot be passed by value</i></sup></td></tr>
<tr><td>Box<T></td><td>rust::Box<T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
-<tr><td><a href="https://docs.rs/cxx/0.3/cxx/struct.UniquePtr.html">UniquePtr<T></a></td><td>std::unique_ptr<T></td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr>
+<tr><td><a href="https://docs.rs/cxx/0.5/cxx/struct.UniquePtr.html">UniquePtr<T></a></td><td>std::unique_ptr<T></td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr>
<tr><td>Vec<T></td><td>rust::Vec<T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
-<tr><td><a href="https://docs.rs/cxx/0.3/cxx/struct.CxxVector.html">CxxVector<T></a></td><td>std::vector<T></td><td><sup><i>cannot be passed by value, cannot hold opaque Rust type</i></sup></td></tr>
+<tr><td><a href="https://docs.rs/cxx/0.5/cxx/struct.CxxVector.html">CxxVector<T></a></td><td>std::vector<T></td><td><sup><i>cannot be passed by value, cannot hold opaque Rust type</i></sup></td></tr>
<tr><td>fn(T, U) -> V</td><td>rust::Fn<V(T, U)></td><td><sup><i>only passing from Rust to C++ is implemented so far</i></sup></td></tr>
<tr><td>Result<T></td><td>throw/catch</td><td><sup><i>allowed as return type only</i></sup></td></tr>
</table>
@@ -330,6 +338,7 @@
<tr><td>BTreeMap<K, V></td><td><sup><i>tbd</i></sup></td></tr>
<tr><td>HashMap<K, V></td><td><sup><i>tbd</i></sup></td></tr>
<tr><td>Arc<T></td><td><sup><i>tbd</i></sup></td></tr>
+<tr><td>Option<T></td><td><sup><i>tbd</i></sup></td></tr>
<tr><td><sup><i>tbd</i></sup></td><td>std::map<K, V></td></tr>
<tr><td><sup><i>tbd</i></sup></td><td>std::unordered_map<K, V></td></tr>
<tr><td><sup><i>tbd</i></sup></td><td>std::shared_ptr<T></td></tr>
@@ -343,10 +352,9 @@
to collect feedback on the direction and invite collaborators. Please check the
open issues.
-On the build side, I don't have much experience with the `cc` crate so I expect
-there may be someone who can suggest ways to make that aspect of this crate
-friendlier or more robust. Please report issues if you run into trouble building
-or linking any of this stuff.
+Especially please report issues if you run into trouble building or linking any
+of this stuff. I'm sure there are ways to make the build aspects friendlier or
+more robust.
Finally, I know more about Rust library design than C++ library design so I
would appreciate help making the C++ APIs in this project more idiomatic where
diff --git a/WORKSPACE b/WORKSPACE
index cd2a2e8..6093eee 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,4 +1,5 @@
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("//tools/bazel:vendor.bzl", "vendor")
http_archive(
name = "io_bazel_rules_rust",
@@ -24,13 +25,18 @@
load("@io_bazel_rules_rust//rust:repositories.bzl", "rust_repository_set")
rust_repository_set(
- name = "rust_1_44_linux",
+ name = "rust_1_47_linux",
exec_triple = "x86_64-unknown-linux-gnu",
- version = "1.44.0",
+ version = "1.47.0",
)
rust_repository_set(
- name = "rust_1_44_darwin",
+ name = "rust_1_47_darwin",
exec_triple = "x86_64-apple-darwin",
- version = "1.44.0",
+ version = "1.47.0",
+)
+
+vendor(
+ name = "third-party",
+ lockfile = "//third-party:Cargo.lock",
)
diff --git a/book/.gitignore b/book/.gitignore
new file mode 100644
index 0000000..690b5b8
--- /dev/null
+++ b/book/.gitignore
@@ -0,0 +1,2 @@
+/build
+/mdbook
diff --git a/book/README.md b/book/README.md
new file mode 100644
index 0000000..e4916e0
--- /dev/null
+++ b/book/README.md
@@ -0,0 +1,9 @@
+Published automatically to https://cxx.rs from master branch.
+
+To build and view locally:
+
+- Install [mdBook]: `cargo install mdbook`.
+- Run `mdbook build` in this directory.
+- Open the generated *build/index.html*.
+
+[mdBook]: https://github.com/rust-lang/mdBook
diff --git a/book/book.toml b/book/book.toml
new file mode 100644
index 0000000..d3187e0
--- /dev/null
+++ b/book/book.toml
@@ -0,0 +1,14 @@
+[book]
+title = "CXX"
+authors = ["David Tolnay"]
+description = "Guide for the `cxx` crate, a safe approach to FFI between Rust and C++."
+
+[rust]
+edition = "2018"
+
+[build]
+build-dir = "build"
+
+[output.html]
+cname = "cxx.rs"
+git-repository-url = "https://github.com/dtolnay/cxx"
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
new file mode 100644
index 0000000..f3fa924
--- /dev/null
+++ b/book/src/SUMMARY.md
@@ -0,0 +1,3 @@
+# Summary
+
+[Rust ❤️ C++](about.md)
diff --git a/book/src/about.md b/book/src/about.md
new file mode 100644
index 0000000..6e63bf5
--- /dev/null
+++ b/book/src/about.md
@@ -0,0 +1 @@
+### Coming soon
diff --git a/build.rs b/build.rs
index a412dbd..57b3e52 100644
--- a/build.rs
+++ b/build.rs
@@ -1,16 +1,18 @@
+use std::env;
+use std::path::Path;
+
fn main() {
cc::Build::new()
.file("src/cxx.cc")
.cpp(true)
.cpp_link_stdlib(None) // linked via link-cplusplus crate
- .flag_if_supported(if cfg!(feature = "c++17") {
- "-std=c++17"
- } else if cfg!(feature = "c++14") {
- "-std=c++14"
- } else {
- "-std=c++11"
- })
- .compile("cxxbridge03");
+ .flag_if_supported(cxxbridge_flags::STD)
+ .compile("cxxbridge05");
println!("cargo:rerun-if-changed=src/cxx.cc");
println!("cargo:rerun-if-changed=include/cxx.h");
+ println!("cargo:rustc-cfg=built_with_cargo");
+ if let Some(manifest_dir) = env::var_os("CARGO_MANIFEST_DIR") {
+ let cxx_h = Path::new(&manifest_dir).join("include").join("cxx.h");
+ println!("cargo:HEADER={}", cxx_h.to_string_lossy());
+ }
}
diff --git a/demo-cxx/BUCK b/demo-cxx/BUCK
deleted file mode 100644
index f60200b..0000000
--- a/demo-cxx/BUCK
+++ /dev/null
@@ -1,17 +0,0 @@
-cxx_library(
- name = "demo-cxx",
- srcs = ["demo.cc"],
- compiler_flags = ["-std=c++14"],
- visibility = ["PUBLIC"],
- deps = [
- ":include",
- "//demo-rs:include",
- ],
-)
-
-cxx_library(
- name = "include",
- exported_headers = ["demo.h"],
- visibility = ["PUBLIC"],
- deps = ["//:core"],
-)
diff --git a/demo-cxx/BUILD b/demo-cxx/BUILD
deleted file mode 100644
index 7b1860a..0000000
--- a/demo-cxx/BUILD
+++ /dev/null
@@ -1,17 +0,0 @@
-cc_library(
- name = "demo-cxx",
- srcs = ["demo.cc"],
- copts = ["-std=c++14"],
- visibility = ["//visibility:public"],
- deps = [
- ":include",
- "//demo-rs:include",
- ],
-)
-
-cc_library(
- name = "include",
- hdrs = ["demo.h"],
- visibility = ["//visibility:public"],
- deps = ["//:core"],
-)
diff --git a/demo-cxx/demo.cc b/demo-cxx/demo.cc
deleted file mode 100644
index 21bdad4..0000000
--- a/demo-cxx/demo.cc
+++ /dev/null
@@ -1,21 +0,0 @@
-#include "demo-cxx/demo.h"
-#include "demo-rs/src/main.rs.h"
-#include <iostream>
-
-namespace org {
-namespace example {
-
-ThingC::ThingC(std::string appname) : appname(std::move(appname)) {}
-
-ThingC::~ThingC() { std::cout << "done with ThingC" << std::endl; }
-
-std::unique_ptr<ThingC> make_demo(rust::Str appname) {
- return std::make_unique<ThingC>(std::string(appname));
-}
-
-const std::string &get_name(const ThingC &thing) { return thing.appname; }
-
-void do_thing(SharedThing state) { print_r(*state.y); }
-
-} // namespace example
-} // namespace org
diff --git a/demo-cxx/demo.h b/demo-cxx/demo.h
deleted file mode 100644
index fafc474..0000000
--- a/demo-cxx/demo.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-#include "rust/cxx.h"
-#include <memory>
-#include <string>
-
-namespace org {
-namespace example {
-
-class ThingC {
-public:
- ThingC(std::string appname);
- ~ThingC();
-
- std::string appname;
-};
-
-struct SharedThing;
-
-std::unique_ptr<ThingC> make_demo(rust::Str appname);
-const std::string &get_name(const ThingC &thing);
-void do_thing(SharedThing state);
-
-} // namespace example
-} // namespace org
diff --git a/demo-rs/BUCK b/demo-rs/BUCK
deleted file mode 100644
index d4164d3..0000000
--- a/demo-rs/BUCK
+++ /dev/null
@@ -1,42 +0,0 @@
-rust_binary(
- name = "demo-rs",
- srcs = glob(["src/**"]),
- deps = [
- ":gen",
- "//:cxx",
- "//demo-cxx:demo-cxx",
- ],
-)
-
-cxx_library(
- name = "gen",
- srcs = [":gen-source"],
- deps = [
- ":include",
- "//demo-cxx:include",
- ],
-)
-
-genrule(
- name = "gen-header",
- srcs = ["src/main.rs"],
- cmd = "$(exe //:codegen) --header ${SRCS} > ${OUT}",
- type = "cxxbridge",
- out = "generated.h",
-)
-
-genrule(
- name = "gen-source",
- srcs = ["src/main.rs"],
- cmd = "$(exe //:codegen) ${SRCS} > ${OUT}",
- type = "cxxbridge",
- out = "generated.cc",
-)
-
-cxx_library(
- name = "include",
- exported_headers = {
- "src/main.rs.h": ":gen-header",
- },
- visibility = ["PUBLIC"],
-)
diff --git a/demo-rs/BUILD b/demo-rs/BUILD
deleted file mode 100644
index e3ebb96..0000000
--- a/demo-rs/BUILD
+++ /dev/null
@@ -1,43 +0,0 @@
-load("//tools/bazel:rust.bzl", "rust_binary", "rust_library")
-
-rust_binary(
- name = "demo-rs",
- srcs = glob(["src/**"]),
- deps = [
- ":gen",
- "//:cxx",
- "//demo-cxx",
- ],
-)
-
-cc_library(
- name = "gen",
- srcs = [":gen-source"],
- deps = [
- ":include",
- "//demo-cxx:include",
- ],
-)
-
-genrule(
- name = "gen-header",
- srcs = ["src/main.rs"],
- outs = ["main.rs.h"],
- cmd = "$(location //:codegen) --header $< > $@",
- tools = ["//:codegen"],
-)
-
-genrule(
- name = "gen-source",
- srcs = ["src/main.rs"],
- outs = ["generated.cc"],
- cmd = "$(location //:codegen) $< > $@",
- tools = ["//:codegen"],
-)
-
-cc_library(
- name = "include",
- hdrs = [":gen-header"],
- include_prefix = "demo-rs/src",
- visibility = ["//visibility:public"],
-)
diff --git a/demo-rs/build.rs b/demo-rs/build.rs
deleted file mode 100644
index f32b8ef..0000000
--- a/demo-rs/build.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-fn main() {
- cxx_build::bridge("src/main.rs")
- .file("../demo-cxx/demo.cc")
- .flag_if_supported("-std=c++14")
- .compile("cxxbridge-demo");
-
- println!("cargo:rerun-if-changed=src/main.rs");
- println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
- println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
-}
diff --git a/demo-rs/src/main.rs b/demo-rs/src/main.rs
deleted file mode 100644
index 66dfc79..0000000
--- a/demo-rs/src/main.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-#[cxx::bridge(namespace = org::example)]
-mod ffi {
- struct SharedThing {
- z: i32,
- y: Box<ThingR>,
- x: UniquePtr<ThingC>,
- }
-
- extern "C" {
- include!("demo-cxx/demo.h");
-
- type ThingC;
- fn make_demo(appname: &str) -> UniquePtr<ThingC>;
- fn get_name(thing: &ThingC) -> &CxxString;
- fn do_thing(state: SharedThing);
- }
-
- extern "Rust" {
- type ThingR;
- fn print_r(r: &ThingR);
- }
-}
-
-pub struct ThingR(usize);
-
-fn print_r(r: &ThingR) {
- println!("called back with r={}", r.0);
-}
-
-fn main() {
- let x = ffi::make_demo("demo of cxx::bridge");
- println!("this is a {}", ffi::get_name(x.as_ref().unwrap()));
-
- ffi::do_thing(ffi::SharedThing {
- z: 222,
- y: Box::new(ThingR(333)),
- x,
- });
-}
diff --git a/demo-rs/Android.bp b/demo/Android.bp
similarity index 78%
rename from demo-rs/Android.bp
rename to demo/Android.bp
index 2fcdf6f..fabfaa7 100644
--- a/demo-rs/Android.bp
+++ b/demo/Android.bp
@@ -3,7 +3,7 @@
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) --header > $(out)",
srcs: ["src/main.rs"],
- out: ["demo-rs/src/main.rs.h"],
+ out: ["demo/src/main.rs.h"],
}
genrule {
@@ -11,12 +11,12 @@
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) >> $(out)",
srcs: ["src/main.rs"],
- out: ["demo-rs/generated.cc"],
+ out: ["demo/generated.cc"],
}
rust_binary {
name: "cxx-demo-rs",
srcs: ["src/main.rs"],
rlibs: ["libcxx"],
- static_libs: ["libcxx-demo-cxx"],
+ static_libs: ["libcxx-demo-blobstore"],
}
diff --git a/demo/BUCK b/demo/BUCK
new file mode 100644
index 0000000..43b83aa
--- /dev/null
+++ b/demo/BUCK
@@ -0,0 +1,33 @@
+load("//tools/buck:rust_cxx_bridge.bzl", "rust_cxx_bridge")
+
+rust_binary(
+ name = "demo",
+ srcs = glob(["src/**/*.rs"]),
+ deps = [
+ ":blobstore-sys",
+ ":bridge",
+ "//:cxx",
+ ],
+)
+
+rust_cxx_bridge(
+ name = "bridge",
+ src = "src/main.rs",
+ deps = [":blobstore-include"],
+)
+
+cxx_library(
+ name = "blobstore-sys",
+ srcs = ["src/blobstore.cc"],
+ compiler_flags = ["-std=c++14"],
+ deps = [
+ ":blobstore-include",
+ ":bridge/include",
+ ],
+)
+
+cxx_library(
+ name = "blobstore-include",
+ exported_headers = ["include/blobstore.h"],
+ deps = ["//:core"],
+)
diff --git a/demo/BUILD b/demo/BUILD
new file mode 100644
index 0000000..cce8119
--- /dev/null
+++ b/demo/BUILD
@@ -0,0 +1,35 @@
+load("@rules_cc//cc:defs.bzl", "cc_library")
+load("//tools/bazel:rust.bzl", "rust_binary")
+load("//tools/bazel:rust_cxx_bridge.bzl", "rust_cxx_bridge")
+
+rust_binary(
+ name = "demo",
+ srcs = glob(["src/**/*.rs"]),
+ deps = [
+ ":blobstore-sys",
+ ":bridge",
+ "//:cxx",
+ ],
+)
+
+rust_cxx_bridge(
+ name = "bridge",
+ src = "src/main.rs",
+ deps = [":blobstore-include"],
+)
+
+cc_library(
+ name = "blobstore-sys",
+ srcs = ["src/blobstore.cc"],
+ copts = ["-std=c++14"],
+ deps = [
+ ":blobstore-include",
+ ":bridge/include",
+ ],
+)
+
+cc_library(
+ name = "blobstore-include",
+ hdrs = ["include/blobstore.h"],
+ deps = ["//:core"],
+)
diff --git a/demo-rs/Cargo.toml b/demo/Cargo.toml
similarity index 89%
rename from demo-rs/Cargo.toml
rename to demo/Cargo.toml
index d2147ab..dc94861 100644
--- a/demo-rs/Cargo.toml
+++ b/demo/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "cxxbridge-demo"
+name = "demo"
version = "0.0.0"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
diff --git a/demo/build.rs b/demo/build.rs
new file mode 100644
index 0000000..c1b55cc
--- /dev/null
+++ b/demo/build.rs
@@ -0,0 +1,10 @@
+fn main() {
+ cxx_build::bridge("src/main.rs")
+ .file("src/blobstore.cc")
+ .flag_if_supported("-std=c++14")
+ .compile("cxxbridge-demo");
+
+ println!("cargo:rerun-if-changed=src/main.rs");
+ println!("cargo:rerun-if-changed=src/blobstore.cc");
+ println!("cargo:rerun-if-changed=include/blobstore.h");
+}
diff --git a/demo/include/blobstore.h b/demo/include/blobstore.h
new file mode 100644
index 0000000..68a7fc2
--- /dev/null
+++ b/demo/include/blobstore.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "rust/cxx.h"
+#include <memory>
+
+namespace org {
+namespace blobstore {
+
+struct MultiBuf;
+struct BlobMetadata;
+
+class BlobstoreClient {
+public:
+ BlobstoreClient();
+ uint64_t put(MultiBuf &buf) const;
+ void tag(uint64_t blobid, rust::Str tag) const;
+ BlobMetadata metadata(uint64_t blobid) const;
+
+private:
+ class Impl;
+ std::shared_ptr<Impl> impl;
+};
+
+std::unique_ptr<BlobstoreClient> new_blobstore_client();
+
+} // namespace blobstore
+} // namespace org
diff --git a/demo/src/blobstore.cc b/demo/src/blobstore.cc
new file mode 100644
index 0000000..0036a32
--- /dev/null
+++ b/demo/src/blobstore.cc
@@ -0,0 +1,71 @@
+#include "demo/include/blobstore.h"
+#include "demo/src/main.rs.h"
+#include <algorithm>
+#include <functional>
+#include <set>
+#include <string>
+#include <unordered_map>
+
+namespace org {
+namespace blobstore {
+
+// Toy implementation of an in-memory blobstore.
+//
+// In reality the implementation of BlobstoreClient could be a large complex C++
+// library.
+class BlobstoreClient::Impl {
+ friend BlobstoreClient;
+ using Blob = struct {
+ std::string data;
+ std::set<std::string> tags;
+ };
+ std::unordered_map<uint64_t, Blob> blobs;
+};
+
+BlobstoreClient::BlobstoreClient() : impl(new BlobstoreClient::Impl) {}
+
+// Upload a new blob and return a blobid that serves as a handle to the blob.
+uint64_t BlobstoreClient::put(MultiBuf &buf) const {
+ std::string contents;
+
+ // Traverse the caller's chunk iterator.
+ //
+ // In reality there might be sophisticated batching of chunks and/or parallel
+ // upload implemented by the blobstore's C++ client.
+ while (true) {
+ auto chunk = next_chunk(buf);
+ if (chunk.size() == 0) {
+ break;
+ }
+ contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size());
+ }
+
+ // Insert into map and provide caller the handle.
+ auto blobid = std::hash<std::string>{}(contents);
+ impl->blobs[blobid] = {std::move(contents), {}};
+ return blobid;
+}
+
+// Add tag to an existing blob.
+void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) const {
+ impl->blobs[blobid].tags.emplace(tag);
+}
+
+// Retrieve metadata about a blob.
+BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const {
+ BlobMetadata metadata{};
+ auto blob = impl->blobs.find(blobid);
+ if (blob != impl->blobs.end()) {
+ metadata.size = blob->second.data.size();
+ std::for_each(blob->second.tags.begin(), blob->second.tags.end(),
+ [&](auto &t) { metadata.tags.emplace_back(t); });
+ }
+ return metadata;
+}
+
+std::unique_ptr<BlobstoreClient> new_blobstore_client() {
+ return std::make_unique<BlobstoreClient>();
+}
+
+} // namespace blobstore
+} // namespace org
diff --git a/demo/src/main.rs b/demo/src/main.rs
new file mode 100644
index 0000000..10f57e5
--- /dev/null
+++ b/demo/src/main.rs
@@ -0,0 +1,59 @@
+#[cxx::bridge(namespace = "org::blobstore")]
+mod ffi {
+ // Shared structs with fields visible to both languages.
+ struct BlobMetadata {
+ size: usize,
+ tags: Vec<String>,
+ }
+
+ // Rust types and signatures exposed to C++.
+ extern "Rust" {
+ type MultiBuf;
+
+ fn next_chunk(buf: &mut MultiBuf) -> &[u8];
+ }
+
+ // C++ types and signatures exposed to Rust.
+ extern "C++" {
+ include!("demo/include/blobstore.h");
+
+ type BlobstoreClient;
+
+ fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
+ fn put(&self, parts: &mut MultiBuf) -> u64;
+ fn tag(&self, blobid: u64, tag: &str);
+ fn metadata(&self, blobid: u64) -> BlobMetadata;
+ }
+}
+
+// An iterator over contiguous chunks of a discontiguous file object.
+//
+// Toy implementation uses a Vec<Vec<u8>> but in reality this might be iterating
+// over some more complex Rust data structure like a rope, or maybe loading
+// chunks lazily from somewhere.
+pub struct MultiBuf {
+ chunks: Vec<Vec<u8>>,
+ pos: usize,
+}
+pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] {
+ let next = buf.chunks.get(buf.pos);
+ buf.pos += 1;
+ next.map(Vec::as_slice).unwrap_or(&[])
+}
+
+fn main() {
+ let client = ffi::new_blobstore_client();
+
+ // Upload a blob.
+ let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
+ let mut buf = MultiBuf { chunks, pos: 0 };
+ let blobid = client.put(&mut buf);
+ println!("blobid = {}", blobid);
+
+ // Add a tag.
+ client.tag(blobid, "rust");
+
+ // Read back the tags.
+ let metadata = client.metadata(blobid);
+ println!("tags = {:?}", metadata.tags);
+}
diff --git a/flags/Cargo.toml b/flags/Cargo.toml
new file mode 100644
index 0000000..cc5f230
--- /dev/null
+++ b/flags/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "cxxbridge-flags"
+version = "0.5.9"
+authors = ["David Tolnay <dtolnay@gmail.com>"]
+edition = "2018"
+license = "MIT OR Apache-2.0"
+description = "Compiler configuration of the `cxx` crate (implementation detail)"
+repository = "https://github.com/dtolnay/cxx"
+
+[features]
+default = [] # c++11
+"c++14" = []
+"c++17" = []
+"c++20" = []
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
diff --git a/flags/src/impl.rs b/flags/src/impl.rs
new file mode 100644
index 0000000..4f7b8fb
--- /dev/null
+++ b/flags/src/impl.rs
@@ -0,0 +1,20 @@
+#[allow(unused_assignments, unused_mut, unused_variables)]
+pub const STD: &str = {
+ let mut flags = ["-std=c++11", "/std:c++11"];
+
+ #[cfg(feature = "c++14")]
+ (flags = ["-std=c++14", "/std:c++14"]);
+
+ #[cfg(feature = "c++17")]
+ (flags = ["-std=c++17", "/std:c++17"]);
+
+ #[cfg(feature = "c++20")]
+ (flags = ["-std=c++20", "/std:c++20"]);
+
+ let [mut flag, msvc_flag] = flags;
+
+ #[cfg(target_env = "msvc")]
+ (flag = msvc_flag);
+
+ flag
+};
diff --git a/flags/src/lib.rs b/flags/src/lib.rs
new file mode 100644
index 0000000..55172b2
--- /dev/null
+++ b/flags/src/lib.rs
@@ -0,0 +1,7 @@
+//! This crate is an implementation detail of the `cxx` and `cxx-build` crates,
+//! and does not expose any public API.
+
+mod r#impl;
+
+#[doc(hidden)]
+pub use r#impl::*;
diff --git a/gen/README.md b/gen/README.md
index 9786911..d00b98f 100644
--- a/gen/README.md
+++ b/gen/README.md
@@ -2,3 +2,6 @@
public frontends, one a command-line application (binary) in the *cmd* directory
and the other a library intended to be used from a build.rs in the *build*
directory.
+
+There's also a 'lib' frontend which is intended to allow higher level code
+generators to embed cxx. This is not yet recommended for general use.
diff --git a/gen/build/Cargo.toml b/gen/build/Cargo.toml
index b43a37e..3f7b0ea 100644
--- a/gen/build/Cargo.toml
+++ b/gen/build/Cargo.toml
@@ -1,21 +1,27 @@
[package]
name = "cxx-build"
-version = "0.3.4"
+version = "0.5.9"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "C++ code generator for integrating `cxx` crate into a Cargo build."
repository = "https://github.com/dtolnay/cxx"
+exclude = ["build.rs"]
keywords = ["ffi"]
categories = ["development-tools::ffi"]
[dependencies]
-anyhow = "1.0"
cc = "1.0.49"
codespan-reporting = "0.9"
+lazy_static = "1.4"
proc-macro2 = { version = "1.0.17", default-features = false, features = ["span-locations"] }
quote = { version = "1.0", default-features = false }
+scratch = "1.0"
syn = { version = "1.0.20", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] }
+[dev-dependencies]
+cxx-gen = { version = "0.6", path = "../lib" }
+pkg-config = "0.3"
+
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
diff --git a/gen/build/build.rs b/gen/build/build.rs
new file mode 100644
index 0000000..c53bef7
--- /dev/null
+++ b/gen/build/build.rs
@@ -0,0 +1 @@
+include!("../../tools/cargo/build.rs");
diff --git a/gen/build/src/cfg.rs b/gen/build/src/cfg.rs
new file mode 100644
index 0000000..c15a173
--- /dev/null
+++ b/gen/build/src/cfg.rs
@@ -0,0 +1,407 @@
+use std::fmt::{self, Debug};
+use std::marker::PhantomData;
+use std::path::Path;
+
+/// Build configuration. See [CFG].
+pub struct Cfg<'a> {
+ /// See [`CFG.include_prefix`][CFG#cfginclude_prefix].
+ pub include_prefix: &'a str,
+ /// See [`CFG.exported_header_dirs`][CFG#cfgexported_header_dirs].
+ pub exported_header_dirs: Vec<&'a Path>,
+ /// See [`CFG.exported_header_prefixes`][CFG#cfgexported_header_prefixes].
+ pub exported_header_prefixes: Vec<&'a str>,
+ /// See [`CFG.exported_header_links`][CFG#cfgexported_header_links].
+ pub exported_header_links: Vec<&'a str>,
+ marker: PhantomData<*const ()>, // !Send + !Sync
+}
+
+/// Global configuration of the current build.
+///
+/// <br>
+///
+/// <div style="float:right;margin:22px 50px 0;font-size:1.15em;color:#444"><strong>&str</strong></div>
+///
+/// ## **`CFG.include_prefix`**
+///
+/// The prefix at which C++ code from your crate as well as directly dependent
+/// crates can access the code generated during this build.
+///
+/// By default, the `include_prefix` is equal to the name of the current crate.
+/// That means if your crate is called `demo` and has Rust source files in a
+/// *src/* directory and maybe some handwritten C++ header files in an
+/// *include/* directory, then the current crate as well as downstream crates
+/// might include them as follows:
+///
+/// ```
+/// # const _: &str = stringify! {
+/// // include one of the handwritten headers:
+/// #include "demo/include/wow.h"
+///
+/// // include a header generated from Rust cxx::bridge:
+/// #include "demo/src/lib.rs.h"
+/// # };
+/// ```
+///
+/// By modifying `CFG.include_prefix` we can substitute a prefix that is
+/// different from the crate name if desired. Here we'll change it to
+/// `"path/to"` which will make import paths take the form
+/// `"path/to/include/wow.h"` and `"path/to/src/lib.rs.h"`.
+///
+/// ```no_run
+/// // build.rs
+///
+/// use cxx_build::CFG;
+///
+/// fn main() {
+/// CFG.include_prefix = "path/to";
+///
+/// cxx_build::bridge("src/lib.rs")
+/// .file("src/demo.cc") // probably contains `#include "path/to/src/lib.rs.h"`
+/// /* ... */
+/// .compile("demo");
+/// }
+/// ```
+///
+/// Note that cross-crate imports are only made available between **direct
+/// dependencies**. Another crate must directly depend on your crate in order to
+/// #include its headers; a transitive dependency is not sufficient.
+/// Additionally, headers from a direct dependency are only importable if the
+/// dependency's Cargo.toml manifest contains a `links` key. If not, its headers
+/// will not be importable from outside of the same crate.
+///
+/// <br>
+///
+/// <div style="float:right;margin:22px 50px 0;font-size:1.15em;color:#444"><strong>Vec<&Path></strong></div>
+///
+/// ## **`CFG.exported_header_dirs`**
+///
+/// A vector of absolute paths. The current crate, directly dependent crates,
+/// and further crates to which this crate's headers are exported (see below)
+/// will be able to `#include` headers from these directories.
+///
+/// Adding a directory to `exported_header_dirs` is similar to adding it to the
+/// current build via the `cc` crate's [`Build::include`][cc::Build::include],
+/// but *also* makes the directory available to downstream crates that want to
+/// `#include` one of the headers from your crate. If the dir were added only
+/// using `Build::include`, the downstream crate including your header would
+/// need to manually add the same directory to their own build as well.
+///
+/// When using `exported_header_dirs`, your crate must also set a `links` key
+/// for itself in Cargo.toml. See [*the `links` manifest key*][links]. The
+/// reason is that Cargo imposes no ordering on the execution of build scripts
+/// without a `links` key, which means the downstream crate's build script might
+/// execute before yours decides what to put into `exported_header_dirs`.
+///
+/// [links]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
+///
+/// ### Example
+///
+/// One of your crate's headers wants to include a system library, such as
+/// `#include "Python.h"`.
+///
+/// ```no_run
+/// // build.rs
+///
+/// use cxx_build::CFG;
+/// use std::path::PathBuf;
+///
+/// fn main() {
+/// let python3 = pkg_config::probe_library("python3").unwrap();
+/// let python_include_paths = python3.include_paths.iter().map(PathBuf::as_path);
+/// CFG.exported_header_dirs.extend(python_include_paths);
+///
+/// cxx_build::bridge("src/bridge.rs").compile("demo");
+/// }
+/// ```
+///
+/// ### Example
+///
+/// Your crate wants to rearrange the headers that it exports vs how they're
+/// laid out locally inside the crate's source directory.
+///
+/// Suppose the crate as published contains a file at `./include/myheader.h` but
+/// wants it available to downstream crates as `#include "foo/v1/public.h"`.
+///
+/// ```no_run
+/// // build.rs
+///
+/// use cxx_build::CFG;
+/// use std::path::Path;
+/// use std::{env, fs};
+///
+/// fn main() {
+/// let out_dir = env::var_os("OUT_DIR").unwrap();
+/// let headers = Path::new(&out_dir).join("headers");
+/// CFG.exported_header_dirs.push(&headers);
+///
+/// // We contain `include/myheader.h` locally, but
+/// // downstream will use `#include "foo/v1/public.h"`
+/// let foo = headers.join("foo").join("v1");
+/// fs::create_dir_all(&foo).unwrap();
+/// fs::copy("include/myheader.h", foo.join("public.h")).unwrap();
+///
+/// cxx_build::bridge("src/bridge.rs").compile("demo");
+/// }
+/// ```
+///
+/// <p style="margin:0"><br><br></p>
+///
+/// <div style="float:right;margin:22px 50px 0;font-size:1.15em;color:#444"><strong>Vec<&str></strong></div>
+///
+/// ## **`CFG.exported_header_prefixes`**
+///
+/// Vector of strings. These each refer to the `include_prefix` of one of your
+/// direct dependencies, or a prefix thereof. They describe which of your
+/// dependencies participate in your crate's C++ public API, as opposed to
+/// private use by your crate's implementation.
+///
+/// As a general rule, if one of your headers `#include`s something from one of
+/// your dependencies, you need to put that dependency's `include_prefix` into
+/// `CFG.exported_header_prefixes` (*or* their `links` key into
+/// `CFG.exported_header_links`; see below). On the other hand if only your C++
+/// implementation files and *not* your headers are importing from the
+/// dependency, you do not export that dependency.
+///
+/// The significance of exported headers is that if downstream code (crate 𝒜)
+/// contains an `#include` of a header from your crate (ℬ) and your header
+/// contains an `#include` of something from your dependency (𝒞), the exported
+/// dependency 𝒞 becomes available during the downstream crate 𝒜's build.
+/// Otherwise the downstream crate 𝒜 doesn't know about 𝒞 and wouldn't be able
+/// to find what header your header is referring to, and would fail to build.
+///
+/// When using `exported_header_prefixes`, your crate must also set a `links`
+/// key for itself in Cargo.toml.
+///
+/// ### Example
+///
+/// Suppose you have a crate with 5 direct dependencies and the `include_prefix`
+/// for each one are:
+///
+/// - "crate0"
+/// - "group/api/crate1"
+/// - "group/api/crate2"
+/// - "group/api/contrib/crate3"
+/// - "detail/crate4"
+///
+/// Your header involves types from the first four so we re-export those as part
+/// of your public API, while crate4 is only used internally by your cc file not
+/// your header, so we do not export:
+///
+/// ```no_run
+/// // build.rs
+///
+/// use cxx_build::CFG;
+///
+/// fn main() {
+/// CFG.exported_header_prefixes = vec!["crate0", "group/api"];
+///
+/// cxx_build::bridge("src/bridge.rs")
+/// .file("src/impl.cc")
+/// .compile("demo");
+/// }
+/// ```
+///
+/// <p style="margin:0"><br><br></p>
+///
+/// <div style="float:right;margin:22px 50px 0;font-size:1.15em;color:#444"><strong>Vec<&str></strong></div>
+///
+/// ## **`CFG.exported_header_links`**
+///
+/// Vector of strings. These each refer to the `links` attribute ([*the `links`
+/// manifest key*][links]) of one of your crate's direct dependencies.
+///
+/// This achieves an equivalent result to `CFG.exported_header_prefixes` by
+/// re-exporting a dependency as part of your crate's public API, except with
+/// finer grained control for cases when multiple crates might be sharing the
+/// same `include_prefix` and you'd like to export some but not others. Links
+/// attributes are guaranteed to be unique identifiers by Cargo.
+///
+/// When using `exported_header_links`, your crate must also set a `links` key
+/// for itself in Cargo.toml.
+///
+/// ### Example
+///
+/// ```no_run
+/// // build.rs
+///
+/// use cxx_build::CFG;
+///
+/// fn main() {
+/// CFG.exported_header_links.push("git2");
+///
+/// cxx_build::bridge("src/bridge.rs").compile("demo");
+/// }
+/// ```
+#[cfg(doc)]
+pub static mut CFG: Cfg = Cfg {
+ include_prefix: "",
+ exported_header_dirs: Vec::new(),
+ exported_header_prefixes: Vec::new(),
+ exported_header_links: Vec::new(),
+ marker: PhantomData,
+};
+
+impl<'a> Debug for Cfg<'a> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ let Self {
+ include_prefix,
+ exported_header_dirs,
+ exported_header_prefixes,
+ exported_header_links,
+ marker: _,
+ } = self;
+ formatter
+ .debug_struct("Cfg")
+ .field("include_prefix", include_prefix)
+ .field("exported_header_dirs", exported_header_dirs)
+ .field("exported_header_prefixes", exported_header_prefixes)
+ .field("exported_header_links", exported_header_links)
+ .finish()
+ }
+}
+
+#[cfg(not(doc))]
+pub use self::r#impl::Cfg::CFG;
+
+#[cfg(not(doc))]
+mod r#impl {
+ use crate::intern::{intern, InternedString};
+ use crate::vec::{self, InternedVec as _};
+ use lazy_static::lazy_static;
+ use std::cell::RefCell;
+ use std::collections::HashMap;
+ use std::fmt::{self, Debug};
+ use std::marker::PhantomData;
+ use std::ops::{Deref, DerefMut};
+ use std::sync::{PoisonError, RwLock};
+
+ struct CurrentCfg {
+ include_prefix: InternedString,
+ exported_header_dirs: Vec<InternedString>,
+ exported_header_prefixes: Vec<InternedString>,
+ exported_header_links: Vec<InternedString>,
+ }
+
+ impl CurrentCfg {
+ fn default() -> Self {
+ let include_prefix = crate::env_os("CARGO_PKG_NAME")
+ .map(|pkg| intern(&pkg.to_string_lossy()))
+ .unwrap_or_default();
+ let exported_header_dirs = Vec::new();
+ let exported_header_prefixes = Vec::new();
+ let exported_header_links = Vec::new();
+ CurrentCfg {
+ include_prefix,
+ exported_header_dirs,
+ exported_header_prefixes,
+ exported_header_links,
+ }
+ }
+ }
+
+ lazy_static! {
+ static ref CURRENT: RwLock<CurrentCfg> = RwLock::new(CurrentCfg::default());
+ }
+
+ thread_local! {
+ // FIXME: If https://github.com/rust-lang/rust/issues/77425 is resolved,
+ // we can delete this thread local side table and instead make each CFG
+ // instance directly own the associated super::Cfg.
+ //
+ // #[allow(const_item_mutation)]
+ // pub const CFG: Cfg = Cfg {
+ // cfg: AtomicPtr::new(ptr::null_mut()),
+ // };
+ // pub struct Cfg {
+ // cfg: AtomicPtr<super::Cfg>,
+ // }
+ //
+ static CONST_DEREFS: RefCell<HashMap<Handle, Box<super::Cfg<'static>>>> = RefCell::default();
+ }
+
+ #[derive(Eq, PartialEq, Hash)]
+ struct Handle(*const Cfg<'static>);
+
+ impl<'a> Cfg<'a> {
+ fn current() -> super::Cfg<'a> {
+ let current = CURRENT.read().unwrap_or_else(PoisonError::into_inner);
+ let include_prefix = current.include_prefix.str();
+ let exported_header_dirs = current.exported_header_dirs.vec();
+ let exported_header_prefixes = current.exported_header_prefixes.vec();
+ let exported_header_links = current.exported_header_links.vec();
+ super::Cfg {
+ include_prefix,
+ exported_header_dirs,
+ exported_header_prefixes,
+ exported_header_links,
+ marker: PhantomData,
+ }
+ }
+
+ const fn handle(self: &Cfg<'a>) -> Handle {
+ Handle(<*const Cfg>::cast(self))
+ }
+ }
+
+ // Since super::Cfg is !Send and !Sync, all Cfg are thread local and will
+ // drop on the same thread where they were created.
+ pub enum Cfg<'a> {
+ Mut(super::Cfg<'a>),
+ CFG,
+ }
+
+ impl<'a> Debug for Cfg<'a> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ if let Cfg::Mut(cfg) = self {
+ Debug::fmt(cfg, formatter)
+ } else {
+ Debug::fmt(&Cfg::current(), formatter)
+ }
+ }
+ }
+
+ impl<'a> Deref for Cfg<'a> {
+ type Target = super::Cfg<'a>;
+
+ fn deref(&self) -> &Self::Target {
+ if let Cfg::Mut(cfg) = self {
+ cfg
+ } else {
+ let cfg = CONST_DEREFS.with(|derefs| -> *mut super::Cfg {
+ &mut **derefs
+ .borrow_mut()
+ .entry(self.handle())
+ .or_insert_with(|| Box::new(Cfg::current()))
+ });
+ unsafe { &mut *cfg }
+ }
+ }
+ }
+
+ impl<'a> DerefMut for Cfg<'a> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ if let Cfg::CFG = self {
+ CONST_DEREFS.with(|derefs| derefs.borrow_mut().remove(&self.handle()));
+ *self = Cfg::Mut(Cfg::current());
+ }
+ match self {
+ Cfg::Mut(cfg) => cfg,
+ Cfg::CFG => unreachable!(),
+ }
+ }
+ }
+
+ impl<'a> Drop for Cfg<'a> {
+ fn drop(&mut self) {
+ if let Cfg::Mut(cfg) = self {
+ let mut current = CURRENT.write().unwrap_or_else(PoisonError::into_inner);
+ current.include_prefix = intern(cfg.include_prefix);
+ current.exported_header_dirs = vec::intern(&cfg.exported_header_dirs);
+ current.exported_header_prefixes = vec::intern(&cfg.exported_header_prefixes);
+ current.exported_header_links = vec::intern(&cfg.exported_header_links);
+ } else {
+ CONST_DEREFS.with(|derefs| derefs.borrow_mut().remove(&self.handle()));
+ }
+ }
+ }
+}
diff --git a/gen/build/src/deps.rs b/gen/build/src/deps.rs
new file mode 100644
index 0000000..fb80072
--- /dev/null
+++ b/gen/build/src/deps.rs
@@ -0,0 +1,107 @@
+use std::collections::BTreeMap;
+use std::env;
+use std::ffi::OsString;
+use std::path::PathBuf;
+
+#[derive(Default)]
+pub struct Crate {
+ pub include_prefix: Option<PathBuf>,
+ pub links: Option<OsString>,
+ pub header_dirs: Vec<HeaderDir>,
+}
+
+pub struct HeaderDir {
+ pub exported: bool,
+ pub path: PathBuf,
+}
+
+impl Crate {
+ pub fn print_to_cargo(&self) {
+ if let Some(include_prefix) = &self.include_prefix {
+ println!(
+ "cargo:CXXBRIDGE_PREFIX={}",
+ include_prefix.to_string_lossy(),
+ );
+ }
+ if let Some(links) = &self.links {
+ println!("cargo:CXXBRIDGE_LINKS={}", links.to_string_lossy());
+ }
+ for (i, header_dir) in self.header_dirs.iter().enumerate() {
+ if header_dir.exported {
+ println!(
+ "cargo:CXXBRIDGE_DIR{}={}",
+ i,
+ header_dir.path.to_string_lossy(),
+ );
+ }
+ }
+ }
+}
+
+pub fn direct_dependencies() -> Vec<Crate> {
+ let mut crates: BTreeMap<String, Crate> = BTreeMap::new();
+ let mut exported_header_dirs: BTreeMap<String, Vec<(usize, PathBuf)>> = BTreeMap::new();
+
+ // Only variables set from a build script of direct dependencies are
+ // observable. That's exactly what we want! Your crate needs to declare a
+ // direct dependency on the other crate in order to be able to #include its
+ // headers.
+ //
+ // Also, they're only observable if the dependency's manifest contains a
+ // `links` key. This is important because Cargo imposes no ordering on the
+ // execution of build scripts without a `links` key. When exposing a
+ // generated header for the current crate to #include, we need to be sure
+ // the dependency's build script has already executed and emitted that
+ // generated header.
+ //
+ // References:
+ // - https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
+ // - https://doc.rust-lang.org/cargo/reference/build-script-examples.html#using-another-sys-crate
+ for (k, v) in env::vars_os() {
+ let mut k = k.to_string_lossy().into_owned();
+ if !k.starts_with("DEP_") {
+ continue;
+ }
+
+ if k.ends_with("_CXXBRIDGE_PREFIX") {
+ k.truncate(k.len() - "_CXXBRIDGE_PREFIX".len());
+ crates.entry(k).or_default().include_prefix = Some(PathBuf::from(v));
+ continue;
+ }
+
+ if k.ends_with("_CXXBRIDGE_LINKS") {
+ k.truncate(k.len() - "_CXXBRIDGE_LINKS".len());
+ crates.entry(k).or_default().links = Some(v);
+ continue;
+ }
+
+ let without_counter = k.trim_end_matches(|ch: char| ch.is_ascii_digit());
+ let counter_len = k.len() - without_counter.len();
+ if counter_len == 0 || !without_counter.ends_with("_CXXBRIDGE_DIR") {
+ continue;
+ }
+
+ let sort_key = k[k.len() - counter_len..]
+ .parse::<usize>()
+ .unwrap_or(usize::MAX);
+ k.truncate(k.len() - counter_len - "_CXXBRIDGE_DIR".len());
+ exported_header_dirs
+ .entry(k)
+ .or_default()
+ .push((sort_key, PathBuf::from(v)));
+ }
+
+ for (k, mut dirs) in exported_header_dirs {
+ dirs.sort_by_key(|(sort_key, _dir)| *sort_key);
+ crates
+ .entry(k)
+ .or_default()
+ .header_dirs
+ .extend(dirs.into_iter().map(|(_sort_key, dir)| HeaderDir {
+ exported: true,
+ path: dir,
+ }));
+ }
+
+ crates.into_iter().map(|entry| entry.1).collect()
+}
diff --git a/gen/build/src/error.rs b/gen/build/src/error.rs
index 740ab94..99d7a30 100644
--- a/gen/build/src/error.rs
+++ b/gen/build/src/error.rs
@@ -1,22 +1,83 @@
+use crate::cfg::CFG;
+use crate::gen::fs;
use std::error::Error as StdError;
+use std::ffi::OsString;
use std::fmt::{self, Display};
-use std::io;
+use std::path::Path;
pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
pub(super) enum Error {
- MissingOutDir,
- TargetDir,
- Io(io::Error),
+ NoEnv(OsString),
+ Fs(fs::Error),
+ ExportedDirNotAbsolute(&'static Path),
+ ExportedEmptyPrefix,
+ ExportedDirsWithoutLinks,
+ ExportedPrefixesWithoutLinks,
+ ExportedLinksWithoutLinks,
+ UnusedExportedPrefix(&'static str),
+ UnusedExportedLinks(&'static str),
}
+macro_rules! expr {
+ ($expr:expr) => {{
+ let _ = $expr; // ensure it doesn't fall out of sync with CFG definition
+ stringify!($expr)
+ }};
+}
+
+const LINKS_DOCUMENTATION: &str =
+ "https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key";
+
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
- Error::MissingOutDir => write!(f, "missing OUT_DIR environment variable"),
- Error::TargetDir => write!(f, "failed to locate target dir"),
- Error::Io(err) => err.fmt(f),
+ Error::NoEnv(var) => {
+ write!(f, "missing {} environment variable", var.to_string_lossy())
+ }
+ Error::Fs(err) => err.fmt(f),
+ Error::ExportedDirNotAbsolute(path) => write!(
+ f,
+ "element of {} must be absolute path, but was: {:?}",
+ expr!(CFG.exported_header_dirs),
+ path,
+ ),
+ Error::ExportedEmptyPrefix => write!(
+ f,
+ "element of {} must not be empty string",
+ expr!(CFG.exported_header_prefixes),
+ ),
+ Error::ExportedDirsWithoutLinks => write!(
+ f,
+ "if {} is nonempty then `links` needs to be set in Cargo.toml; see {}",
+ expr!(CFG.exported_header_dirs),
+ LINKS_DOCUMENTATION,
+ ),
+ Error::ExportedPrefixesWithoutLinks => write!(
+ f,
+ "if {} is nonempty then `links` needs to be set in Cargo.toml; see {}",
+ expr!(CFG.exported_header_prefixes),
+ LINKS_DOCUMENTATION,
+ ),
+ Error::ExportedLinksWithoutLinks => write!(
+ f,
+ "if {} is nonempty then `links` needs to be set in Cargo.toml; see {}",
+ expr!(CFG.exported_header_links),
+ LINKS_DOCUMENTATION,
+ ),
+ Error::UnusedExportedPrefix(unused) => write!(
+ f,
+ "unused element in {}: {:?} does not match the include prefix of any direct dependency",
+ expr!(CFG.exported_header_prefixes),
+ unused,
+ ),
+ Error::UnusedExportedLinks(unused) => write!(
+ f,
+ "unused element in {}: {:?} does not match the `links` attribute any direct dependency",
+ expr!(CFG.exported_header_links),
+ unused,
+ ),
}
}
}
@@ -24,14 +85,14 @@
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
- Error::Io(err) => Some(err),
+ Error::Fs(err) => err.source(),
_ => None,
}
}
}
-impl From<io::Error> for Error {
- fn from(err: io::Error) -> Self {
- Error::Io(err)
+impl From<fs::Error> for Error {
+ fn from(err: fs::Error) -> Self {
+ Error::Fs(err)
}
}
diff --git a/gen/build/src/intern.rs b/gen/build/src/intern.rs
new file mode 100644
index 0000000..51bc07c
--- /dev/null
+++ b/gen/build/src/intern.rs
@@ -0,0 +1,28 @@
+use lazy_static::lazy_static;
+use std::collections::HashSet;
+use std::sync::{Mutex, PoisonError};
+
+#[derive(Copy, Clone, Default)]
+pub struct InternedString(&'static str);
+
+impl InternedString {
+ pub fn str(self) -> &'static str {
+ self.0
+ }
+}
+
+pub fn intern(s: &str) -> InternedString {
+ lazy_static! {
+ static ref INTERN: Mutex<HashSet<&'static str>> = Mutex::new(HashSet::new());
+ }
+
+ let mut set = INTERN.lock().unwrap_or_else(PoisonError::into_inner);
+ InternedString(match set.get(s) {
+ Some(interned) => *interned,
+ None => {
+ let interned = Box::leak(Box::from(s));
+ set.insert(interned);
+ interned
+ }
+ })
+}
diff --git a/gen/build/src/lib.rs b/gen/build/src/lib.rs
index 14683b6..db15335 100644
--- a/gen/build/src/lib.rs
+++ b/gen/build/src/lib.rs
@@ -15,18 +15,18 @@
//!
//! fn main() {
//! cxx_build::bridge("src/main.rs")
-//! .file("../demo-cxx/demo.cc")
+//! .file("src/demo.cc")
//! .flag_if_supported("-std=c++11")
//! .compile("cxxbridge-demo");
//!
//! println!("cargo:rerun-if-changed=src/main.rs");
-//! println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
-//! println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
+//! println!("cargo:rerun-if-changed=src/demo.cc");
+//! println!("cargo:rerun-if-changed=include/demo.h");
//! }
//! ```
//!
-//! A runnable working setup with this build script is shown in the
-//! *demo-rs* and *demo-cxx* directories of [https://github.com/dtolnay/cxx].
+//! A runnable working setup with this build script is shown in the *demo*
+//! directory of [https://github.com/dtolnay/cxx].
//!
//! [https://github.com/dtolnay/cxx]: https://github.com/dtolnay/cxx
//!
@@ -46,33 +46,50 @@
//! ```
#![allow(
+ clippy::drop_copy,
clippy::inherent_to_string,
clippy::needless_doctest_main,
clippy::new_without_default,
+ clippy::or_fun_call,
clippy::toplevel_ref_arg
)]
+mod cfg;
+mod deps;
mod error;
mod gen;
+mod intern;
+mod out;
mod paths;
mod syntax;
+mod target;
+mod vec;
-use crate::error::Result;
+use crate::deps::{Crate, HeaderDir};
+use crate::error::{Error, Result};
+use crate::gen::error::report;
use crate::gen::Opt;
-use anyhow::anyhow;
-use std::fs;
+use crate::paths::PathExt;
+use crate::target::TargetDir;
+use cc::Build;
+use std::collections::btree_map::Entry;
+use std::collections::{BTreeMap, BTreeSet};
+use std::env;
+use std::ffi::{OsStr, OsString};
use std::io::{self, Write};
use std::iter;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use std::process;
+pub use crate::cfg::{Cfg, CFG};
+
/// This returns a [`cc::Build`] on which you should continue to set up any
/// additional source files or compiler flags, and lastly call its [`compile`]
/// method to execute the C++ build.
///
/// [`compile`]: https://docs.rs/cc/1.0.49/cc/struct.Build.html#method.compile
#[must_use]
-pub fn bridge(rust_source_file: impl AsRef<Path>) -> cc::Build {
+pub fn bridge(rust_source_file: impl AsRef<Path>) -> Build {
bridges(iter::once(rust_source_file))
}
@@ -82,41 +99,301 @@
/// ```no_run
/// let source_files = vec!["src/main.rs", "src/path/to/other.rs"];
/// cxx_build::bridges(source_files)
-/// .file("../demo-cxx/demo.cc")
+/// .file("src/demo.cc")
/// .flag_if_supported("-std=c++11")
/// .compile("cxxbridge-demo");
/// ```
-pub fn bridges(rust_source_files: impl IntoIterator<Item = impl AsRef<Path>>) -> cc::Build {
- let mut build = paths::cc_build();
+#[must_use]
+pub fn bridges(rust_source_files: impl IntoIterator<Item = impl AsRef<Path>>) -> Build {
+ let ref mut rust_source_files = rust_source_files.into_iter();
+ build(rust_source_files).unwrap_or_else(|err| {
+ let _ = writeln!(io::stderr(), "\n\ncxxbridge error: {}\n\n", report(err));
+ process::exit(1);
+ })
+}
+
+struct Project {
+ include_prefix: PathBuf,
+ manifest_dir: PathBuf,
+ // The `links = "..."` value from Cargo.toml.
+ links_attribute: Option<OsString>,
+ // Output directory as received from Cargo.
+ out_dir: PathBuf,
+ // Directory into which to symlink all generated code.
+ //
+ // This is *not* used for an #include path, only as a debugging convenience.
+ // Normally available at target/cxxbridge/ if we are able to know where the
+ // target dir is, otherwise under a common scratch dir.
+ //
+ // The reason this isn't the #include dir is that we do not want builds to
+ // have access to headers from arbitrary other parts of the dependency
+ // graph. Using a global directory for all builds would be both a race
+ // condition depending on what order Cargo randomly executes the build
+ // scripts, as well as semantically undesirable for builds not to have to
+ // declare their real dependencies.
+ shared_dir: PathBuf,
+}
+
+impl Project {
+ fn init() -> Result<Self> {
+ let include_prefix = Path::new(CFG.include_prefix);
+ assert!(include_prefix.is_relative());
+ let include_prefix = include_prefix.components().collect();
+
+ let links_attribute = env::var_os("CARGO_MANIFEST_LINKS");
+
+ let manifest_dir = paths::manifest_dir()?;
+ let out_dir = paths::out_dir()?;
+
+ let shared_dir = match target::find_target_dir(&out_dir) {
+ TargetDir::Path(target_dir) => target_dir.join("cxxbridge"),
+ TargetDir::Unknown => scratch::path("cxxbridge"),
+ };
+
+ Ok(Project {
+ include_prefix,
+ manifest_dir,
+ links_attribute,
+ out_dir,
+ shared_dir,
+ })
+ }
+}
+
+// We lay out the OUT_DIR as follows. Everything is namespaced under a cxxbridge
+// subdirectory to avoid stomping on other things that the caller's build script
+// might be doing inside OUT_DIR.
+//
+// $OUT_DIR/
+// cxxbridge/
+// crate/
+// $CARGO_PKG_NAME -> $CARGO_MANIFEST_DIR
+// include/
+// rust/
+// cxx.h
+// $CARGO_PKG_NAME/
+// .../
+// lib.rs.h
+// sources/
+// $CARGO_PKG_NAME/
+// .../
+// lib.rs.cc
+//
+// The crate/ and include/ directories are placed on the #include path for the
+// current build as well as for downstream builds that have a direct dependency
+// on the current crate.
+fn build(rust_source_files: &mut dyn Iterator<Item = impl AsRef<Path>>) -> Result<Build> {
+ let ref prj = Project::init()?;
+ validate_cfg(prj)?;
+ let this_crate = make_this_crate(prj)?;
+ this_crate.print_to_cargo();
+
+ let mut build = Build::new();
build.cpp(true);
build.cpp_link_stdlib(None); // linked via link-cplusplus crate
for path in rust_source_files {
- if let Err(err) = try_generate_bridge(&mut build, path.as_ref()) {
- let _ = writeln!(io::stderr(), "\n\ncxxbridge error: {:?}\n\n", anyhow!(err));
- process::exit(1);
+ generate_bridge(prj, &mut build, path.as_ref())?;
+ }
+
+ eprintln!("\nCXX include path:");
+ for header_dir in this_crate.header_dirs {
+ build.include(&header_dir.path);
+ if header_dir.exported {
+ eprintln!(" {}", header_dir.path.display());
+ } else {
+ eprintln!(" {} (private)", header_dir.path.display());
}
}
- build
+ Ok(build)
}
-fn try_generate_bridge(build: &mut cc::Build, rust_source_file: &Path) -> Result<()> {
- let header = gen::do_generate_header(rust_source_file, Opt::default());
- let header_path = paths::out_with_extension(rust_source_file, ".h")?;
- fs::create_dir_all(header_path.parent().unwrap())?;
- fs::write(&header_path, header)?;
- paths::symlink_header(&header_path, rust_source_file);
+fn validate_cfg(prj: &Project) -> Result<()> {
+ for exported_dir in &CFG.exported_header_dirs {
+ if !exported_dir.is_absolute() {
+ return Err(Error::ExportedDirNotAbsolute(exported_dir));
+ }
+ }
- let bridge = gen::do_generate_bridge(rust_source_file, Opt::default());
- let bridge_path = paths::out_with_extension(rust_source_file, ".cc")?;
- fs::write(&bridge_path, bridge)?;
- build.file(&bridge_path);
+ for prefix in &CFG.exported_header_prefixes {
+ if prefix.is_empty() {
+ return Err(Error::ExportedEmptyPrefix);
+ }
+ }
- let ref cxx_h = paths::include_dir()?.join("rust").join("cxx.h");
- let _ = fs::create_dir_all(cxx_h.parent().unwrap());
- let _ = fs::remove_file(cxx_h);
- let _ = fs::write(cxx_h, gen::include::HEADER);
+ if prj.links_attribute.is_none() {
+ if !CFG.exported_header_dirs.is_empty() {
+ return Err(Error::ExportedDirsWithoutLinks);
+ }
+ if !CFG.exported_header_prefixes.is_empty() {
+ return Err(Error::ExportedPrefixesWithoutLinks);
+ }
+ if !CFG.exported_header_links.is_empty() {
+ return Err(Error::ExportedLinksWithoutLinks);
+ }
+ }
Ok(())
}
+
+fn make_this_crate(prj: &Project) -> Result<Crate> {
+ let crate_dir = make_crate_dir(prj);
+ let include_dir = make_include_dir(prj)?;
+
+ let mut this_crate = Crate {
+ include_prefix: Some(prj.include_prefix.clone()),
+ links: prj.links_attribute.clone(),
+ header_dirs: Vec::new(),
+ };
+
+ // The generated code directory (include_dir) is placed in front of
+ // crate_dir on the include line so that `#include "path/to/file.rs"` from
+ // C++ "magically" works and refers to the API generated from that Rust
+ // source file.
+ this_crate.header_dirs.push(HeaderDir {
+ exported: true,
+ path: include_dir,
+ });
+
+ if let Some(crate_dir) = crate_dir {
+ this_crate.header_dirs.push(HeaderDir {
+ exported: true,
+ path: crate_dir,
+ });
+ }
+
+ for exported_dir in &CFG.exported_header_dirs {
+ this_crate.header_dirs.push(HeaderDir {
+ exported: true,
+ path: PathBuf::from(exported_dir),
+ });
+ }
+
+ let mut header_dirs_index = BTreeMap::new();
+ let mut used_header_links = BTreeSet::new();
+ let mut used_header_prefixes = BTreeSet::new();
+ for krate in deps::direct_dependencies() {
+ let mut is_link_exported = || match &krate.links {
+ None => false,
+ Some(links_attribute) => CFG.exported_header_links.iter().any(|&exported| {
+ let matches = links_attribute == exported;
+ if matches {
+ used_header_links.insert(exported);
+ }
+ matches
+ }),
+ };
+
+ let mut is_prefix_exported = || match &krate.include_prefix {
+ None => false,
+ Some(include_prefix) => CFG.exported_header_prefixes.iter().any(|&exported| {
+ let matches = include_prefix.starts_with(exported);
+ if matches {
+ used_header_prefixes.insert(exported);
+ }
+ matches
+ }),
+ };
+
+ let exported = is_link_exported() || is_prefix_exported();
+
+ for dir in krate.header_dirs {
+ // Deduplicate dirs reachable via multiple transitive dependencies.
+ match header_dirs_index.entry(dir.path.clone()) {
+ Entry::Vacant(entry) => {
+ entry.insert(this_crate.header_dirs.len());
+ this_crate.header_dirs.push(HeaderDir {
+ exported,
+ path: dir.path,
+ });
+ }
+ Entry::Occupied(entry) => {
+ let index = *entry.get();
+ this_crate.header_dirs[index].exported |= exported;
+ }
+ }
+ }
+ }
+
+ if let Some(unused) = CFG
+ .exported_header_links
+ .iter()
+ .find(|&exported| !used_header_links.contains(exported))
+ {
+ return Err(Error::UnusedExportedLinks(unused));
+ }
+
+ if let Some(unused) = CFG
+ .exported_header_prefixes
+ .iter()
+ .find(|&exported| !used_header_prefixes.contains(exported))
+ {
+ return Err(Error::UnusedExportedPrefix(unused));
+ }
+
+ Ok(this_crate)
+}
+
+fn make_crate_dir(prj: &Project) -> Option<PathBuf> {
+ if prj.include_prefix.as_os_str().is_empty() {
+ return Some(prj.manifest_dir.clone());
+ }
+ let crate_dir = prj.out_dir.join("cxxbridge").join("crate");
+ let link = crate_dir.join(&prj.include_prefix);
+ if out::symlink_dir(&prj.manifest_dir, link).is_ok() {
+ Some(crate_dir)
+ } else {
+ None
+ }
+}
+
+fn make_include_dir(prj: &Project) -> Result<PathBuf> {
+ let include_dir = prj.out_dir.join("cxxbridge").join("include");
+ let cxx_h = include_dir.join("rust").join("cxx.h");
+ let ref shared_cxx_h = prj.shared_dir.join("rust").join("cxx.h");
+ if let Some(ref original) = env::var_os("DEP_CXXBRIDGE05_HEADER") {
+ out::symlink_file(original, cxx_h)?;
+ out::symlink_file(original, shared_cxx_h)?;
+ } else {
+ out::write(shared_cxx_h, gen::include::HEADER.as_bytes())?;
+ out::symlink_file(shared_cxx_h, cxx_h)?;
+ }
+ Ok(include_dir)
+}
+
+fn generate_bridge(prj: &Project, build: &mut Build, rust_source_file: &Path) -> Result<()> {
+ let opt = Opt {
+ allow_dot_includes: false,
+ ..Opt::default()
+ };
+ let generated = gen::generate_from_path(rust_source_file, &opt);
+ let ref rel_path = paths::local_relative_path(rust_source_file);
+
+ let cxxbridge = prj.out_dir.join("cxxbridge");
+ let include_dir = cxxbridge.join("include").join(&prj.include_prefix);
+ let sources_dir = cxxbridge.join("sources").join(&prj.include_prefix);
+
+ let ref rel_path_h = rel_path.with_appended_extension(".h");
+ let ref header_path = include_dir.join(rel_path_h);
+ out::write(header_path, &generated.header)?;
+
+ let ref link_path = include_dir.join(rel_path);
+ let _ = out::symlink_file(header_path, link_path);
+
+ let ref rel_path_cc = rel_path.with_appended_extension(".cc");
+ let ref implementation_path = sources_dir.join(rel_path_cc);
+ out::write(implementation_path, &generated.implementation)?;
+ build.file(implementation_path);
+
+ let shared_h = prj.shared_dir.join(&prj.include_prefix).join(rel_path_h);
+ let shared_cc = prj.shared_dir.join(&prj.include_prefix).join(rel_path_cc);
+ let _ = out::symlink_file(header_path, shared_h);
+ let _ = out::symlink_file(implementation_path, shared_cc);
+ Ok(())
+}
+
+fn env_os(key: impl AsRef<OsStr>) -> Result<OsString> {
+ let key = key.as_ref();
+ env::var_os(key).ok_or_else(|| Error::NoEnv(key.to_owned()))
+}
diff --git a/gen/build/src/out.rs b/gen/build/src/out.rs
new file mode 100644
index 0000000..b97e992
--- /dev/null
+++ b/gen/build/src/out.rs
@@ -0,0 +1,69 @@
+use crate::error::{Error, Result};
+use crate::gen::fs;
+use crate::paths;
+use std::path::Path;
+
+pub(crate) fn write(path: impl AsRef<Path>, content: &[u8]) -> Result<()> {
+ let path = path.as_ref();
+
+ let mut create_dir_error = None;
+ if path.exists() {
+ if let Ok(existing) = fs::read(path) {
+ if existing == content {
+ // Avoid bumping modified time with unchanged contents.
+ return Ok(());
+ }
+ }
+ let _ = fs::remove_file(path);
+ } else {
+ let parent = path.parent().unwrap();
+ create_dir_error = fs::create_dir_all(parent).err();
+ }
+
+ match fs::write(path, content) {
+ // As long as write succeeded, ignore any create_dir_all error.
+ Ok(()) => Ok(()),
+ // If create_dir_all and write both failed, prefer the first error.
+ Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))),
+ }
+}
+
+pub(crate) fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
+ let src = src.as_ref();
+ let dst = dst.as_ref();
+
+ let mut create_dir_error = None;
+ if dst.exists() {
+ let _ = fs::remove_file(dst).unwrap();
+ } else {
+ let parent = dst.parent().unwrap();
+ create_dir_error = fs::create_dir_all(parent).err();
+ }
+
+ match paths::symlink_or_copy(src, dst) {
+ // As long as symlink_or_copy succeeded, ignore any create_dir_all error.
+ Ok(()) => Ok(()),
+ // If create_dir_all and symlink_or_copy both failed, prefer the first error.
+ Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))),
+ }
+}
+
+pub(crate) fn symlink_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
+ let src = src.as_ref();
+ let dst = dst.as_ref();
+
+ let mut create_dir_error = None;
+ if dst.exists() {
+ let _ = paths::remove_symlink_dir(dst).unwrap();
+ } else {
+ let parent = dst.parent().unwrap();
+ create_dir_error = fs::create_dir_all(parent).err();
+ }
+
+ match paths::symlink_dir(src, dst) {
+ // As long as symlink_dir succeeded, ignore any create_dir_all error.
+ Ok(()) => Ok(()),
+ // If create_dir_all and symlink_dir both failed, prefer the first error.
+ Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))),
+ }
+}
diff --git a/gen/build/src/paths.rs b/gen/build/src/paths.rs
index ca183d9..4459363 100644
--- a/gen/build/src/paths.rs
+++ b/gen/build/src/paths.rs
@@ -1,116 +1,74 @@
-use crate::error::{Error, Result};
-use std::env;
-use std::fs;
-use std::path::{Path, PathBuf};
+use crate::error::Result;
+use crate::gen::fs;
+use std::ffi::OsStr;
+use std::path::{Component, Path, PathBuf};
-fn out_dir() -> Result<PathBuf> {
- env::var_os("OUT_DIR")
- .map(PathBuf::from)
- .ok_or(Error::MissingOutDir)
+pub(crate) fn manifest_dir() -> Result<PathBuf> {
+ crate::env_os("CARGO_MANIFEST_DIR").map(PathBuf::from)
}
-pub(crate) fn cc_build() -> cc::Build {
- try_cc_build().unwrap_or_default()
+pub(crate) fn out_dir() -> Result<PathBuf> {
+ crate::env_os("OUT_DIR").map(PathBuf::from)
}
-fn try_cc_build() -> Result<cc::Build> {
- let mut build = cc::Build::new();
- build.include(include_dir()?);
- build.include(target_dir()?.parent().unwrap());
- Ok(build)
-}
-
-// Symlink the header file into a predictable place. The header generated from
-// path/to/mod.rs gets linked to targets/cxxbridge/path/to/mod.rs.h.
-pub(crate) fn symlink_header(path: &Path, original: &Path) {
- let _ = try_symlink_header(path, original);
-}
-
-fn try_symlink_header(path: &Path, original: &Path) -> Result<()> {
- let suffix = relative_to_parent_of_target_dir(original)?;
- let ref dst = include_dir()?.join(suffix);
-
- fs::create_dir_all(dst.parent().unwrap())?;
- let _ = fs::remove_file(dst);
- symlink_or_copy(path, dst)?;
-
- let mut file_name = dst.file_name().unwrap().to_os_string();
- file_name.push(".h");
- let ref dst2 = dst.with_file_name(file_name);
- symlink_or_copy(path, dst2)?;
-
- Ok(())
-}
-
-fn relative_to_parent_of_target_dir(original: &Path) -> Result<PathBuf> {
- let target_dir = target_dir()?;
- let mut outer = target_dir.parent().unwrap();
- let original = canonicalize(original)?;
- loop {
- if let Ok(suffix) = original.strip_prefix(outer) {
- return Ok(suffix.to_owned());
- }
- match outer.parent() {
- Some(parent) => outer = parent,
- None => return Ok(original.components().skip(1).collect()),
+// Given a path provided by the user, determines where generated files related
+// to that path should go in our out dir. In particular we don't want to
+// accidentally write generated code upward of our out dir, even if the user
+// passed a path containing lots of `..` or an absolute path.
+pub(crate) fn local_relative_path(path: &Path) -> PathBuf {
+ let mut rel_path = PathBuf::new();
+ for component in path.components() {
+ match component {
+ Component::Prefix(_) | Component::RootDir | Component::CurDir => {}
+ Component::ParentDir => drop(rel_path.pop()), // noop if empty
+ Component::Normal(name) => rel_path.push(name),
}
}
+ rel_path
}
-pub(crate) fn out_with_extension(path: &Path, ext: &str) -> Result<PathBuf> {
- let mut file_name = path.file_name().unwrap().to_owned();
- file_name.push(ext);
-
- let out_dir = out_dir()?;
- let rel = relative_to_parent_of_target_dir(path)?;
- Ok(out_dir.join(rel).with_file_name(file_name))
+pub(crate) trait PathExt {
+ fn with_appended_extension(&self, suffix: impl AsRef<OsStr>) -> PathBuf;
}
-pub(crate) fn include_dir() -> Result<PathBuf> {
- let target_dir = target_dir()?;
- Ok(target_dir.join("cxxbridge"))
-}
-
-fn target_dir() -> Result<PathBuf> {
- let mut dir = out_dir().and_then(canonicalize)?;
- loop {
- if dir.ends_with("target") {
- return Ok(dir);
- }
- if !dir.pop() {
- return Err(Error::TargetDir);
- }
+impl PathExt for Path {
+ fn with_appended_extension(&self, suffix: impl AsRef<OsStr>) -> PathBuf {
+ let mut file_name = self.file_name().unwrap().to_owned();
+ file_name.push(suffix);
+ self.with_file_name(file_name)
}
}
-#[cfg(not(windows))]
-fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
- Ok(fs::canonicalize(path)?)
-}
-
-#[cfg(windows)]
-fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf> {
- // Real fs::canonicalize on Windows produces UNC paths which cl.exe is
- // unable to handle in includes. Use a poor approximation instead.
- // https://github.com/rust-lang/rust/issues/42869
- // https://github.com/alexcrichton/cc-rs/issues/169
- Ok(env::current_dir()?.join(path))
-}
-
#[cfg(unix)]
-use std::os::unix::fs::symlink as symlink_or_copy;
+pub(crate) use self::fs::symlink_file as symlink_or_copy;
#[cfg(windows)]
-fn symlink_or_copy(src: &Path, dst: &Path) -> Result<()> {
- use std::os::windows::fs::symlink_file;
-
+pub(crate) fn symlink_or_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> fs::Result<()> {
// Pre-Windows 10, symlinks require admin privileges. Since Windows 10, they
// require Developer Mode. If it fails, fall back to copying the file.
- if symlink_file(src, dst).is_err() {
+ let src = src.as_ref();
+ let dst = dst.as_ref();
+ if fs::symlink_file(src, dst).is_err() {
fs::copy(src, dst)?;
}
Ok(())
}
#[cfg(not(any(unix, windows)))]
-use std::fs::copy as symlink_or_copy;
+pub(crate) use self::fs::copy as symlink_or_copy;
+
+#[cfg(any(unix, windows))]
+pub(crate) use self::fs::symlink_dir;
+
+#[cfg(not(any(unix, windows)))]
+pub(crate) fn symlink_dir(_src: impl AsRef<Path>, _dst: impl AsRef<Path>) -> fs::Result<()> {
+ Ok(())
+}
+
+#[cfg(not(windows))]
+pub(crate) use self::fs::remove_file as remove_symlink_dir;
+
+// On Windows, trying to use remove_file to remove a symlink which points to a
+// directory fails with "Access is denied".
+#[cfg(windows)]
+pub(crate) use self::fs::remove_dir as remove_symlink_dir;
diff --git a/gen/build/src/target.rs b/gen/build/src/target.rs
new file mode 100644
index 0000000..58ada3a
--- /dev/null
+++ b/gen/build/src/target.rs
@@ -0,0 +1,42 @@
+use std::env;
+use std::path::{Path, PathBuf};
+
+pub(crate) enum TargetDir {
+ Path(PathBuf),
+ Unknown,
+}
+
+pub(crate) fn find_target_dir(out_dir: &Path) -> TargetDir {
+ if let Some(target_dir) = env::var_os("CARGO_TARGET_DIR") {
+ let target_dir = PathBuf::from(target_dir);
+ if target_dir.is_absolute() {
+ return TargetDir::Path(target_dir);
+ } else {
+ return TargetDir::Unknown;
+ };
+ }
+
+ // fs::canonicalize on Windows produces UNC paths which cl.exe is unable to
+ // handle in includes.
+ // https://github.com/rust-lang/rust/issues/42869
+ // https://github.com/alexcrichton/cc-rs/issues/169
+ let mut also_try_canonical = cfg!(not(windows));
+
+ let mut dir = out_dir.to_owned();
+ loop {
+ if dir.join(".rustc_info.json").exists() || dir.join("CACHEDIR.TAG").exists() {
+ return TargetDir::Path(dir);
+ }
+ if dir.pop() {
+ continue;
+ }
+ if also_try_canonical {
+ if let Ok(canonical_dir) = out_dir.canonicalize() {
+ dir = canonical_dir;
+ also_try_canonical = false;
+ continue;
+ }
+ }
+ return TargetDir::Unknown;
+ }
+}
diff --git a/gen/build/src/vec.rs b/gen/build/src/vec.rs
new file mode 100644
index 0000000..ac9235e
--- /dev/null
+++ b/gen/build/src/vec.rs
@@ -0,0 +1,50 @@
+use crate::intern::{self, InternedString};
+use std::path::Path;
+
+pub trait InternedVec<T>
+where
+ T: ?Sized,
+{
+ fn vec(&self) -> Vec<&'static T>;
+}
+
+impl<T> InternedVec<T> for Vec<InternedString>
+where
+ T: ?Sized + Element,
+{
+ fn vec(&self) -> Vec<&'static T> {
+ self.iter().copied().map(Element::unintern).collect()
+ }
+}
+
+pub fn intern<T>(elements: &[&T]) -> Vec<InternedString>
+where
+ T: ?Sized + Element,
+{
+ elements.iter().copied().map(Element::intern).collect()
+}
+
+pub trait Element {
+ fn intern(&self) -> InternedString;
+ fn unintern(_: InternedString) -> &'static Self;
+}
+
+impl Element for str {
+ fn intern(&self) -> InternedString {
+ intern::intern(self)
+ }
+
+ fn unintern(interned: InternedString) -> &'static Self {
+ interned.str()
+ }
+}
+
+impl Element for Path {
+ fn intern(&self) -> InternedString {
+ intern::intern(&self.to_string_lossy())
+ }
+
+ fn unintern(interned: InternedString) -> &'static Self {
+ Path::new(interned.str())
+ }
+}
diff --git a/gen/cmd/Android.bp b/gen/cmd/Android.bp
index 30d13e3..14e48e9 100644
--- a/gen/cmd/Android.bp
+++ b/gen/cmd/Android.bp
@@ -6,12 +6,11 @@
srcs: ["src/main.rs"],
edition: "2018",
rustlibs: [
- "libanyhow",
+ "libclap",
"libcodespan_reporting",
"libcxxbridge_cmd",
"libproc_macro2",
"libquote",
- "libstructopt",
"libsyn",
],
compile_multilib: "first",
@@ -23,11 +22,10 @@
srcs: ["src/lib.rs"],
edition: "2018",
rustlibs: [
- "libanyhow",
+ "libclap",
"libcodespan_reporting",
"libproc_macro2",
"libquote",
- "libstructopt",
"libsyn",
],
compile_multilib: "first",
@@ -35,26 +33,17 @@
// dependent_library ["feature_list"]
// ansi_term-0.11.0
-// anyhow-1.0.32 "default,std"
// atty-0.2.14
// bitflags-1.2.1 "default"
-// clap-2.33.1 "ansi_term,atty,color,default,strsim,suggestions,vec_map"
+// clap-2.33.3 "ansi_term,atty,color,default,strsim,suggestions,vec_map"
// codespan-reporting-0.9.5
-// heck-0.3.1
-// lazy_static-1.4.0
-// libc-0.2.74
-// proc-macro-error-1.0.4 "default,syn,syn-error"
-// proc-macro-error-attr-1.0.4
-// proc-macro2-1.0.19 "default,proc-macro,span-locations"
-// quote-1.0.7 "default,proc-macro"
+// libc-0.2.80
+// proc-macro2-1.0.24 "span-locations"
+// quote-1.0.7
// strsim-0.8.0
-// structopt-0.3.15 "default"
-// structopt-derive-0.4.8
-// syn-1.0.36 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote"
-// termcolor-1.1.0
+// syn-1.0.50 "clone-impls,full,parsing,printing,quote"
+// termcolor-1.1.2
// textwrap-0.11.0
-// unicode-segmentation-1.6.0
// unicode-width-0.1.8 "default"
// unicode-xid-0.2.1 "default"
// vec_map-0.8.2
-// version_check-0.9.2
diff --git a/gen/cmd/Cargo.toml b/gen/cmd/Cargo.toml
index 7a80a2f..f3bdebf 100644
--- a/gen/cmd/Cargo.toml
+++ b/gen/cmd/Cargo.toml
@@ -1,11 +1,12 @@
[package]
name = "cxxbridge-cmd"
-version = "0.3.4"
+version = "0.5.9"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
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"
+exclude = ["build.rs"]
keywords = ["ffi"]
categories = ["development-tools::ffi"]
@@ -14,11 +15,10 @@
path = "src/main.rs"
[dependencies]
-anyhow = "1.0"
+clap = "2.33"
codespan-reporting = "0.9"
proc-macro2 = { version = "1.0.17", default-features = false, features = ["span-locations"] }
quote = { version = "1.0", default-features = false }
-structopt = "0.3"
syn = { version = "1.0.20", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] }
[package.metadata.docs.rs]
diff --git a/gen/cmd/build.rs b/gen/cmd/build.rs
new file mode 100644
index 0000000..c53bef7
--- /dev/null
+++ b/gen/cmd/build.rs
@@ -0,0 +1 @@
+include!("../../tools/cargo/build.rs");
diff --git a/gen/cmd/src/app.rs b/gen/cmd/src/app.rs
new file mode 100644
index 0000000..e2945a1
--- /dev/null
+++ b/gen/cmd/src/app.rs
@@ -0,0 +1,171 @@
+#[cfg(test)]
+#[path = "test.rs"]
+mod test;
+
+use super::{Opt, Output};
+use crate::gen::include::Include;
+use crate::syntax::IncludeKind;
+use clap::AppSettings;
+use std::ffi::{OsStr, OsString};
+use std::path::PathBuf;
+
+type App = clap::App<'static, 'static>;
+type Arg = clap::Arg<'static, 'static>;
+
+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:
+{unified}\
+";
+
+fn app() -> App {
+ let mut app = App::new("cxxbridge")
+ .usage(USAGE)
+ .template(TEMPLATE)
+ .setting(AppSettings::NextLineHelp)
+ .arg(arg_input())
+ .arg(arg_cxx_impl_annotations())
+ .arg(arg_header())
+ .arg(arg_include())
+ .arg(arg_output())
+ .help_message("Print help information.")
+ .version_message("Print version information.");
+ if let Some(version) = option_env!("CARGO_PKG_VERSION") {
+ app = app.version(version);
+ }
+ 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,
+ cxx_impl_annotations,
+ header,
+ include,
+ outputs,
+ }
+}
+
+fn validate_utf8(arg: &OsStr) -> Result<(), OsString> {
+ if arg.to_str().is_some() {
+ Ok(())
+ } else {
+ Err(OsString::from("invalid utf-8 sequence"))
+ }
+}
+
+fn arg_input() -> Arg {
+ Arg::with_name(INPUT)
+ .help("Input Rust source file containing #[cxx::bridge].")
+ .required_unless(HEADER)
+}
+
+fn arg_cxx_impl_annotations() -> Arg {
+ 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::with_name(CXX_IMPL_ANNOTATIONS)
+ .long(CXX_IMPL_ANNOTATIONS)
+ .takes_value(true)
+ .value_name("annotation")
+ .validator_os(validate_utf8)
+ .help(HELP)
+}
+
+fn arg_header() -> Arg {
+ const HELP: &str = "\
+Emit header with declarations only. Optional if using `-o` with
+a path ending in `.h`.
+ ";
+ Arg::with_name(HEADER).long(HEADER).help(HELP)
+}
+
+fn arg_include() -> Arg {
+ 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::with_name(INCLUDE)
+ .long(INCLUDE)
+ .short("i")
+ .takes_value(true)
+ .multiple(true)
+ .number_of_values(1)
+ .validator_os(validate_utf8)
+ .help(HELP)
+}
+
+fn arg_output() -> Arg {
+ const HELP: &str = "\
+Path of file to write as output. Output goes to stdout if -o is
+not specified.
+ ";
+ Arg::with_name(OUTPUT)
+ .long(OUTPUT)
+ .short("o")
+ .takes_value(true)
+ .multiple(true)
+ .number_of_values(1)
+ .validator_os(validate_utf8)
+ .help(HELP)
+}
diff --git a/gen/cmd/src/main.rs b/gen/cmd/src/main.rs
index 701f77f..c723039 100644
--- a/gen/cmd/src/main.rs
+++ b/gen/cmd/src/main.rs
@@ -3,68 +3,89 @@
clippy::inherent_to_string,
clippy::large_enum_variant,
clippy::new_without_default,
+ clippy::or_fun_call,
clippy::toplevel_ref_arg
)]
+mod app;
mod gen;
+mod output;
mod syntax;
-use gen::include;
+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 structopt::StructOpt;
+use std::process;
-#[derive(StructOpt, Debug)]
-#[structopt(
- name = "cxxbridge",
- author = "David Tolnay <dtolnay@gmail.com>",
- about = "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",
- help_message = "Print help information",
- version_message = "Print version information"
-)]
+#[derive(Debug)]
struct Opt {
- /// Input Rust source file containing #[cxx::bridge]
- #[structopt(parse(from_os_str), required_unless = "header")]
input: Option<PathBuf>,
-
- /// Emit header with declarations only
- #[structopt(long)]
header: bool,
-
- /// 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.
- #[structopt(long)]
cxx_impl_annotations: Option<String>,
-
- /// Any additional headers to #include
- #[structopt(short, long)]
- include: Vec<String>,
-}
-
-fn write(content: impl AsRef<[u8]>) {
- let _ = io::stdout().lock().write_all(content.as_ref());
+ include: Vec<Include>,
+ outputs: Vec<Output>,
}
fn main() {
- let opt = Opt::from_args();
+ 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") {
+ 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()
};
- match (opt.input, opt.header) {
- (Some(input), true) => write(gen::do_generate_header(&input, gen)),
- (Some(input), false) => write(gen::do_generate_bridge(&input, gen)),
- (None, true) => write(include::HEADER),
- (None, false) => unreachable!(), // enforced by required_unless
+ 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/gen/cmd/src/output.rs b/gen/cmd/src/output.rs
new file mode 100644
index 0000000..a46581b
--- /dev/null
+++ b/gen/cmd/src/output.rs
@@ -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/gen/cmd/src/test.rs b/gen/cmd/src/test.rs
new file mode 100644
index 0000000..17023e4
--- /dev/null
+++ b/gen/cmd/src/test.rs
@@ -0,0 +1,52 @@
+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.
+ \x20
+ -h, --help
+ Print help information.
+
+ --header
+ Emit header with declarations only. Optional if using `-o` with
+ a path ending in `.h`.
+ \x20
+ -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.
+ \x20
+ -o, --output <output>...
+ Path of file to write as output. Output goes to stdout if -o is
+ not specified.
+ \x20
+ -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);
+}
diff --git a/gen/lib/Cargo.toml b/gen/lib/Cargo.toml
new file mode 100644
index 0000000..2de0f28
--- /dev/null
+++ b/gen/lib/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "cxx-gen"
+version = "0.6.6"
+authors = ["Adrian Taylor <adetaylor@chromium.org>"]
+edition = "2018"
+license = "MIT OR Apache-2.0"
+description = "C++ code generator for integrating `cxx` crate into higher level tools."
+repository = "https://github.com/dtolnay/cxx"
+exclude = ["build.rs"]
+keywords = ["ffi"]
+categories = ["development-tools::ffi"]
+
+[dependencies]
+cc = "1.0.49"
+codespan-reporting = "0.9"
+proc-macro2 = { version = "1.0.17", default-features = false, features = ["span-locations"] }
+quote = { version = "1.0", default-features = false }
+syn = { version = "1.0.20", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] }
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
diff --git a/gen/lib/LICENSE-APACHE b/gen/lib/LICENSE-APACHE
new file mode 120000
index 0000000..1cd601d
--- /dev/null
+++ b/gen/lib/LICENSE-APACHE
@@ -0,0 +1 @@
+../../LICENSE-APACHE
\ No newline at end of file
diff --git a/gen/lib/LICENSE-MIT b/gen/lib/LICENSE-MIT
new file mode 120000
index 0000000..b2cfbdc
--- /dev/null
+++ b/gen/lib/LICENSE-MIT
@@ -0,0 +1 @@
+../../LICENSE-MIT
\ No newline at end of file
diff --git a/gen/lib/build.rs b/gen/lib/build.rs
new file mode 100644
index 0000000..c53bef7
--- /dev/null
+++ b/gen/lib/build.rs
@@ -0,0 +1 @@
+include!("../../tools/cargo/build.rs");
diff --git a/gen/lib/src/error.rs b/gen/lib/src/error.rs
new file mode 100644
index 0000000..26249be
--- /dev/null
+++ b/gen/lib/src/error.rs
@@ -0,0 +1,33 @@
+// We can expose more detail on the error as the need arises, but start with an
+// opaque error type for now.
+
+use std::error::Error as StdError;
+use std::fmt::{self, Debug, Display};
+
+pub struct Error {
+ pub(crate) err: crate::gen::Error,
+}
+
+impl From<crate::gen::Error> for Error {
+ fn from(err: crate::gen::Error) -> Self {
+ Error { err }
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Display::fmt(&self.err, f)
+ }
+}
+
+impl Debug for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Debug::fmt(&self.err, f)
+ }
+}
+
+impl StdError for Error {
+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
+ self.err.source()
+ }
+}
diff --git a/gen/lib/src/gen b/gen/lib/src/gen
new file mode 120000
index 0000000..929cb3d
--- /dev/null
+++ b/gen/lib/src/gen
@@ -0,0 +1 @@
+../../src
\ No newline at end of file
diff --git a/gen/lib/src/lib.rs b/gen/lib/src/lib.rs
new file mode 100644
index 0000000..963e870
--- /dev/null
+++ b/gen/lib/src/lib.rs
@@ -0,0 +1,35 @@
+//! The CXX code generator for constructing and compiling C++ code.
+//!
+//! This is intended as a mechanism for embedding the `cxx` crate into
+//! higher-level code generators. See [dtolnay/cxx#235] and
+//! [https://github.com/google/autocxx].
+//!
+//! [dtolnay/cxx#235]: https://github.com/dtolnay/cxx/issues/235
+//! [https://github.com/google/autocxx]: https://github.com/google/autocxx
+
+#![allow(dead_code)]
+#![allow(
+ clippy::inherent_to_string,
+ clippy::new_without_default,
+ clippy::or_fun_call,
+ clippy::toplevel_ref_arg
+)]
+
+mod error;
+mod gen;
+mod syntax;
+
+pub use crate::error::Error;
+pub use crate::gen::include::{Include, HEADER};
+pub use crate::gen::{GeneratedCode, Opt};
+pub use crate::syntax::IncludeKind;
+use proc_macro2::TokenStream;
+
+/// Generate C++ bindings code from a Rust token stream. This should be a Rust
+/// token stream which somewhere contains a `#[cxx::bridge] mod {}`.
+pub fn generate_header_and_cc(rust_source: TokenStream, opt: &Opt) -> Result<GeneratedCode, Error> {
+ let syntax = syn::parse2(rust_source)
+ .map_err(crate::gen::Error::from)
+ .map_err(Error::from)?;
+ gen::generate(syntax, opt).map_err(Error::from)
+}
diff --git a/gen/lib/src/syntax b/gen/lib/src/syntax
new file mode 120000
index 0000000..a6fe06c
--- /dev/null
+++ b/gen/lib/src/syntax
@@ -0,0 +1 @@
+../../../syntax
\ No newline at end of file
diff --git a/gen/lib/tests/test.rs b/gen/lib/tests/test.rs
new file mode 100644
index 0000000..73a25f3
--- /dev/null
+++ b/gen/lib/tests/test.rs
@@ -0,0 +1,28 @@
+use cxx_gen::Opt;
+use quote::quote;
+
+#[test]
+fn test_positive() {
+ let rs = quote! {
+ #[cxx::bridge]
+ mod ffi {
+ extern "C" {
+ fn in_C();
+ }
+ extern "Rust" {
+ fn in_rs();
+ }
+ }
+ };
+ let opt = Opt::default();
+ let code = cxx_gen::generate_header_and_cc(rs, &opt).unwrap();
+ assert!(!code.header.is_empty());
+ assert!(!code.implementation.is_empty());
+}
+
+#[test]
+fn test_negative() {
+ let rs = quote! {};
+ let opt = Opt::default();
+ assert!(cxx_gen::generate_header_and_cc(rs, &opt).is_err())
+}
diff --git a/gen/src/block.rs b/gen/src/block.rs
new file mode 100644
index 0000000..96a9a6e
--- /dev/null
+++ b/gen/src/block.rs
@@ -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/gen/src/builtin.rs b/gen/src/builtin.rs
new file mode 100644
index 0000000..41d037a
--- /dev/null
+++ b/gen/src/builtin.rs
@@ -0,0 +1,259 @@
+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 unsafe_bitcopy: bool,
+ pub rust_error: bool,
+ pub manually_drop: bool,
+ pub maybe_uninit: bool,
+ pub trycatch: bool,
+ pub ptr_len: 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 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.cstdint = true;
+ include.string = true;
+ builtin.friend_impl = true;
+ }
+
+ if builtin.rust_slice {
+ builtin.friend_impl = true;
+ }
+
+ if builtin.rust_box {
+ include.new = true;
+ include.type_traits = true;
+ include.utility = true;
+ }
+
+ if builtin.rust_vec {
+ include.array = true;
+ include.new = true;
+ include.type_traits = true;
+ include.utility = true;
+ builtin.panic = true;
+ builtin.unsafe_bitcopy = 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;
+ }
+
+ if builtin.relocatable {
+ include.type_traits = true;
+ }
+
+ out.begin_block(Block::Namespace("rust"));
+ out.begin_block(Block::InlineNamespace("cxxbridge05"));
+ writeln!(out, "// #include \"rust/cxx.h\"");
+
+ ifndef::write(out, builtin.panic, "CXXBRIDGE05_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);
+ }
+
+ ifndef::write(out, builtin.rust_string, "CXXBRIDGE05_RUST_STRING");
+ ifndef::write(out, builtin.rust_str, "CXXBRIDGE05_RUST_STR");
+ ifndef::write(out, builtin.rust_slice, "CXXBRIDGE05_RUST_SLICE");
+ ifndef::write(out, builtin.rust_box, "CXXBRIDGE05_RUST_BOX");
+ ifndef::write(out, builtin.unsafe_bitcopy, "CXXBRIDGE05_RUST_BITCOPY");
+ ifndef::write(out, builtin.rust_vec, "CXXBRIDGE05_RUST_VEC");
+ ifndef::write(out, builtin.rust_fn, "CXXBRIDGE05_RUST_FN");
+ ifndef::write(out, builtin.rust_error, "CXXBRIDGE05_RUST_ERROR");
+ ifndef::write(out, builtin.rust_isize, "CXXBRIDGE05_RUST_ISIZE");
+ ifndef::write(out, builtin.relocatable, "CXXBRIDGE05_RELOCATABLE");
+
+ 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 {
+ out.next_section();
+ writeln!(out, "template <typename T>");
+ writeln!(out, "union MaybeUninit {{");
+ writeln!(out, " T value;");
+ writeln!(out, " MaybeUninit() {{}}");
+ writeln!(out, " ~MaybeUninit() {{}}");
+ writeln!(out, "}};");
+ }
+
+ out.begin_block(Block::AnonymousNamespace);
+
+ if builtin.ptr_len {
+ out.begin_block(Block::Namespace("repr"));
+ writeln!(out, "struct PtrLen final {{");
+ writeln!(out, " const void *ptr;");
+ writeln!(out, " 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::PtrLen repr) noexcept {{",
+ );
+ writeln!(out, " Str str;");
+ writeln!(out, " str.ptr = static_cast<const char *>(repr.ptr);");
+ writeln!(out, " str.len = repr.len;");
+ writeln!(out, " return str;");
+ writeln!(out, " }}");
+ }
+ if builtin.rust_str_repr {
+ writeln!(out, " static repr::PtrLen repr(Str str) noexcept {{");
+ writeln!(out, " return repr::PtrLen{{str.ptr, str.len}};");
+ 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::PtrLen repr) noexcept {{",
+ );
+ writeln!(
+ out,
+ " return {{static_cast<const T *>(repr.ptr), repr.len}};",
+ );
+ writeln!(out, " }}");
+ }
+ if builtin.rust_slice_repr {
+ writeln!(
+ out,
+ " static repr::PtrLen repr(Slice<T> slice) noexcept {{",
+ );
+ writeln!(out, " return repr::PtrLen{{slice.ptr, slice.len}};");
+ 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, "}};");
+ }
+
+ out.end_block(Block::AnonymousNamespace);
+ out.end_block(Block::InlineNamespace("cxxbridge05"));
+
+ 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 {
+ out.begin_block(Block::ExternC);
+ writeln!(
+ out,
+ "const char *cxxbridge05$exception(const char *, size_t);",
+ );
+ out.end_block(Block::ExternC);
+ }
+}
diff --git a/gen/src/check.rs b/gen/src/check.rs
new file mode 100644
index 0000000..35929ad
--- /dev/null
+++ b/gen/src/check.rs
@@ -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;
+
+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/gen/src/error.rs b/gen/src/error.rs
index 51dbbe7..2335d43 100644
--- a/gen/src/error.rs
+++ b/gen/src/error.rs
@@ -1,23 +1,25 @@
+use crate::gen::fs;
use crate::syntax;
-use anyhow::anyhow;
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;
+use std::path::{Path, PathBuf};
use std::process;
+use std::str::Utf8Error;
-pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
+pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug)]
-pub(super) enum Error {
+pub(crate) enum Error {
NoBridgeMod,
- OutOfLineMod,
- Io(io::Error),
+ Fs(fs::Error),
+ Utf8(PathBuf, Utf8Error),
Syn(syn::Error),
}
@@ -25,8 +27,8 @@
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::NoBridgeMod => write!(f, "no #[cxx::bridge] module found"),
- Error::OutOfLineMod => write!(f, "#[cxx::bridge] module must have inline contents"),
- Error::Io(err) => err.fmt(f),
+ Error::Fs(err) => err.fmt(f),
+ Error::Utf8(path, _) => write!(f, "Failed to read file `{}`", path.display()),
Error::Syn(err) => err.fmt(f),
}
}
@@ -35,16 +37,17 @@
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
- Error::Io(err) => Some(err),
- Error::Syn(err) => Some(err),
+ Error::Fs(err) => err.source(),
+ Error::Utf8(_, err) => Some(err),
+ Error::Syn(err) => err.source(),
_ => None,
}
}
}
-impl From<io::Error> for Error {
- fn from(err: io::Error) -> Self {
- Error::Io(err)
+impl From<fs::Error> for Error {
+ fn from(err: fs::Error) -> Self {
+ Error::Fs(err)
}
}
@@ -65,11 +68,34 @@
display_syn_error(stderr, path, source, error);
}
}
- _ => eprintln!("cxxbridge: {:?}", anyhow!(error)),
+ _ => {
+ 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 {
+ Display::fmt(&self.0, formatter)?;
+ let mut error: &dyn StdError = &self.0;
+
+ while let Some(cause) = error.source() {
+ formatter.write_str("\n\nCaused by:\n ")?;
+ Display::fmt(cause, formatter)?;
+ 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| {
@@ -109,8 +135,13 @@
.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.to_string_lossy(), source);
+ let file = files.add(path, source);
let diagnostic = diagnose(file, start_offset..end_offset, error);
diff --git a/gen/src/file.rs b/gen/src/file.rs
new file mode 100644
index 0000000..46616fb
--- /dev/null
+++ b/gen/src/file.rs
@@ -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/gen/src/find.rs b/gen/src/find.rs
deleted file mode 100644
index 86e1dc7..0000000
--- a/gen/src/find.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-use crate::gen::{Error, Input, Result};
-use crate::syntax::namespace::Namespace;
-use quote::quote;
-use syn::{Attribute, File, Item};
-
-pub(super) fn find_bridge_mod(syntax: File) -> Result<Input> {
- match scan(syntax.items)? {
- Some(input) => Ok(input),
- None => Err(Error::NoBridgeMod),
- }
-}
-
-fn scan(items: Vec<Item>) -> Result<Option<Input>> {
- for item in items {
- if let Item::Mod(item) = item {
- for attr in &item.attrs {
- let path = &attr.path;
- if quote!(#path).to_string() == "cxx :: bridge" {
- let module = match item.content {
- Some(module) => module.1,
- None => {
- return Err(Error::Syn(syn::Error::new_spanned(
- item,
- Error::OutOfLineMod,
- )));
- }
- };
- let namespace = parse_args(attr)?;
- return Ok(Some(Input { namespace, module }));
- }
- }
- if let Some(module) = item.content {
- if let Some(input) = scan(module.1)? {
- return Ok(Some(input));
- }
- }
- }
- }
- Ok(None)
-}
-
-fn parse_args(attr: &Attribute) -> syn::Result<Namespace> {
- if attr.tokens.is_empty() {
- Ok(Namespace::none())
- } else {
- attr.parse_args()
- }
-}
diff --git a/gen/src/fs.rs b/gen/src/fs.rs
new file mode 100644
index 0000000..8f94f00
--- /dev/null
+++ b/gen/src/fs.rs
@@ -0,0 +1,142 @@
+#![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: io::Error,
+ message: String,
+}
+
+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)> {
+ Some(&self.source)
+ }
+}
+
+macro_rules! err {
+ ($io_error:expr, $fmt:expr $(, $path:expr)* $(,)?) => {
+ Err(Error {
+ source: $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>(
+ src: &'a Path,
+ dst: &'a Path,
+ fun: fn(&'a Path, &'a Path) -> io::Result<()>,
+) -> Result<()> {
+ match fun(src, dst) {
+ Ok(()) => Ok(()),
+ Err(e) => err!(
+ e,
+ "Failed to create symlink `{}` pointing to `{}`",
+ dst,
+ src,
+ ),
+ }
+}
+
+#[cfg(unix)]
+#[allow(unused_imports)]
+pub(crate) use self::symlink_file as symlink_dir;
+
+#[cfg(unix)]
+pub(crate) fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
+ symlink(src.as_ref(), dst.as_ref(), std::os::unix::fs::symlink)
+}
+
+#[cfg(windows)]
+pub(crate) fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
+ symlink(
+ src.as_ref(),
+ dst.as_ref(),
+ std::os::windows::fs::symlink_file,
+ )
+}
+
+#[cfg(windows)]
+pub(crate) fn symlink_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
+ symlink(
+ src.as_ref(),
+ dst.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/gen/src/ifndef.rs b/gen/src/ifndef.rs
new file mode 100644
index 0000000..b436266
--- /dev/null
+++ b/gen/src/ifndef.rs
@@ -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/gen/src/include.rs b/gen/src/include.rs
index 4309c9c..cf718da 100644
--- a/gen/src/include.rs
+++ b/gen/src/include.rs
@@ -1,55 +1,26 @@
-use crate::gen::out::OutFile;
-use std::fmt::{self, Display};
+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");
-pub(super) fn write(out: &mut OutFile, 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();
- }
+/// 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 {
- custom: Vec<String>,
+pub struct Includes<'a> {
+ pub custom: Vec<Include>,
pub array: bool,
pub cstddef: bool,
pub cstdint: bool,
@@ -61,75 +32,105 @@
pub type_traits: bool,
pub utility: bool,
pub vector: bool,
- pub base_tsd: bool,
+ pub basetsd: bool,
+ pub content: Content<'a>,
}
-impl Includes {
+impl<'a> Includes<'a> {
pub fn new() -> Self {
Includes::default()
}
- pub fn insert(&mut self, include: impl AsRef<str>) {
- self.custom.push(include.as_ref().to_owned());
+ pub fn insert(&mut self, include: impl Into<Include>) {
+ self.custom.push(include.into());
}
}
-impl Extend<String> for Includes {
- fn extend<I: IntoIterator<Item = String>>(&mut self, iter: I) {
- self.custom.extend(iter);
- }
-}
+pub(super) fn write(out: &mut OutFile) {
+ let header = out.header;
+ let include = &mut out.include;
+ let out = &mut include.content;
-impl Display for Includes {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- for include in &self.custom {
- if include.starts_with('<') && include.ends_with('>') {
- writeln!(f, "#include {}", include)?;
- } else {
- writeln!(f, "#include \"{}\"", include.escape_default())?;
+ 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);
}
}
- if self.array {
- writeln!(f, "#include <array>")?;
+ }
+
+ if include.array {
+ writeln!(out, "#include <array>");
+ }
+ if include.cstddef {
+ writeln!(out, "#include <cstddef>");
+ }
+ if include.cstdint {
+ writeln!(out, "#include <cstdint>");
+ }
+ if include.cstring {
+ writeln!(out, "#include <cstring>");
+ }
+ if include.exception {
+ writeln!(out, "#include <exception>");
+ }
+ if include.memory {
+ writeln!(out, "#include <memory>");
+ }
+ if include.new {
+ writeln!(out, "#include <new>");
+ }
+ if include.string {
+ writeln!(out, "#include <string>");
+ }
+ if include.type_traits {
+ writeln!(out, "#include <type_traits>");
+ }
+ if include.utility {
+ writeln!(out, "#include <utility>");
+ }
+ if include.vector {
+ writeln!(out, "#include <vector>");
+ }
+ if include.basetsd {
+ writeln!(out, "#if defined(_WIN32)");
+ writeln!(out, "#include <basetsd.h>");
+ 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,
}
- if self.cstddef {
- writeln!(f, "#include <cstddef>")?;
- }
- if self.cstdint {
- writeln!(f, "#include <cstdint>")?;
- }
- if self.cstring {
- writeln!(f, "#include <cstring>")?;
- }
- if self.exception {
- writeln!(f, "#include <exception>")?;
- }
- if self.memory {
- writeln!(f, "#include <memory>")?;
- }
- if self.new {
- writeln!(f, "#include <new>")?;
- }
- if self.string {
- writeln!(f, "#include <string>")?;
- }
- if self.type_traits {
- writeln!(f, "#include <type_traits>")?;
- }
- if self.utility {
- writeln!(f, "#include <utility>")?;
- }
- if self.vector {
- writeln!(f, "#include <vector>")?;
- }
- if self.base_tsd {
- writeln!(f, "#if defined(_WIN32)")?;
- writeln!(f, "#include <BaseTsd.h>")?;
- writeln!(f, "#endif")?;
- }
- if *self != Self::default() {
- writeln!(f)?;
- }
- Ok(())
+ }
+}
+
+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/gen/src/mod.rs b/gen/src/mod.rs
index 710bca2..3d12c71 100644
--- a/gen/src/mod.rs
+++ b/gen/src/mod.rs
@@ -1,69 +1,150 @@
// Functionality that is shared between the cxx_build::bridge entry point and
// the cxxbridge CLI command.
-mod error;
-mod find;
+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;
-#[cfg(test)]
-mod tests;
-
-use self::error::{format_err, Error, Result};
-use crate::syntax::namespace::Namespace;
+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, check, Types};
-use std::fs;
+use crate::syntax::{self, Types};
use std::path::Path;
-use syn::Item;
-struct Input {
- namespace: Namespace,
- module: Vec<Item>,
-}
-
-#[derive(Default)]
-pub(super) struct Opt {
- /// Any additional headers to #include
- pub include: Vec<String>,
- /// Whether to set __attribute__((visibility("default")))
- /// or similar annotations on function implementations.
+/// 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,
}
-pub(super) fn do_generate_bridge(path: &Path, opt: Opt) -> Vec<u8> {
- let header = false;
- generate_from_path(path, opt, header)
+/// 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>,
}
-pub(super) fn do_generate_header(path: &Path, opt: Opt) -> Vec<u8> {
- let header = true;
- generate_from_path(path, opt, header)
+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,
+ }
+ }
}
-fn generate_from_path(path: &Path, opt: Opt, header: bool) -> Vec<u8> {
- let source = match fs::read_to_string(path) {
+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, "", Error::Io(err)),
+ Err(err) => format_err(path, "", err),
};
- match generate(&source, opt, header) {
+ match generate_from_string(&source, opt) {
Ok(out) => out,
Err(err) => format_err(path, &source, err),
}
}
-fn generate(source: &str, opt: Opt, header: bool) -> Result<Vec<u8>> {
+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();
- let syntax = syn::parse_file(&source)?;
- let bridge = find::find_bridge_mod(syntax)?;
- let ref namespace = bridge.namespace;
- let ref apis = syntax::parse_items(errors, bridge.module);
+ 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()?;
- check::typecheck(errors, namespace, apis, types);
+ check::typecheck(errors, apis, types);
errors.propagate()?;
- let out = write::gen(namespace, apis, types, opt, header);
- Ok(out.content())
+
+ // 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/gen/src/namespace.rs b/gen/src/namespace.rs
new file mode 100644
index 0000000..b79c38f
--- /dev/null
+++ b/gen/src/namespace.rs
@@ -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/gen/src/nested.rs b/gen/src/nested.rs
new file mode 100644
index 0000000..22b0c9f
--- /dev/null
+++ b/gen/src/nested.rs
@@ -0,0 +1,136 @@
+use crate::syntax::Api;
+use proc_macro2::Ident;
+use std::collections::HashMap as Map;
+
+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::namespace::Namespace;
+ use crate::syntax::{Api, Doc, ExternType, Pair};
+ use proc_macro2::{Ident, Span};
+ use std::iter::FromIterator;
+ 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, 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 {
+ doc: Doc::new(),
+ type_token: Token![type](Span::call_site()),
+ name: Pair::new(ns, Ident::new(ident, Span::call_site())),
+ semi_token: Token![;](Span::call_site()),
+ trusted: false,
+ })
+ }
+}
diff --git a/gen/src/out.rs b/gen/src/out.rs
index 08bf85f..bf880cc 100644
--- a/gen/src/out.rs
+++ b/gen/src/out.rs
@@ -1,59 +1,63 @@
+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 {
- pub namespace: Namespace,
+pub(crate) struct OutFile<'a> {
pub header: bool,
- pub include: Includes,
- content: RefCell<Content>,
+ pub opt: &'a Opt,
+ pub types: &'a Types<'a>,
+ pub include: Includes<'a>,
+ pub builtin: Builtins<'a>,
+ content: RefCell<Content<'a>>,
}
-struct Content {
- bytes: Vec<u8>,
+#[derive(Default)]
+pub struct Content<'a> {
+ bytes: String,
+ namespace: &'a Namespace,
+ blocks: Vec<BlockBoundary<'a>>,
section_pending: bool,
- blocks_pending: Vec<&'static str>,
+ blocks_pending: usize,
}
-impl OutFile {
- pub fn new(namespace: Namespace, header: bool) -> Self {
+#[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 {
- namespace,
header,
+ opt,
+ types,
include: Includes::new(),
- content: RefCell::new(Content {
- bytes: Vec::new(),
- section_pending: false,
- blocks_pending: Vec::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) {
- let content = self.content.get_mut();
- content.section_pending = true;
+ self.content.get_mut().next_section();
}
- pub fn begin_block(&mut self, block: &'static str) {
- let content = self.content.get_mut();
- content.blocks_pending.push(block);
+ pub fn begin_block(&mut self, block: Block<'a>) {
+ self.content.get_mut().begin_block(block);
}
- pub fn end_block(&mut self, block: &'static str) {
- let content = self.content.get_mut();
- if content.blocks_pending.pop().is_none() {
- content.bytes.extend_from_slice(b"} // ");
- content.bytes.extend_from_slice(block.as_bytes());
- content.bytes.push(b'\n');
- content.section_pending = true;
- }
+ pub fn end_block(&mut self, block: Block<'a>) {
+ self.content.get_mut().end_block(block);
}
- pub fn prepend(&mut self, section: String) {
- let content = self.content.get_mut();
- content.bytes.splice(..0, section.into_bytes());
+ pub fn set_namespace(&mut self, namespace: &'a Namespace) {
+ self.content.get_mut().set_namespace(namespace);
}
pub fn write_fmt(&self, args: Arguments) {
@@ -61,31 +65,146 @@
Write::write_fmt(content, args).unwrap();
}
- pub fn content(&self) -> Vec<u8> {
- self.content.borrow().bytes.clone()
+ 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 Write for Content {
+impl<'a> Write for Content<'a> {
fn write_str(&mut self, s: &str) -> fmt::Result {
- if !s.is_empty() {
- if !self.blocks_pending.is_empty() {
- if !self.bytes.is_empty() {
- self.bytes.push(b'\n');
- }
- for block in self.blocks_pending.drain(..) {
- self.bytes.extend_from_slice(block.as_bytes());
- self.bytes.extend_from_slice(b" {\n");
- }
- self.section_pending = false;
- } else if self.section_pending {
- if !self.bytes.is_empty() {
- self.bytes.push(b'\n');
- }
- self.section_pending = false;
- }
- self.bytes.extend_from_slice(s.as_bytes());
- }
+ self.write(s);
Ok(())
}
}
+
+impl<'a> PartialEq for Content<'a> {
+ fn eq(&self, _other: &Content) -> 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/gen/src/tests.rs b/gen/src/tests.rs
deleted file mode 100644
index 0e7a910..0000000
--- a/gen/src/tests.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use crate::gen::{generate, Opt};
-
-const CPP_EXAMPLE: &'static str = r#"
- #[cxx::bridge]
- mod ffi {
- extern "C" {
- pub fn do_cpp_thing(foo: &str);
- }
- }
- "#;
-
-#[test]
-fn test_cpp() {
- let opts = Opt {
- include: Vec::new(),
- cxx_impl_annotations: None,
- };
- let output = generate(CPP_EXAMPLE, opts, false).unwrap();
- let output = std::str::from_utf8(&output).unwrap();
- // To avoid continual breakage we won't test every byte.
- // Let's look for the major features.
- assert!(output.contains("void cxxbridge03$do_cpp_thing(::rust::Str::Repr foo)"));
-}
-
-#[test]
-fn test_annotation() {
- let opts = Opt {
- include: Vec::new(),
- cxx_impl_annotations: Some("ANNOTATION".to_string()),
- };
- let output = generate(CPP_EXAMPLE, opts, false).unwrap();
- let output = std::str::from_utf8(&output).unwrap();
- assert!(output.contains("ANNOTATION void cxxbridge03$do_cpp_thing(::rust::Str::Repr foo)"));
-}
diff --git a/gen/src/write.rs b/gen/src/write.rs
index 04d8e3e..5fe8ee3 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -1,79 +1,111 @@
+use crate::gen::block::Block;
+use crate::gen::nested::NamespaceEntries;
use crate::gen::out::OutFile;
-use crate::gen::{include, Opt};
+use crate::gen::{builtin, include, Opt};
use crate::syntax::atom::Atom::{self, *};
-use crate::syntax::namespace::Namespace;
use crate::syntax::symbol::Symbol;
-use crate::syntax::{mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, Types, Var};
+use crate::syntax::{
+ mangle, Api, Enum, ExternFn, ExternType, Pair, ResolvableName, Signature, Struct, Type, Types,
+ Var,
+};
use proc_macro2::Ident;
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
-pub(super) fn gen(
- namespace: &Namespace,
- apis: &[Api],
- types: &Types,
- opt: Opt,
- header: bool,
-) -> OutFile {
- let mut out_file = OutFile::new(namespace.clone(), header);
+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;
- if header {
- writeln!(out, "#pragma once");
- }
+ pick_includes_and_builtins(out, apis);
+ out.include.extend(&opt.include);
- out.include.extend(opt.include);
- for api in apis {
- if let Api::Include(include) = api {
- out.include.insert(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.cxx),
+ 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.cxx),
+ _ => 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);
}
}
+}
- write_includes(out, types);
- write_include_cxxbridge(out, apis, types);
-
- out.next_section();
- for name in namespace {
- writeln!(out, "namespace {} {{", name);
- }
-
- out.next_section();
- for api in apis {
- match api {
- Api::Struct(strct) => write_struct_decl(out, &strct.ident),
- Api::CxxType(ety) => write_struct_using(out, &ety.ident),
- Api::RustType(ety) => write_struct_decl(out, &ety.ident),
- _ => {}
- }
- }
-
+fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
let mut methods_for_type = HashMap::new();
for api in apis {
- if let Api::RustFunction(efn) = api {
+ if let Api::CxxFunction(efn) | Api::RustFunction(efn) = api {
if let Some(receiver) = &efn.sig.receiver {
methods_for_type
- .entry(&receiver.ty)
+ .entry(&receiver.ty.rust)
.or_insert_with(Vec::new)
.push(efn);
}
}
}
+ let mut structs_written = HashSet::new();
+ let mut toposorted_structs = out.types.toposorted_structs.iter();
for api in apis {
match api {
- Api::Struct(strct) => {
- out.next_section();
- write_struct(out, strct);
+ 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 types.cxx.contains(&enm.ident) {
+ if out.types.cxx.contains(&enm.name.rust) {
check_enum(out, enm);
} else {
write_enum(out, enm);
}
}
Api::RustType(ety) => {
- if let Some(methods) = methods_for_type.get(&ety.ident) {
+ if let Some(methods) = methods_for_type.get(&ety.name.rust) {
out.next_section();
write_struct_with_methods(out, ety, methods);
}
@@ -82,290 +114,162 @@
}
}
- if !header {
- out.begin_block("extern \"C\"");
- write_exception_glue(out, apis);
- for api in apis {
- let (efn, write): (_, fn(_, _, _, _)) = match api {
- Api::CxxFunction(efn) => (efn, write_cxx_function_shim),
- Api::RustFunction(efn) => (efn, write_rust_function_decl),
- _ => continue,
- };
- out.next_section();
- write(out, efn, types, &opt.cxx_impl_annotations);
+ out.next_section();
+ for api in apis {
+ if let Api::TypeAlias(ety) = api {
+ if out.types.required_trivial.contains_key(&ety.name.rust) {
+ check_trivial_extern_type(out, &ety.name)
+ }
}
- out.end_block("extern \"C\"");
+ }
+}
+
+fn write_functions<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
+ if !out.header {
+ for api in apis {
+ match api {
+ Api::CxxFunction(efn) => write_cxx_function_shim(out, efn),
+ Api::RustFunction(efn) => write_rust_function_decl(out, efn),
+ _ => {}
+ }
+ }
}
for api in apis {
if let Api::RustFunction(efn) = api {
out.next_section();
- write_rust_function_shim(out, efn, types);
+ write_rust_function_shim(out, efn);
+ }
+ }
+}
+
+fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) {
+ for api in apis {
+ if let Api::Include(include) = api {
+ out.include.insert(include);
}
}
- out.next_section();
- for name in namespace.iter().rev() {
- writeln!(out, "}} // namespace {}", name);
- }
-
- if !header {
- out.next_section();
- write_generic_instantiations(out, types);
- }
-
- out.prepend(out.include.to_string());
-
- out_file
-}
-
-fn write_includes(out: &mut OutFile, types: &Types) {
- for ty in types {
+ for ty in out.types {
match ty {
- Type::Ident(ident) => match Atom::from(ident) {
+ 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(Bool) | Some(Isize) | Some(F32) | Some(F64) | Some(RustString) | None => {}
+ Some(RustString) => out.builtin.rust_string = true,
+ Some(Bool) | Some(F32) | Some(F64) | None => {}
},
- Type::RustBox(_) => out.include.type_traits = true,
+ Type::RustBox(_) => out.builtin.rust_box = true,
+ Type::RustVec(_) => out.builtin.rust_vec = true,
Type::UniquePtr(_) => out.include.memory = true,
+ Type::Str(_) => out.builtin.rust_str = true,
Type::CxxVector(_) => out.include.vector = true,
- Type::SliceRefU8(_) => out.include.cstdint = true,
- _ => {}
+ Type::Fn(_) => out.builtin.rust_fn = true,
+ Type::Slice(_) => out.builtin.rust_slice = true,
+ Type::SliceRefU8(_) => {
+ out.include.cstdint = true;
+ out.builtin.rust_slice = true;
+ }
+ Type::Ref(_) | Type::Void(_) => {}
}
}
}
-fn write_include_cxxbridge(out: &mut OutFile, apis: &[Api], types: &Types) {
- let mut needs_rust_string = false;
- let mut needs_rust_str = false;
- let mut needs_rust_slice = false;
- let mut needs_rust_box = false;
- let mut needs_rust_vec = false;
- let mut needs_rust_fn = false;
- let mut needs_rust_isize = false;
- for ty in types {
- match ty {
- Type::RustBox(_) => {
- out.include.new = true;
- out.include.type_traits = true;
- needs_rust_box = true;
- }
- Type::RustVec(_) => {
- out.include.array = true;
- out.include.new = true;
- out.include.type_traits = true;
- needs_rust_vec = true;
- }
- Type::Str(_) => {
- out.include.cstdint = true;
- out.include.string = true;
- needs_rust_str = true;
- }
- Type::Fn(_) => {
- needs_rust_fn = true;
- }
- Type::Slice(_) | Type::SliceRefU8(_) => {
- needs_rust_slice = true;
- }
- ty if ty == Isize => {
- out.include.base_tsd = true;
- needs_rust_isize = true;
- }
- ty if ty == RustString => {
- out.include.array = true;
- out.include.cstdint = true;
- out.include.string = true;
- needs_rust_string = true;
- }
- _ => {}
- }
- }
-
- let mut needs_rust_error = false;
- let mut needs_unsafe_bitcopy = false;
- let mut needs_manually_drop = false;
- let mut needs_maybe_uninit = false;
- let mut needs_trycatch = false;
- for api in apis {
- match api {
- Api::CxxFunction(efn) if !out.header => {
- if efn.throws {
- needs_trycatch = true;
- }
- for arg in &efn.args {
- let bitcopy = match arg.ty {
- Type::RustVec(_) => true,
- _ => arg.ty == RustString,
- };
- if bitcopy {
- needs_unsafe_bitcopy = true;
- break;
- }
- }
- }
- Api::RustFunction(efn) if !out.header => {
- if efn.throws {
- out.include.exception = true;
- needs_rust_error = true;
- }
- for arg in &efn.args {
- if arg.ty != RustString && types.needs_indirect_abi(&arg.ty) {
- needs_manually_drop = true;
- break;
- }
- }
- if let Some(ret) = &efn.ret {
- if types.needs_indirect_abi(ret) {
- needs_maybe_uninit = true;
- }
- }
- }
- _ => {}
- }
- }
-
- out.begin_block("namespace rust");
- out.begin_block("inline namespace cxxbridge03");
-
- if needs_rust_string
- || needs_rust_str
- || needs_rust_slice
- || needs_rust_box
- || needs_rust_vec
- || needs_rust_fn
- || needs_rust_error
- || needs_rust_isize
- || needs_unsafe_bitcopy
- || needs_manually_drop
- || needs_maybe_uninit
- || needs_trycatch
- {
- writeln!(out, "// #include \"rust/cxx.h\"");
- }
-
- if needs_rust_string || needs_rust_vec {
- out.next_section();
- writeln!(out, "struct unsafe_bitcopy_t;");
- }
-
- include::write(out, needs_rust_string, "CXXBRIDGE03_RUST_STRING");
- include::write(out, needs_rust_str, "CXXBRIDGE03_RUST_STR");
- include::write(out, needs_rust_slice, "CXXBRIDGE03_RUST_SLICE");
- include::write(out, needs_rust_box, "CXXBRIDGE03_RUST_BOX");
- include::write(out, needs_rust_vec, "CXXBRIDGE03_RUST_VEC");
- include::write(out, needs_rust_fn, "CXXBRIDGE03_RUST_FN");
- include::write(out, needs_rust_error, "CXXBRIDGE03_RUST_ERROR");
- include::write(out, needs_rust_isize, "CXXBRIDGE03_RUST_ISIZE");
- include::write(out, needs_unsafe_bitcopy, "CXXBRIDGE03_RUST_BITCOPY");
-
- if needs_manually_drop {
- out.next_section();
- out.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 needs_maybe_uninit {
- out.next_section();
- writeln!(out, "template <typename T>");
- writeln!(out, "union MaybeUninit {{");
- writeln!(out, " T value;");
- writeln!(out, " MaybeUninit() {{}}");
- writeln!(out, " ~MaybeUninit() {{}}");
- writeln!(out, "}};");
- }
-
- out.end_block("namespace cxxbridge03");
-
- if needs_trycatch {
- out.begin_block("namespace behavior");
- out.include.exception = true;
- out.include.type_traits = true;
- out.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("namespace behavior");
- }
-
- out.end_block("namespace rust");
-}
-
-fn write_struct(out: &mut OutFile, strct: &Struct) {
+fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&ExternFn]) {
+ out.set_namespace(&strct.name.namespace);
+ let guard = format!("CXXBRIDGE05_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.ident);
+ writeln!(out, "struct {} final {{", strct.name.cxx);
for field in &strct.fields {
write!(out, " ");
write_type_space(out, &field.ty);
writeln!(out, "{};", field.ident);
}
+ if !methods.is_empty() {
+ writeln!(out);
+ }
+ for method in methods {
+ write!(out, " ");
+ let sig = &method.sig;
+ let local_name = method.name.cxx.to_string();
+ write_rust_function_shim_decl(out, &local_name, sig, false);
+ writeln!(out, ";");
+ }
writeln!(out, "}};");
+ writeln!(out, "#endif // {}", guard);
}
fn write_struct_decl(out: &mut OutFile, ident: &Ident) {
writeln!(out, "struct {};", ident);
}
-fn write_struct_using(out: &mut OutFile, ident: &Ident) {
- writeln!(out, "using {} = {};", ident, ident);
+fn write_enum_decl(out: &mut OutFile, enm: &Enum) {
+ write!(out, "enum class {} : ", enm.name.cxx);
+ write_atom(out, enm.repr);
+ writeln!(out, ";");
}
-fn write_struct_with_methods(out: &mut OutFile, ety: &ExternType, methods: &[&ExternFn]) {
+fn write_struct_using(out: &mut OutFile, ident: &Pair) {
+ writeln!(out, "using {} = {};", ident.cxx, ident.to_fully_qualified());
+}
+
+fn write_struct_with_methods<'a>(
+ out: &mut OutFile<'a>,
+ ety: &'a ExternType,
+ methods: &[&ExternFn],
+) {
+ out.set_namespace(&ety.name.namespace);
+ let guard = format!("CXXBRIDGE05_STRUCT_{}", ety.name.to_symbol());
+ writeln!(out, "#ifndef {}", guard);
+ writeln!(out, "#define {}", guard);
for line in ety.doc.to_string().lines() {
writeln!(out, "//{}", line);
}
- writeln!(out, "struct {} final {{", ety.ident);
- writeln!(out, " {}() = delete;", ety.ident);
- writeln!(out, " {}(const {} &) = delete;", ety.ident, ety.ident);
+ writeln!(out, "struct {} final {{", ety.name.cxx);
+ writeln!(out, " {}() = delete;", ety.name.cxx);
+ writeln!(
+ out,
+ " {}(const {} &) = delete;",
+ ety.name.cxx, ety.name.cxx,
+ );
for method in methods {
write!(out, " ");
let sig = &method.sig;
- let local_name = method.ident.to_string();
+ let local_name = method.name.cxx.to_string();
write_rust_function_shim_decl(out, &local_name, sig, false);
writeln!(out, ";");
}
writeln!(out, "}};");
+ writeln!(out, "#endif // {}", guard);
}
-fn write_enum(out: &mut OutFile, enm: &Enum) {
+fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) {
+ out.set_namespace(&enm.name.namespace);
+ let guard = format!("CXXBRIDGE05_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.ident);
+ write!(out, "enum class {} : ", enm.name.cxx);
write_atom(out, enm.repr);
writeln!(out, " {{");
for variant in &enm.variants {
writeln!(out, " {} = {},", variant.ident, variant.discriminant);
}
writeln!(out, "}};");
+ writeln!(out, "#endif // {}", guard);
}
-fn check_enum(out: &mut OutFile, enm: &Enum) {
- write!(out, "static_assert(sizeof({}) == sizeof(", enm.ident);
+fn check_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) {
+ out.set_namespace(&enm.name.namespace);
+ write!(out, "static_assert(sizeof({}) == sizeof(", enm.name.cxx);
write_atom(out, enm.repr);
writeln!(out, "), \"incorrect size\");");
for variant in &enm.variants {
@@ -374,54 +278,80 @@
writeln!(
out,
">({}::{}) == {}, \"disagrees with the value in #[cxx::bridge]\");",
- enm.ident, variant.ident, variant.discriminant,
+ enm.name.cxx, variant.ident, variant.discriminant,
);
}
}
-fn write_exception_glue(out: &mut OutFile, apis: &[Api]) {
- let mut has_cxx_throws = false;
- for api in apis {
- if let Api::CxxFunction(efn) = api {
- if efn.throws {
- has_cxx_throws = true;
- break;
- }
- }
- }
+fn check_trivial_extern_type(out: &mut OutFile, id: &Pair) {
+ // 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 {};
+ //
- if has_cxx_throws {
- out.next_section();
- writeln!(
- out,
- "const char *cxxbridge03$exception(const char *, size_t);",
- );
- }
+ let id = id.to_fully_qualified();
+ out.builtin.relocatable = true;
+ writeln!(out, "static_assert(");
+ writeln!(out, " ::rust::IsRelocatable<{}>::value,", id);
+ writeln!(
+ out,
+ " \"type {} marked as Trivial in Rust is not trivially move constructible and trivially destructible in C++\");",
+ id,
+ );
}
-fn write_cxx_function_shim(
- out: &mut OutFile,
- efn: &ExternFn,
- types: &Types,
- impl_annotations: &Option<String>,
-) {
- if !out.header {
- if let Some(annotation) = 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);
+ if let Some(annotation) = &out.opt.cxx_impl_annotations {
+ write!(out, "{} ", annotation);
}
if efn.throws {
- write!(out, "::rust::Str::Repr ");
+ out.builtin.ptr_len = true;
+ write!(out, "::rust::repr::PtrLen ");
} else {
- write_extern_return_type_space(out, &efn.ret, types);
+ write_extern_return_type_space(out, &efn.ret);
}
- let mangled = mangle::extern_fn(&out.namespace, efn);
+ let mangled = mangle::extern_fn(efn, out.types);
write!(out, "{}(", mangled);
if let Some(receiver) = &efn.receiver {
if receiver.mutability.is_none() {
write!(out, "const ");
}
- write!(out, "{} &self", receiver.ty);
+ write!(
+ out,
+ "{} &self",
+ out.types.resolve(&receiver.ty).to_fully_qualified(),
+ );
}
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 || efn.receiver.is_some() {
@@ -432,9 +362,9 @@
} else if let Type::RustVec(_) = arg.ty {
write!(out, "const ");
}
- write_extern_arg(out, arg, types);
+ write_extern_arg(out, arg);
}
- let indirect_return = indirect_return(efn, types);
+ let indirect_return = indirect_return(efn, out.types);
if indirect_return {
if !efn.args.is_empty() || efn.receiver.is_some() {
write!(out, ", ");
@@ -446,8 +376,13 @@
write!(out, " ");
write_return_type(out, &efn.ret);
match &efn.receiver {
- None => write!(out, "(*{}$)(", efn.ident),
- Some(receiver) => write!(out, "({}::*{}$)(", receiver.ty, efn.ident),
+ None => write!(out, "(*{}$)(", efn.name.rust),
+ Some(receiver) => write!(
+ out,
+ "({}::*{}$)(",
+ out.types.resolve(&receiver.ty).to_fully_qualified(),
+ efn.name.rust,
+ ),
}
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
@@ -463,13 +398,20 @@
}
write!(out, " = ");
match &efn.receiver {
- None => write!(out, "{}", efn.ident),
- Some(receiver) => write!(out, "&{}::{}", receiver.ty, efn.ident),
+ None => write!(out, "{}", efn.name.to_fully_qualified()),
+ Some(receiver) => write!(
+ out,
+ "&{}::{}",
+ out.types.resolve(&receiver.ty).to_fully_qualified(),
+ efn.name.cxx,
+ ),
}
writeln!(out, ";");
write!(out, " ");
if efn.throws {
- writeln!(out, "::rust::Str::Repr throw$;");
+ out.builtin.ptr_len = true;
+ out.builtin.trycatch = true;
+ writeln!(out, "::rust::repr::PtrLen throw$;");
writeln!(out, " ::rust::behavior::trycatch(");
writeln!(out, " [&] {{");
write!(out, " ");
@@ -484,15 +426,19 @@
}
match &efn.ret {
Some(Type::Ref(_)) => write!(out, "&"),
- Some(Type::Str(_)) if !indirect_return => write!(out, "::rust::Str::Repr("),
+ Some(Type::Str(_)) if !indirect_return => {
+ out.builtin.rust_str_repr = true;
+ write!(out, "::rust::impl<::rust::Str>::repr(");
+ }
Some(Type::SliceRefU8(_)) if !indirect_return => {
- write!(out, "::rust::Slice<uint8_t>::Repr(")
+ out.builtin.rust_slice_repr = true;
+ write!(out, "::rust::impl<::rust::Slice<uint8_t>>::repr(")
}
_ => {}
}
match &efn.receiver {
- None => write!(out, "{}$(", efn.ident),
- Some(_) => write!(out, "(self.*{}$)(", efn.ident),
+ None => write!(out, "{}$(", efn.name.rust),
+ Some(_) => write!(out, "(self.*{}$)(", efn.name.rust),
}
for (i, arg) in efn.args.iter().enumerate() {
if i > 0 {
@@ -504,16 +450,31 @@
} else if let Type::UniquePtr(_) = &arg.ty {
write_type(out, &arg.ty);
write!(out, "({})", arg.ident);
+ } else if let Type::Str(_) = arg.ty {
+ out.builtin.rust_str_new_unchecked = true;
+ write!(
+ out,
+ "::rust::impl<::rust::Str>::new_unchecked({})",
+ arg.ident,
+ );
} else if arg.ty == RustString {
+ out.builtin.unsafe_bitcopy = true;
write!(
out,
"::rust::String(::rust::unsafe_bitcopy, *{})",
arg.ident,
);
} else if let Type::RustVec(_) = arg.ty {
+ out.builtin.unsafe_bitcopy = true;
write_type(out, &arg.ty);
write!(out, "(::rust::unsafe_bitcopy, *{})", arg.ident);
- } else if types.needs_indirect_abi(&arg.ty) {
+ } else if let Type::SliceRefU8(_) = arg.ty {
+ write!(
+ out,
+ "::rust::Slice<uint8_t>(static_cast<const uint8_t *>({0}.ptr), {0}.len)",
+ arg.ident,
+ );
+ } else if out.types.needs_indirect_abi(&arg.ty) {
out.include.utility = true;
write!(out, "::std::move(*{})", arg.ident);
} else {
@@ -533,13 +494,14 @@
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 = cxxbridge03$exception(catch$, throw$.len);",
+ " throw$.ptr = ::cxxbridge05$exception(catch$, throw$.len);",
);
writeln!(out, " }});");
writeln!(out, " return throw$;");
@@ -548,9 +510,10 @@
for arg in &efn.args {
if let Type::Fn(f) = &arg.ty {
let var = &arg.ident;
- write_function_pointer_trampoline(out, efn, var, f, types);
+ write_function_pointer_trampoline(out, efn, var, f);
}
}
+ out.end_block(Block::ExternC);
}
fn write_function_pointer_trampoline(
@@ -558,35 +521,37 @@
efn: &ExternFn,
var: &Ident,
f: &Signature,
- types: &Types,
) {
- out.next_section();
- let r_trampoline = mangle::r_trampoline(&out.namespace, efn, var);
+ let r_trampoline = mangle::r_trampoline(efn, var, out.types);
let indirect_call = true;
- write_rust_function_decl_impl(out, &r_trampoline, f, types, indirect_call);
+ write_rust_function_decl_impl(out, &r_trampoline, f, indirect_call);
out.next_section();
- let c_trampoline = mangle::c_trampoline(&out.namespace, efn, var).to_string();
- write_rust_function_shim_impl(out, &c_trampoline, f, types, &r_trampoline, indirect_call);
+ let c_trampoline = mangle::c_trampoline(efn, var, out.types).to_string();
+ write_rust_function_shim_impl(out, &c_trampoline, f, &r_trampoline, indirect_call);
}
-fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types, _: &Option<String>) {
- let link_name = mangle::extern_fn(&out.namespace, efn);
+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, types, indirect_call);
+ 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,
- types: &Types,
indirect_call: bool,
) {
+ out.next_section();
if sig.throws {
- write!(out, "::rust::Str::Repr ");
+ out.builtin.ptr_len = true;
+ write!(out, "::rust::repr::PtrLen ");
} else {
- write_extern_return_type_space(out, &sig.ret, types);
+ write_extern_return_type_space(out, &sig.ret);
}
write!(out, "{}(", link_name);
let mut needs_comma = false;
@@ -594,17 +559,21 @@
if receiver.mutability.is_none() {
write!(out, "const ");
}
- write!(out, "{} &self", receiver.ty);
+ write!(
+ out,
+ "{} &self",
+ out.types.resolve(&receiver.ty).to_fully_qualified(),
+ );
needs_comma = true;
}
for arg in &sig.args {
if needs_comma {
write!(out, ", ");
}
- write_extern_arg(out, arg, types);
+ write_extern_arg(out, arg);
needs_comma = true;
}
- if indirect_return(sig, types) {
+ if indirect_return(sig, out.types) {
if needs_comma {
write!(out, ", ");
}
@@ -621,17 +590,18 @@
writeln!(out, ") noexcept;");
}
-fn write_rust_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
+fn write_rust_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) {
+ out.set_namespace(&efn.name.namespace);
for line in efn.doc.to_string().lines() {
writeln!(out, "//{}", line);
}
let local_name = match &efn.sig.receiver {
- None => efn.ident.to_string(),
- Some(receiver) => format!("{}::{}", receiver.ty, efn.ident),
+ None => efn.name.cxx.to_string(),
+ Some(receiver) => format!("{}::{}", out.types.resolve(&receiver.ty).cxx, efn.name.cxx),
};
- let invoke = mangle::extern_fn(&out.namespace, efn);
+ let invoke = mangle::extern_fn(efn, out.types);
let indirect_call = false;
- write_rust_function_shim_impl(out, &local_name, efn, types, &invoke, indirect_call);
+ write_rust_function_shim_impl(out, &local_name, efn, &invoke, indirect_call);
}
fn write_rust_function_shim_decl(
@@ -670,7 +640,6 @@
out: &mut OutFile,
local_name: &str,
sig: &Signature,
- types: &Types,
invoke: &Symbol,
indirect_call: bool,
) {
@@ -685,16 +654,18 @@
}
writeln!(out, " {{");
for arg in &sig.args {
- if arg.ty != RustString && types.needs_indirect_abi(&arg.ty) {
+ 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.ident);
}
}
write!(out, " ");
- let indirect_return = indirect_return(sig, types);
+ let indirect_return = indirect_return(sig, out.types);
if indirect_return {
+ out.builtin.maybe_uninit = true;
write!(out, "::rust::MaybeUninit<");
write_type(out, sig.ret.as_ref().unwrap());
writeln!(out, "> return$;");
@@ -711,24 +682,41 @@
write!(out, "(");
}
Type::Ref(_) => write!(out, "*"),
+ Type::Str(_) => {
+ out.builtin.rust_str_new_unchecked = true;
+ write!(out, "::rust::impl<::rust::Str>::new_unchecked(");
+ }
+ Type::SliceRefU8(_) => {
+ out.builtin.rust_slice_new = true;
+ write!(out, "::rust::impl<::rust::Slice<uint8_t>>::slice(");
+ }
_ => {}
}
}
if sig.throws {
- write!(out, "::rust::Str::Repr error$ = ");
+ 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 (i, arg) in sig.args.iter().enumerate() {
- if i > 0 || sig.receiver.is_some() {
+ for arg in &sig.args {
+ if needs_comma {
write!(out, ", ");
}
match &arg.ty {
- Type::Str(_) => write!(out, "::rust::Str::Repr("),
- Type::SliceRefU8(_) => write!(out, "::rust::Slice<uint8_t>::Repr("),
- ty if types.needs_indirect_abi(ty) => write!(out, "&"),
+ Type::Str(_) => {
+ out.builtin.rust_str_repr = true;
+ write!(out, "::rust::impl<::rust::Str>::repr(");
+ }
+ Type::SliceRefU8(_) => {
+ out.builtin.rust_slice_repr = true;
+ write!(out, "::rust::impl<::rust::Slice<uint8_t>>::repr(");
+ }
+ ty if out.types.needs_indirect_abi(ty) => write!(out, "&"),
_ => {}
}
write!(out, "{}", arg.ident);
@@ -736,32 +724,38 @@
Type::RustBox(_) => write!(out, ".into_raw()"),
Type::UniquePtr(_) => write!(out, ".release()"),
Type::Str(_) | Type::SliceRefU8(_) => write!(out, ")"),
- ty if ty != RustString && types.needs_indirect_abi(ty) => write!(out, "$.value"),
+ ty if ty != RustString && out.types.needs_indirect_abi(ty) => write!(out, "$.value"),
_ => {}
}
+ needs_comma = true;
}
if indirect_return {
- if !sig.args.is_empty() {
+ if needs_comma {
write!(out, ", ");
}
write!(out, "&return$.value");
+ needs_comma = true;
}
if indirect_call {
- if !sig.args.is_empty() || indirect_return {
+ if needs_comma {
write!(out, ", ");
}
write!(out, "extern$");
}
write!(out, ")");
- if let Some(ret) = &sig.ret {
- if let Type::RustBox(_) | Type::UniquePtr(_) = ret {
- write!(out, ")");
+ if !indirect_return {
+ if let Some(ret) = &sig.ret {
+ if let Type::RustBox(_) | Type::UniquePtr(_) | Type::Str(_) | Type::SliceRefU8(_) = ret
+ {
+ write!(out, ")");
+ }
}
}
writeln!(out, ";");
if sig.throws {
+ out.builtin.rust_error = true;
writeln!(out, " if (error$.ptr) {{");
- writeln!(out, " throw ::rust::Error(error$);");
+ writeln!(out, " throw ::rust::impl<::rust::Error>::error(error$);");
writeln!(out, " }}");
}
if indirect_return {
@@ -797,8 +791,6 @@
write_type(out, &ty.inner);
write!(out, " *");
}
- Type::Str(_) => write!(out, "::rust::Str::Repr"),
- Type::SliceRefU8(_) => write!(out, "::rust::Slice<uint8_t>::Repr"),
_ => write_type(out, ty),
}
}
@@ -812,7 +804,7 @@
}
}
-fn write_extern_return_type_space(out: &mut OutFile, ty: &Option<Type>, types: &Types) {
+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);
@@ -825,24 +817,28 @@
write_type(out, &ty.inner);
write!(out, " *");
}
- Some(Type::Str(_)) => write!(out, "::rust::Str::Repr "),
- Some(Type::SliceRefU8(_)) => write!(out, "::rust::Slice<uint8_t>::Repr "),
- Some(ty) if types.needs_indirect_abi(ty) => write!(out, "void "),
+ Some(Type::Str(_)) | Some(Type::SliceRefU8(_)) => {
+ out.builtin.ptr_len = true;
+ write!(out, "::rust::repr::PtrLen ");
+ }
+ 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, types: &Types) {
+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, "*");
}
- Type::Str(_) => write!(out, "::rust::Str::Repr "),
- Type::SliceRefU8(_) => write!(out, "::rust::Slice<uint8_t>::Repr "),
+ Type::Str(_) | Type::SliceRefU8(_) => {
+ out.builtin.ptr_len = true;
+ write!(out, "::rust::repr::PtrLen ");
+ }
_ => write_type_space(out, &arg.ty),
}
- if types.needs_indirect_abi(&arg.ty) {
+ if out.types.needs_indirect_abi(&arg.ty) {
write!(out, "*");
}
write!(out, "{}", arg.ident);
@@ -850,9 +846,9 @@
fn write_type(out: &mut OutFile, ty: &Type) {
match ty {
- Type::Ident(ident) => match Atom::from(ident) {
+ Type::Ident(ident) => match Atom::from(&ident.rust) {
Some(atom) => write_atom(out, atom),
- None => write!(out, "{}", ident),
+ None => write!(out, "{}", out.types.resolve(ident).to_fully_qualified()),
},
Type::RustBox(ty) => {
write!(out, "::rust::Box<");
@@ -952,223 +948,256 @@
// Only called for legal referent types of unique_ptr and element types of
// std::vector and Vec.
-fn to_typename(namespace: &Namespace, ty: &Type) -> String {
+fn to_typename(ty: &Type, types: &Types) -> String {
match ty {
- Type::Ident(ident) => {
- let mut path = String::new();
- for name in namespace {
- path += &name.to_string();
- path += "::";
- }
- path += &ident.to_string();
- path
- }
- Type::CxxVector(ptr) => format!("::std::vector<{}>", to_typename(namespace, &ptr.inner)),
+ Type::Ident(ident) => types.resolve(&ident).to_fully_qualified(),
+ Type::CxxVector(ptr) => format!("::std::vector<{}>", to_typename(&ptr.inner, types)),
_ => unreachable!(),
}
}
// Only called for legal referent types of unique_ptr and element types of
// std::vector and Vec.
-fn to_mangled(namespace: &Namespace, ty: &Type) -> String {
+fn to_mangled(ty: &Type, types: &Types) -> Symbol {
match ty {
- Type::Ident(_) => to_typename(namespace, ty).replace("::", "$"),
- Type::CxxVector(ptr) => format!("std$vector${}", to_mangled(namespace, &ptr.inner)),
+ Type::Ident(ident) => ident.to_symbol(types),
+ Type::CxxVector(ptr) => to_mangled(&ptr.inner, types).prefix_with("std$vector$"),
_ => unreachable!(),
}
}
-fn write_generic_instantiations(out: &mut OutFile, types: &Types) {
- out.begin_block("extern \"C\"");
- for ty in types {
+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 ty in out.types {
if let Type::RustBox(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
out.next_section();
- write_rust_box_extern(out, inner);
+ write_rust_box_extern(out, &out.types.resolve(&inner));
}
} else if let Type::RustVec(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
- if Atom::from(inner).is_none() {
+ if Atom::from(&inner.rust).is_none() {
out.next_section();
write_rust_vec_extern(out, inner);
}
}
} else if let Type::UniquePtr(ptr) = ty {
if let Type::Ident(inner) = &ptr.inner {
- if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
+ if Atom::from(&inner.rust).is_none()
+ && (!out.types.aliases.contains_key(&inner.rust)
+ || out.types.explicit_impls.contains(ty))
+ {
out.next_section();
- write_unique_ptr(out, inner, types);
+ write_unique_ptr(out, inner);
}
}
} else if let Type::CxxVector(ptr) = ty {
if let Type::Ident(inner) = &ptr.inner {
- if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
+ if Atom::from(&inner.rust).is_none()
+ && (!out.types.aliases.contains_key(&inner.rust)
+ || out.types.explicit_impls.contains(ty))
+ {
out.next_section();
- write_cxx_vector(out, ty, inner, types);
+ write_cxx_vector(out, ty, inner);
}
}
}
}
- out.end_block("extern \"C\"");
+ out.end_block(Block::ExternC);
- out.begin_block("namespace rust");
- out.begin_block("inline namespace cxxbridge03");
- for ty in types {
+ out.begin_block(Block::Namespace("rust"));
+ out.begin_block(Block::InlineNamespace("cxxbridge05"));
+ for ty in out.types {
if let Type::RustBox(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
- write_rust_box_impl(out, inner);
+ write_rust_box_impl(out, &out.types.resolve(&inner));
}
} else if let Type::RustVec(ty) = ty {
if let Type::Ident(inner) = &ty.inner {
- if Atom::from(inner).is_none() {
+ if Atom::from(&inner.rust).is_none() {
write_rust_vec_impl(out, inner);
}
}
}
}
- out.end_block("namespace cxxbridge03");
- out.end_block("namespace rust");
+ out.end_block(Block::InlineNamespace("cxxbridge05"));
+ out.end_block(Block::Namespace("rust"));
}
-fn write_rust_box_extern(out: &mut OutFile, ident: &Ident) {
- let mut inner = String::new();
- for name in &out.namespace {
- inner += &name.to_string();
- inner += "::";
- }
- inner += &ident.to_string();
- let instance = inner.replace("::", "$");
+fn write_rust_box_extern(out: &mut OutFile, ident: &Pair) {
+ let inner = ident.to_fully_qualified();
+ let instance = ident.to_symbol();
- writeln!(out, "#ifndef CXXBRIDGE03_RUST_BOX_{}", instance);
- writeln!(out, "#define CXXBRIDGE03_RUST_BOX_{}", instance);
+ writeln!(out, "#ifndef CXXBRIDGE05_RUST_BOX_{}", instance);
+ writeln!(out, "#define CXXBRIDGE05_RUST_BOX_{}", instance);
writeln!(
out,
- "void cxxbridge03$box${}$uninit(::rust::Box<{}> *ptr) noexcept;",
+ "void cxxbridge05$box${}$uninit(::rust::Box<{}> *ptr) noexcept;",
instance, inner,
);
writeln!(
out,
- "void cxxbridge03$box${}$drop(::rust::Box<{}> *ptr) noexcept;",
+ "void cxxbridge05$box${}$drop(::rust::Box<{}> *ptr) noexcept;",
instance, inner,
);
- writeln!(out, "#endif // CXXBRIDGE03_RUST_BOX_{}", instance);
+ writeln!(out, "#endif // CXXBRIDGE05_RUST_BOX_{}", instance);
}
-fn write_rust_vec_extern(out: &mut OutFile, element: &Ident) {
+fn write_rust_vec_extern(out: &mut OutFile, element: &ResolvableName) {
let element = Type::Ident(element.clone());
- let inner = to_typename(&out.namespace, &element);
- let instance = to_mangled(&out.namespace, &element);
+ let inner = to_typename(&element, out.types);
+ let instance = to_mangled(&element, out.types);
- writeln!(out, "#ifndef CXXBRIDGE03_RUST_VEC_{}", instance);
- writeln!(out, "#define CXXBRIDGE03_RUST_VEC_{}", instance);
+ writeln!(out, "#ifndef CXXBRIDGE05_RUST_VEC_{}", instance);
+ writeln!(out, "#define CXXBRIDGE05_RUST_VEC_{}", instance);
writeln!(
out,
- "void cxxbridge03$rust_vec${}$new(const ::rust::Vec<{}> *ptr) noexcept;",
+ "void cxxbridge05$rust_vec${}$new(const ::rust::Vec<{}> *ptr) noexcept;",
instance, inner,
);
writeln!(
out,
- "void cxxbridge03$rust_vec${}$drop(::rust::Vec<{}> *ptr) noexcept;",
+ "void cxxbridge05$rust_vec${}$drop(::rust::Vec<{}> *ptr) noexcept;",
instance, inner,
);
writeln!(
out,
- "size_t cxxbridge03$rust_vec${}$len(const ::rust::Vec<{}> *ptr) noexcept;",
+ "size_t cxxbridge05$rust_vec${}$len(const ::rust::Vec<{}> *ptr) noexcept;",
instance, inner,
);
writeln!(
out,
- "const {} *cxxbridge03$rust_vec${}$data(const ::rust::Vec<{0}> *ptr) noexcept;",
+ "const {} *cxxbridge05$rust_vec${}$data(const ::rust::Vec<{0}> *ptr) noexcept;",
inner, instance,
);
writeln!(
out,
- "size_t cxxbridge03$rust_vec${}$stride() noexcept;",
+ "void cxxbridge05$rust_vec${}$reserve_total(::rust::Vec<{}> *ptr, size_t cap) noexcept;",
+ instance, inner,
+ );
+ writeln!(
+ out,
+ "void cxxbridge05$rust_vec${}$set_len(::rust::Vec<{}> *ptr, size_t len) noexcept;",
+ instance, inner,
+ );
+ writeln!(
+ out,
+ "size_t cxxbridge05$rust_vec${}$stride() noexcept;",
instance,
);
- writeln!(out, "#endif // CXXBRIDGE03_RUST_VEC_{}", instance);
+ writeln!(out, "#endif // CXXBRIDGE05_RUST_VEC_{}", instance);
}
-fn write_rust_box_impl(out: &mut OutFile, ident: &Ident) {
- let mut inner = String::new();
- for name in &out.namespace {
- inner += &name.to_string();
- inner += "::";
- }
- inner += &ident.to_string();
- let instance = inner.replace("::", "$");
+fn write_rust_box_impl(out: &mut OutFile, ident: &Pair) {
+ let inner = ident.to_fully_qualified();
+ let instance = ident.to_symbol();
writeln!(out, "template <>");
writeln!(out, "void Box<{}>::uninit() noexcept {{", inner);
- writeln!(out, " cxxbridge03$box${}$uninit(this);", instance);
+ writeln!(out, " cxxbridge05$box${}$uninit(this);", instance);
writeln!(out, "}}");
writeln!(out, "template <>");
writeln!(out, "void Box<{}>::drop() noexcept {{", inner);
- writeln!(out, " cxxbridge03$box${}$drop(this);", instance);
+ writeln!(out, " cxxbridge05$box${}$drop(this);", instance);
writeln!(out, "}}");
}
-fn write_rust_vec_impl(out: &mut OutFile, element: &Ident) {
+fn write_rust_vec_impl(out: &mut OutFile, element: &ResolvableName) {
let element = Type::Ident(element.clone());
- let inner = to_typename(&out.namespace, &element);
- let instance = to_mangled(&out.namespace, &element);
+ let inner = to_typename(&element, out.types);
+ let instance = to_mangled(&element, out.types);
writeln!(out, "template <>");
writeln!(out, "Vec<{}>::Vec() noexcept {{", inner);
- writeln!(out, " cxxbridge03$rust_vec${}$new(this);", instance);
+ writeln!(out, " cxxbridge05$rust_vec${}$new(this);", instance);
writeln!(out, "}}");
writeln!(out, "template <>");
writeln!(out, "void Vec<{}>::drop() noexcept {{", inner);
writeln!(
out,
- " return cxxbridge03$rust_vec${}$drop(this);",
+ " return cxxbridge05$rust_vec${}$drop(this);",
instance,
);
writeln!(out, "}}");
writeln!(out, "template <>");
writeln!(out, "size_t Vec<{}>::size() const noexcept {{", inner);
- writeln!(out, " return cxxbridge03$rust_vec${}$len(this);", instance);
+ writeln!(out, " return cxxbridge05$rust_vec${}$len(this);", instance);
writeln!(out, "}}");
writeln!(out, "template <>");
writeln!(out, "const {} *Vec<{0}>::data() const noexcept {{", inner);
writeln!(
out,
- " return cxxbridge03$rust_vec${}$data(this);",
+ " return cxxbridge05$rust_vec${}$data(this);",
+ instance,
+ );
+ writeln!(out, "}}");
+
+ writeln!(out, "template <>");
+ writeln!(
+ out,
+ "void Vec<{}>::reserve_total(size_t cap) noexcept {{",
+ inner,
+ );
+ writeln!(
+ out,
+ " return cxxbridge05$rust_vec${}$reserve_total(this, cap);",
+ instance,
+ );
+ writeln!(out, "}}");
+
+ writeln!(out, "template <>");
+ writeln!(out, "void Vec<{}>::set_len(size_t len) noexcept {{", inner);
+ writeln!(
+ out,
+ " return cxxbridge05$rust_vec${}$set_len(this, len);",
instance,
);
writeln!(out, "}}");
writeln!(out, "template <>");
writeln!(out, "size_t Vec<{}>::stride() noexcept {{", inner);
- writeln!(out, " return cxxbridge03$rust_vec${}$stride();", instance);
+ writeln!(out, " return cxxbridge05$rust_vec${}$stride();", instance);
writeln!(out, "}}");
}
-fn write_unique_ptr(out: &mut OutFile, ident: &Ident, types: &Types) {
+fn write_unique_ptr(out: &mut OutFile, ident: &ResolvableName) {
let ty = Type::Ident(ident.clone());
- let instance = to_mangled(&out.namespace, &ty);
+ let instance = to_mangled(&ty, out.types);
- writeln!(out, "#ifndef CXXBRIDGE03_UNIQUE_PTR_{}", instance);
- writeln!(out, "#define CXXBRIDGE03_UNIQUE_PTR_{}", instance);
+ writeln!(out, "#ifndef CXXBRIDGE05_UNIQUE_PTR_{}", instance);
+ writeln!(out, "#define CXXBRIDGE05_UNIQUE_PTR_{}", instance);
- write_unique_ptr_common(out, &ty, types);
+ write_unique_ptr_common(out, &ty);
- writeln!(out, "#endif // CXXBRIDGE03_UNIQUE_PTR_{}", instance);
+ writeln!(out, "#endif // CXXBRIDGE05_UNIQUE_PTR_{}", instance);
}
// Shared by UniquePtr<T> and UniquePtr<CxxVector<T>>.
-fn write_unique_ptr_common(out: &mut OutFile, ty: &Type, types: &Types) {
+fn write_unique_ptr_common(out: &mut OutFile, ty: &Type) {
out.include.new = true;
out.include.utility = true;
- let inner = to_typename(&out.namespace, ty);
- let instance = to_mangled(&out.namespace, ty);
+ let inner = to_typename(ty, out.types);
+ let instance = to_mangled(ty, out.types);
let can_construct_from_value = match ty {
- Type::Ident(ident) => types.structs.contains_key(ident),
+ // 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.
+ Type::Ident(ident) => {
+ out.types.structs.contains_key(&ident.rust)
+ || out.types.aliases.contains_key(&ident.rust)
+ }
_ => false,
};
@@ -1184,7 +1213,7 @@
);
writeln!(
out,
- "void cxxbridge03$unique_ptr${}$null(::std::unique_ptr<{}> *ptr) noexcept {{",
+ "void cxxbridge05$unique_ptr${}$null(::std::unique_ptr<{}> *ptr) noexcept {{",
instance, inner,
);
writeln!(out, " new (ptr) ::std::unique_ptr<{}>();", inner);
@@ -1192,7 +1221,7 @@
if can_construct_from_value {
writeln!(
out,
- "void cxxbridge03$unique_ptr${}$new(::std::unique_ptr<{}> *ptr, {} *value) noexcept {{",
+ "void cxxbridge05$unique_ptr${}$new(::std::unique_ptr<{}> *ptr, {} *value) noexcept {{",
instance, inner, inner,
);
writeln!(
@@ -1204,57 +1233,57 @@
}
writeln!(
out,
- "void cxxbridge03$unique_ptr${}$raw(::std::unique_ptr<{}> *ptr, {} *raw) noexcept {{",
+ "void cxxbridge05$unique_ptr${}$raw(::std::unique_ptr<{}> *ptr, {} *raw) noexcept {{",
instance, inner, inner,
);
writeln!(out, " new (ptr) ::std::unique_ptr<{}>(raw);", inner);
writeln!(out, "}}");
writeln!(
out,
- "const {} *cxxbridge03$unique_ptr${}$get(const ::std::unique_ptr<{}>& ptr) noexcept {{",
+ "const {} *cxxbridge05$unique_ptr${}$get(const ::std::unique_ptr<{}>& ptr) noexcept {{",
inner, instance, inner,
);
writeln!(out, " return ptr.get();");
writeln!(out, "}}");
writeln!(
out,
- "{} *cxxbridge03$unique_ptr${}$release(::std::unique_ptr<{}>& ptr) noexcept {{",
+ "{} *cxxbridge05$unique_ptr${}$release(::std::unique_ptr<{}>& ptr) noexcept {{",
inner, instance, inner,
);
writeln!(out, " return ptr.release();");
writeln!(out, "}}");
writeln!(
out,
- "void cxxbridge03$unique_ptr${}$drop(::std::unique_ptr<{}> *ptr) noexcept {{",
+ "void cxxbridge05$unique_ptr${}$drop(::std::unique_ptr<{}> *ptr) noexcept {{",
instance, inner,
);
writeln!(out, " ptr->~unique_ptr();");
writeln!(out, "}}");
}
-fn write_cxx_vector(out: &mut OutFile, vector_ty: &Type, element: &Ident, types: &Types) {
+fn write_cxx_vector(out: &mut OutFile, vector_ty: &Type, element: &ResolvableName) {
let element = Type::Ident(element.clone());
- let inner = to_typename(&out.namespace, &element);
- let instance = to_mangled(&out.namespace, &element);
+ let inner = to_typename(&element, out.types);
+ let instance = to_mangled(&element, out.types);
- writeln!(out, "#ifndef CXXBRIDGE03_VECTOR_{}", instance);
- writeln!(out, "#define CXXBRIDGE03_VECTOR_{}", instance);
+ writeln!(out, "#ifndef CXXBRIDGE05_VECTOR_{}", instance);
+ writeln!(out, "#define CXXBRIDGE05_VECTOR_{}", instance);
writeln!(
out,
- "size_t cxxbridge03$std$vector${}$size(const ::std::vector<{}> &s) noexcept {{",
+ "size_t cxxbridge05$std$vector${}$size(const ::std::vector<{}> &s) noexcept {{",
instance, inner,
);
writeln!(out, " return s.size();");
writeln!(out, "}}");
writeln!(
out,
- "const {} *cxxbridge03$std$vector${}$get_unchecked(const ::std::vector<{}> &s, size_t pos) noexcept {{",
+ "const {} *cxxbridge05$std$vector${}$get_unchecked(const ::std::vector<{}> &s, size_t pos) noexcept {{",
inner, instance, inner,
);
writeln!(out, " return &s[pos];");
writeln!(out, "}}");
- write_unique_ptr_common(out, vector_ty, types);
+ write_unique_ptr_common(out, vector_ty);
- writeln!(out, "#endif // CXXBRIDGE03_VECTOR_{}", instance);
+ writeln!(out, "#endif // CXXBRIDGE05_VECTOR_{}", instance);
}
diff --git a/include/cxx.h b/include/cxx.h
index c3231fd..7dfbbca 100644
--- a/include/cxx.h
+++ b/include/cxx.h
@@ -5,21 +5,27 @@
#include <exception>
#include <iosfwd>
#include <new>
+#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#if defined(_WIN32)
-#include <BaseTsd.h>
+#include <basetsd.h>
#endif
namespace rust {
-inline namespace cxxbridge03 {
+inline namespace cxxbridge05 {
struct unsafe_bitcopy_t;
-#ifndef CXXBRIDGE03_RUST_STRING
-#define CXXBRIDGE03_RUST_STRING
+namespace {
+template <typename T>
+class impl;
+}
+
+#ifndef CXXBRIDGE05_RUST_STRING
+#define CXXBRIDGE05_RUST_STRING
class String final {
public:
String() noexcept;
@@ -48,21 +54,18 @@
// Size and alignment statically verified by rust_string.rs.
std::array<uintptr_t, 3> repr;
};
-#endif // CXXBRIDGE03_RUST_STRING
+#endif // CXXBRIDGE05_RUST_STRING
-#ifndef CXXBRIDGE03_RUST_STR
-#define CXXBRIDGE03_RUST_STR
+#ifndef CXXBRIDGE05_RUST_STR
class Str final {
public:
Str() noexcept;
- Str(const Str &) noexcept;
-
Str(const std::string &);
Str(const char *);
Str(const char *, size_t);
Str(std::string &&) = delete;
- Str &operator=(Str) noexcept;
+ Str &operator=(const Str &) noexcept = default;
explicit operator std::string() const;
@@ -71,54 +74,46 @@
size_t size() const noexcept;
size_t length() const noexcept;
- // Repr is PRIVATE; must not be used other than by our generated code.
- //
- // Not necessarily ABI compatible with &str. Codegen will translate to
- // cxx::rust_str::RustStr which matches this layout.
- struct Repr {
- const char *ptr;
- size_t len;
- };
- Str(Repr) noexcept;
- explicit operator Repr() noexcept;
+ // Important in order for System V ABI to pass in registers.
+ Str(const Str &) noexcept = default;
+ ~Str() noexcept = default;
private:
- Repr repr;
+ friend impl<Str>;
+ // Not necessarily ABI compatible with &str. Codegen will translate to
+ // cxx::rust_str::RustStr which matches this layout.
+ const char *ptr;
+ size_t len;
};
-#endif // CXXBRIDGE03_RUST_STR
+#endif // CXXBRIDGE05_RUST_STR
-#ifndef CXXBRIDGE03_RUST_SLICE
+#ifndef CXXBRIDGE05_RUST_SLICE
template <typename T>
class Slice final {
public:
Slice() noexcept;
- Slice(const Slice<T> &) noexcept;
Slice(const T *, size_t count) noexcept;
- Slice &operator=(Slice<T>) noexcept;
+ Slice &operator=(const Slice<T> &) noexcept = default;
const T *data() const noexcept;
size_t size() const noexcept;
size_t length() const noexcept;
- // Repr is PRIVATE; must not be used other than by our generated code.
- //
- // At present this class is only used for &[u8] slices.
- // Not necessarily ABI compatible with &[u8]. Codegen will translate to
- // cxx::rust_sliceu8::RustSliceU8 which matches this layout.
- struct Repr {
- const T *ptr;
- size_t len;
- };
- Slice(Repr) noexcept;
- explicit operator Repr() noexcept;
+ // Important in order for System V ABI to pass in registers.
+ Slice(const Slice<T> &) noexcept = default;
+ ~Slice() noexcept = default;
private:
- Repr repr;
+ friend impl<Slice>;
+ // Not necessarily ABI compatible with &[T]. Codegen will translate to
+ // cxx::rust_sliceu8::RustSliceU8 which matches this layout.
+ const T *ptr;
+ size_t len;
};
-#endif // CXXBRIDGE03_RUST_SLICE
+#endif // CXXBRIDGE05_RUST_SLICE
-#ifndef CXXBRIDGE03_RUST_BOX
+#ifndef CXXBRIDGE05_RUST_BOX
template <typename T>
class Box final {
public:
@@ -157,9 +152,9 @@
void drop() noexcept;
T *ptr;
};
-#endif // CXXBRIDGE03_RUST_BOX
+#endif // CXXBRIDGE05_RUST_BOX
-#ifndef CXXBRIDGE03_RUST_VEC
+#ifndef CXXBRIDGE05_RUST_VEC
template <typename T>
class Vec final {
public:
@@ -174,8 +169,21 @@
size_t size() const noexcept;
bool empty() const noexcept;
const T *data() const noexcept;
+ T *data() noexcept;
- class const_iterator {
+ const T &operator[](size_t n) const noexcept;
+ const T &at(size_t n) const;
+
+ const T &front() const;
+ const T &back() const;
+
+ void reserve(size_t new_cap);
+ void push_back(const T &value);
+ void push_back(T &&value);
+ template <class... Args>
+ void emplace_back(Args &&... args);
+
+ class const_iterator final {
public:
using difference_type = ptrdiff_t;
using value_type = typename std::add_const<T>::type;
@@ -206,20 +214,21 @@
private:
static size_t stride() noexcept;
+ void reserve_total(size_t cap) noexcept;
+ void set_len(size_t len) noexcept;
void drop() noexcept;
// Size and alignment statically verified by rust_vec.rs.
std::array<uintptr_t, 3> repr;
};
-#endif // CXXBRIDGE03_RUST_VEC
+#endif // CXXBRIDGE05_RUST_VEC
-#ifndef CXXBRIDGE03_RUST_FN
-#define CXXBRIDGE03_RUST_FN
+#ifndef CXXBRIDGE05_RUST_FN
template <typename Signature, bool Throws = false>
class Fn;
template <typename Ret, typename... Args, bool Throws>
-class Fn<Ret(Args...), Throws> {
+class Fn<Ret(Args...), Throws> final {
public:
Ret operator()(Args... args) const noexcept(!Throws);
Fn operator*() const noexcept;
@@ -231,51 +240,92 @@
template <typename Signature>
using TryFn = Fn<Signature, true>;
-#endif // CXXBRIDGE03_RUST_FN
+#endif // CXXBRIDGE05_RUST_FN
-#ifndef CXXBRIDGE03_RUST_ERROR
-#define CXXBRIDGE03_RUST_ERROR
-class Error final : std::exception {
+#ifndef CXXBRIDGE05_RUST_ERROR
+#define CXXBRIDGE05_RUST_ERROR
+class Error final : public std::exception {
public:
Error(const Error &);
Error(Error &&) noexcept;
- Error(Str::Repr) noexcept;
~Error() noexcept;
+
+ Error &operator=(const Error &);
+ Error &operator=(Error &&) noexcept;
+
const char *what() const noexcept override;
private:
- Str::Repr msg;
+ Error() noexcept = default;
+ friend impl<Error>;
+ const char *msg;
+ size_t len;
};
-#endif // CXXBRIDGE03_RUST_ERROR
+#endif // CXXBRIDGE05_RUST_ERROR
-#ifndef CXXBRIDGE03_RUST_ISIZE
-#define CXXBRIDGE03_RUST_ISIZE
+#ifndef CXXBRIDGE05_RUST_ISIZE
+#define CXXBRIDGE05_RUST_ISIZE
#if defined(_WIN32)
using isize = SSIZE_T;
#else
using isize = ssize_t;
#endif
-#endif // CXXBRIDGE03_RUST_ISIZE
+#endif // CXXBRIDGE05_RUST_ISIZE
std::ostream &operator<<(std::ostream &, const String &);
std::ostream &operator<<(std::ostream &, const Str &);
+// 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;
+
// Snake case aliases for use in code that uses this style for type names.
using string = String;
using str = Str;
template <class T>
+using slice = Slice<T>;
+template <class T>
using box = Box<T>;
+template <class T>
+using vec = Vec<T>;
using error = Error;
template <typename Signature, bool Throws = false>
using fn = Fn<Signature, Throws>;
template <typename Signature>
using try_fn = TryFn<Signature>;
+template <typename T>
+using is_relocatable = IsRelocatable<T>;
////////////////////////////////////////////////////////////////////////////////
/// end public API, begin implementation details
+#ifndef CXXBRIDGE05_PANIC
+#define CXXBRIDGE05_PANIC
+template <typename Exception>
+void panic [[noreturn]] (const char *msg);
+#endif // CXXBRIDGE05_PANIC
+
+#ifndef CXXBRIDGE05_RUST_FN
+#define CXXBRIDGE05_RUST_FN
template <typename Ret, typename... Args, bool Throws>
Ret Fn<Ret(Args...), Throws>::operator()(Args... args) const noexcept(!Throws) {
return (*this->trampoline)(std::move(args)..., this->fn);
@@ -285,59 +335,52 @@
Fn<Ret(Args...), Throws> Fn<Ret(Args...), Throws>::operator*() const noexcept {
return *this;
}
+#endif // CXXBRIDGE05_RUST_FN
-#ifndef CXXBRIDGE03_RUST_BITCOPY
-#define CXXBRIDGE03_RUST_BITCOPY
-struct unsafe_bitcopy_t {
+#ifndef CXXBRIDGE05_RUST_BITCOPY
+#define CXXBRIDGE05_RUST_BITCOPY
+struct unsafe_bitcopy_t final {
explicit unsafe_bitcopy_t() = default;
};
constexpr unsafe_bitcopy_t unsafe_bitcopy{};
-#endif // CXXBRIDGE03_RUST_BITCOPY
+#endif // CXXBRIDGE05_RUST_BITCOPY
-#ifndef CXXBRIDGE03_RUST_SLICE
-#define CXXBRIDGE03_RUST_SLICE
+#ifndef CXXBRIDGE05_RUST_STR
+#define CXXBRIDGE05_RUST_STR
+inline const char *Str::data() const noexcept { return this->ptr; }
+
+inline size_t Str::size() const noexcept { return this->len; }
+
+inline size_t Str::length() const noexcept { return this->len; }
+#endif // CXXBRIDGE05_RUST_STR
+
+#ifndef CXXBRIDGE05_RUST_SLICE
+#define CXXBRIDGE05_RUST_SLICE
template <typename T>
-Slice<T>::Slice() noexcept : repr(Repr{reinterpret_cast<const T *>(this), 0}) {}
+Slice<T>::Slice() noexcept : ptr(reinterpret_cast<const T *>(this)), len(0) {}
template <typename T>
-Slice<T>::Slice(const Slice<T> &) noexcept = default;
-
-template <typename T>
-Slice<T>::Slice(const T *s, size_t count) noexcept : repr(Repr{s, count}) {}
-
-template <typename T>
-Slice<T> &Slice<T>::operator=(Slice<T> other) noexcept {
- this->repr = other.repr;
- return *this;
-}
+Slice<T>::Slice(const T *s, size_t count) noexcept : ptr(s), len(count) {}
template <typename T>
const T *Slice<T>::data() const noexcept {
- return this->repr.ptr;
+ return this->ptr;
}
template <typename T>
size_t Slice<T>::size() const noexcept {
- return this->repr.len;
+ return this->len;
}
template <typename T>
size_t Slice<T>::length() const noexcept {
- return this->repr.len;
+ return this->len;
}
+#endif // CXXBRIDGE05_RUST_SLICE
-template <typename T>
-Slice<T>::Slice(Repr repr_) noexcept : repr(repr_) {}
-
-template <typename T>
-Slice<T>::operator Repr() noexcept {
- return this->repr;
-}
-#endif // CXXBRIDGE03_RUST_SLICE
-
-#ifndef CXXBRIDGE03_RUST_BOX
-#define CXXBRIDGE03_RUST_BOX
+#ifndef CXXBRIDGE05_RUST_BOX
+#define CXXBRIDGE05_RUST_BOX
template <typename T>
Box<T>::Box(const Box &other) : Box(*other) {}
@@ -433,10 +476,10 @@
template <typename T>
Box<T>::Box() noexcept {}
-#endif // CXXBRIDGE03_RUST_BOX
+#endif // CXXBRIDGE05_RUST_BOX
-#ifndef CXXBRIDGE03_RUST_VEC
-#define CXXBRIDGE03_RUST_VEC
+#ifndef CXXBRIDGE05_RUST_VEC
+#define CXXBRIDGE05_RUST_VEC
template <typename T>
Vec<T>::Vec(Vec &&other) noexcept {
this->repr = other.repr;
@@ -464,6 +507,61 @@
}
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[](size_t n) const noexcept {
+ auto data = reinterpret_cast<const char *>(this->data());
+ return *reinterpret_cast<const T *>(data + n * this->stride());
+}
+
+template <typename T>
+const T &Vec<T>::at(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 {
+ return (*this)[0];
+}
+
+template <typename T>
+const T &Vec<T>::back() const {
+ return (*this)[this->size() - 1];
+}
+
+template <typename T>
+void Vec<T>::reserve(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 * this->stride()))
+ T(std::forward<Args>(args)...);
+ this->set_len(size + 1);
+}
+
+template <typename T>
const T &Vec<T>::const_iterator::operator*() const noexcept {
return *static_cast<const T *>(this->pos);
}
@@ -488,14 +586,14 @@
}
template <typename T>
-bool Vec<T>::const_iterator::operator==(const const_iterator &other) const
- noexcept {
+bool Vec<T>::const_iterator::operator==(
+ const const_iterator &other) const noexcept {
return this->pos == other.pos;
}
template <typename T>
-bool Vec<T>::const_iterator::operator!=(const const_iterator &other) const
- noexcept {
+bool Vec<T>::const_iterator::operator!=(
+ const const_iterator &other) const noexcept {
return this->pos != other.pos;
}
@@ -517,7 +615,44 @@
// 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 // CXXBRIDGE03_RUST_VEC
+#endif // CXXBRIDGE05_RUST_VEC
-} // namespace cxxbridge03
+#ifndef CXXBRIDGE05_RELOCATABLE
+#define CXXBRIDGE05_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 // CXXBRIDGE05_RELOCATABLE
+
+} // namespace cxxbridge05
} // namespace rust
diff --git a/macro/Cargo.toml b/macro/Cargo.toml
index 21c4318..6b07a07 100644
--- a/macro/Cargo.toml
+++ b/macro/Cargo.toml
@@ -1,12 +1,12 @@
[package]
name = "cxxbridge-macro"
-version = "0.3.4"
+version = "0.5.9"
authors = ["David Tolnay <dtolnay@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
description = "Implementation detail of the `cxx` crate."
repository = "https://github.com/dtolnay/cxx"
-exclude = ["README.md"]
+exclude = ["build.rs", "README.md"]
keywords = ["ffi"]
categories = ["development-tools::ffi"]
@@ -19,7 +19,7 @@
syn = { version = "1.0.20", features = ["full"] }
[dev-dependencies]
-cxx = { version = "0.3", path = ".." }
+cxx = { version = "0.5", path = ".." }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
diff --git a/macro/build.rs b/macro/build.rs
new file mode 100644
index 0000000..927f72b
--- /dev/null
+++ b/macro/build.rs
@@ -0,0 +1 @@
+include!("../tools/cargo/build.rs");
diff --git a/macro/src/derive.rs b/macro/src/derive.rs
new file mode 100644
index 0000000..1abc5da
--- /dev/null
+++ b/macro/src/derive.rs
@@ -0,0 +1,14 @@
+use crate::syntax::Derive;
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+
+pub struct DeriveAttribute<'a>(pub &'a [Derive]);
+
+impl<'a> ToTokens for DeriveAttribute<'a> {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ if !self.0.is_empty() {
+ let derives = self.0;
+ tokens.extend(quote!(#[derive(#(#derives),*)]));
+ }
+ }
+}
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index f28d955..903585e 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -1,93 +1,95 @@
+use crate::derive::DeriveAttribute;
use crate::syntax::atom::Atom::{self, *};
-use crate::syntax::namespace::Namespace;
+use crate::syntax::file::Module;
use crate::syntax::report::Errors;
use crate::syntax::symbol::Symbol;
use crate::syntax::{
- self, check, mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, TypeAlias, Types,
+ self, check, mangle, Api, Enum, ExternFn, ExternType, Impl, Pair, ResolvableName, Signature,
+ Struct, Type, TypeAlias, Types,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
-use syn::{parse_quote, Error, ItemMod, Result, Token};
+use std::mem;
+use syn::{parse_quote, Result, Token};
-pub fn bridge(namespace: &Namespace, mut ffi: ItemMod) -> Result<TokenStream> {
+pub fn bridge(mut ffi: Module) -> Result<TokenStream> {
let ref mut errors = Errors::new();
- let content = ffi.content.take().ok_or(Error::new(
- Span::call_site(),
- "#[cxx::bridge] module must have inline contents",
- ))?;
- let ref apis = syntax::parse_items(errors, content.1);
+ let content = mem::take(&mut ffi.content);
+ let trusted = ffi.unsafety.is_some();
+ let namespace = &ffi.namespace;
+ let ref apis = syntax::parse_items(errors, content, trusted, namespace);
let ref types = Types::collect(errors, apis);
errors.propagate()?;
- check::typecheck(errors, namespace, apis, types);
+ check::typecheck(errors, apis, types);
errors.propagate()?;
- Ok(expand(namespace, ffi, apis, types))
+ Ok(expand(ffi, apis, types))
}
-fn expand(namespace: &Namespace, ffi: ItemMod, apis: &[Api], types: &Types) -> TokenStream {
+fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
let mut expanded = TokenStream::new();
let mut hidden = TokenStream::new();
for api in apis {
if let Api::RustType(ety) = api {
expanded.extend(expand_rust_type(ety));
- let ident = &ety.ident;
- let span = ident.span();
- hidden.extend(quote_spanned! {span=>
- let _ = ::std::ptr::read::<#ident>;
- });
+ hidden.extend(expand_rust_type_assert_sized(ety));
}
}
for api in apis {
match api {
- Api::Include(_) | Api::RustType(_) => {}
+ Api::Include(_) | Api::RustType(_) | Api::Impl(_) => {}
Api::Struct(strct) => expanded.extend(expand_struct(strct)),
Api::Enum(enm) => expanded.extend(expand_enum(enm)),
Api::CxxType(ety) => {
- if !types.enums.contains_key(&ety.ident) {
- expanded.extend(expand_cxx_type(namespace, ety));
+ let ident = &ety.name.rust;
+ if !types.structs.contains_key(ident) && !types.enums.contains_key(ident) {
+ expanded.extend(expand_cxx_type(ety));
}
}
Api::CxxFunction(efn) => {
- expanded.extend(expand_cxx_function_shim(namespace, efn, types));
+ expanded.extend(expand_cxx_function_shim(efn, types));
}
- Api::RustFunction(efn) => {
- hidden.extend(expand_rust_function_shim(namespace, efn, types))
- }
+ Api::RustFunction(efn) => hidden.extend(expand_rust_function_shim(efn, types)),
Api::TypeAlias(alias) => {
expanded.extend(expand_type_alias(alias));
- hidden.extend(expand_type_alias_verify(namespace, alias));
+ hidden.extend(expand_type_alias_verify(alias, types));
}
}
}
for ty in types {
+ let explicit_impl = types.explicit_impls.get(ty);
if let Type::RustBox(ty) = ty {
if let Type::Ident(ident) = &ty.inner {
- if Atom::from(ident).is_none() {
- hidden.extend(expand_rust_box(namespace, ident));
+ if Atom::from(&ident.rust).is_none() {
+ hidden.extend(expand_rust_box(ident, types));
}
}
} else if let Type::RustVec(ty) = ty {
if let Type::Ident(ident) = &ty.inner {
- if Atom::from(ident).is_none() {
- hidden.extend(expand_rust_vec(namespace, ident));
+ if Atom::from(&ident.rust).is_none() {
+ hidden.extend(expand_rust_vec(ident, types));
}
}
} else if let Type::UniquePtr(ptr) = ty {
if let Type::Ident(ident) = &ptr.inner {
- if Atom::from(ident).is_none() && !types.aliases.contains_key(ident) {
- expanded.extend(expand_unique_ptr(namespace, ident, types));
+ if Atom::from(&ident.rust).is_none()
+ && (explicit_impl.is_some() || !types.aliases.contains_key(&ident.rust))
+ {
+ expanded.extend(expand_unique_ptr(ident, types, explicit_impl));
}
}
} else if let Type::CxxVector(ptr) = ty {
if let Type::Ident(ident) = &ptr.inner {
- if Atom::from(ident).is_none() && !types.aliases.contains_key(ident) {
+ if Atom::from(&ident.rust).is_none()
+ && (explicit_impl.is_some() || !types.aliases.contains_key(&ident.rust))
+ {
// Generate impl for CxxVector<T> if T is a struct or opaque
// C++ type. Impl for primitives is already provided by cxx
// crate.
- expanded.extend(expand_cxx_vector(namespace, ident));
+ expanded.extend(expand_cxx_vector(ident, explicit_impl, types));
}
}
}
@@ -121,29 +123,37 @@
}
fn expand_struct(strct: &Struct) -> TokenStream {
- let ident = &strct.ident;
+ let ident = &strct.name.rust;
let doc = &strct.doc;
- let derives = &strct.derives;
+ let derives = DeriveAttribute(&strct.derives);
+ let type_id = type_id(&strct.name);
let fields = strct.fields.iter().map(|field| {
// This span on the pub makes "private type in public interface" errors
// appear in the right place.
let vis = Token![pub](field.ident.span());
quote!(#vis #field)
});
+
quote! {
#doc
- #[derive(#(#derives),*)]
+ #derives
#[repr(C)]
pub struct #ident {
#(#fields,)*
}
+
+ unsafe impl ::cxx::ExternType for #ident {
+ type Id = #type_id;
+ type Kind = ::cxx::kind::Trivial;
+ }
}
}
fn expand_enum(enm: &Enum) -> TokenStream {
- let ident = &enm.ident;
+ let ident = &enm.name.rust;
let doc = &enm.doc;
let repr = enm.repr;
+ let type_id = type_id(&enm.name);
let variants = enm.variants.iter().map(|variant| {
let variant_ident = &variant.ident;
let discriminant = &variant.discriminant;
@@ -151,6 +161,7 @@
pub const #variant_ident: Self = #ident { repr: #discriminant };
})
});
+
quote! {
#doc
#[derive(Copy, Clone, PartialEq, Eq)]
@@ -163,13 +174,18 @@
impl #ident {
#(#variants)*
}
+
+ unsafe impl ::cxx::ExternType for #ident {
+ type Id = #type_id;
+ type Kind = ::cxx::kind::Trivial;
+ }
}
}
-fn expand_cxx_type(namespace: &Namespace, ety: &ExternType) -> TokenStream {
- let ident = &ety.ident;
+fn expand_cxx_type(ety: &ExternType) -> TokenStream {
+ let ident = &ety.name.rust;
let doc = &ety.doc;
- let type_id = type_id(namespace, ident);
+ let type_id = type_id(&ety.name);
quote! {
#doc
@@ -180,19 +196,19 @@
unsafe impl ::cxx::ExternType for #ident {
type Id = #type_id;
+ type Kind = ::cxx::kind::Opaque;
}
}
}
-fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
- let ident = &efn.ident;
+fn expand_cxx_function_decl(efn: &ExternFn, types: &Types) -> TokenStream {
let receiver = efn.receiver.iter().map(|receiver| {
let receiver_type = receiver.ty();
quote!(_: #receiver_type)
});
let args = efn.args.iter().map(|arg| {
let ident = &arg.ident;
- let ty = expand_extern_type(&arg.ty);
+ let ty = expand_extern_type(&arg.ty, types, true);
if arg.ty == RustString {
quote!(#ident: *const #ty)
} else if let Type::RustVec(_) = arg.ty {
@@ -209,25 +225,24 @@
let ret = if efn.throws {
quote!(-> ::cxx::private::Result)
} else {
- expand_extern_return_type(&efn.ret, types)
+ expand_extern_return_type(&efn.ret, types, true)
};
let mut outparam = None;
if indirect_return(efn, types) {
- let ret = expand_extern_type(efn.ret.as_ref().unwrap());
+ let ret = expand_extern_type(efn.ret.as_ref().unwrap(), types, true);
outparam = Some(quote!(__return: *mut #ret));
}
- let link_name = mangle::extern_fn(namespace, efn);
- let local_name = format_ident!("__{}", ident);
+ let link_name = mangle::extern_fn(efn, types);
+ let local_name = format_ident!("__{}", efn.name.rust);
quote! {
#[link_name = #link_name]
fn #local_name(#(#all_args,)* #outparam) #ret;
}
}
-fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
- let ident = &efn.ident;
+fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream {
let doc = &efn.doc;
- let decl = expand_cxx_function_decl(namespace, efn, types);
+ let decl = expand_cxx_function_decl(efn, types);
let receiver = efn.receiver.iter().map(|receiver| {
let ampersand = receiver.ampersand;
let mutability = receiver.mutability;
@@ -253,17 +268,29 @@
let arg_vars = efn.args.iter().map(|arg| {
let var = &arg.ident;
match &arg.ty {
- Type::Ident(ident) if ident == RustString => {
+ Type::Ident(ident) if ident.rust == RustString => {
quote!(#var.as_mut_ptr() as *const ::cxx::private::RustString)
}
Type::RustBox(_) => quote!(::std::boxed::Box::into_raw(#var)),
Type::UniquePtr(_) => quote!(::cxx::UniquePtr::into_raw(#var)),
Type::RustVec(_) => quote!(#var.as_mut_ptr() as *const ::cxx::private::RustVec<_>),
Type::Ref(ty) => match &ty.inner {
- Type::Ident(ident) if ident == RustString => {
- quote!(::cxx::private::RustString::from_ref(#var))
- }
- Type::RustVec(_) => quote!(::cxx::private::RustVec::from_ref(#var)),
+ Type::Ident(ident) if ident.rust == RustString => match ty.mutability {
+ None => quote!(::cxx::private::RustString::from_ref(#var)),
+ Some(_) => quote!(::cxx::private::RustString::from_mut(#var)),
+ },
+ Type::RustVec(vec) if vec.inner == RustString => match ty.mutability {
+ None => quote!(::cxx::private::RustVec::from_ref_vec_string(#var)),
+ Some(_) => quote!(::cxx::private::RustVec::from_mut_vec_string(#var)),
+ },
+ Type::RustVec(_) => match ty.mutability {
+ None => quote!(::cxx::private::RustVec::from_ref(#var)),
+ Some(_) => quote!(::cxx::private::RustVec::from_mut(#var)),
+ },
+ inner if types.is_considered_improper_ctype(inner) => match ty.mutability {
+ None => quote!(#var as *const #inner as *const ::std::ffi::c_void),
+ Some(_) => quote!(#var as *mut #inner as *mut ::std::ffi::c_void),
+ },
_ => quote!(#var),
},
Type::Str(_) => quote!(::cxx::private::RustStr::from(#var)),
@@ -279,9 +306,7 @@
.filter_map(|arg| {
if let Type::Fn(f) = &arg.ty {
let var = &arg.ident;
- Some(expand_function_pointer_trampoline(
- namespace, efn, var, f, types,
- ))
+ Some(expand_function_pointer_trampoline(efn, var, f, types))
} else {
None
}
@@ -300,23 +325,22 @@
}
})
.collect::<TokenStream>();
- let local_name = format_ident!("__{}", ident);
+ let local_name = format_ident!("__{}", efn.name.rust);
let call = if indirect_return {
- let ret = expand_extern_type(efn.ret.as_ref().unwrap());
+ let ret = expand_extern_type(efn.ret.as_ref().unwrap(), types, true);
setup.extend(quote! {
let mut __return = ::std::mem::MaybeUninit::<#ret>::uninit();
});
- if efn.throws {
- setup.extend(quote! {
+ setup.extend(if efn.throws {
+ quote! {
#local_name(#(#vars,)* __return.as_mut_ptr()).exception()?;
- });
- quote!(::std::result::Result::Ok(__return.assume_init()))
+ }
} else {
- setup.extend(quote! {
+ quote! {
#local_name(#(#vars,)* __return.as_mut_ptr());
- });
- quote!(__return.assume_init())
- }
+ }
+ });
+ quote!(__return.assume_init())
} else if efn.throws {
quote! {
#local_name(#(#vars),*).exception()
@@ -326,53 +350,65 @@
#local_name(#(#vars),*)
}
};
- let expr = if efn.throws {
- efn.ret.as_ref().and_then(|ret| match ret {
- Type::Ident(ident) if ident == RustString => {
- Some(quote!(#call.map(|r| r.into_string())))
- }
- Type::RustBox(_) => Some(quote!(#call.map(|r| ::std::boxed::Box::from_raw(r)))),
- Type::RustVec(_) => Some(quote!(#call.map(|r| r.into_vec()))),
- Type::UniquePtr(_) => Some(quote!(#call.map(|r| ::cxx::UniquePtr::from_raw(r)))),
- Type::Ref(ty) => match &ty.inner {
- Type::Ident(ident) if ident == RustString => {
- Some(quote!(#call.map(|r| r.as_string())))
- }
- Type::RustVec(_) => Some(quote!(#call.map(|r| r.as_vec()))),
- _ => None,
- },
- Type::Str(_) => Some(quote!(#call.map(|r| r.as_str()))),
- Type::SliceRefU8(_) => Some(quote!(#call.map(|r| r.as_slice()))),
- _ => None,
- })
+ let mut expr;
+ if efn.throws && efn.sig.ret.is_none() {
+ expr = call;
} else {
- efn.ret.as_ref().and_then(|ret| match ret {
- Type::Ident(ident) if ident == RustString => Some(quote!(#call.into_string())),
- Type::RustBox(_) => Some(quote!(::std::boxed::Box::from_raw(#call))),
- Type::RustVec(_) => Some(quote!(#call.into_vec())),
- Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::from_raw(#call))),
- Type::Ref(ty) => match &ty.inner {
- Type::Ident(ident) if ident == RustString => Some(quote!(#call.as_string())),
- Type::RustVec(_) => Some(quote!(#call.as_vec())),
- _ => None,
+ expr = match &efn.ret {
+ None => call,
+ Some(ret) => match ret {
+ Type::Ident(ident) if ident.rust == RustString => quote!(#call.into_string()),
+ Type::RustBox(_) => quote!(::std::boxed::Box::from_raw(#call)),
+ Type::RustVec(vec) => {
+ if vec.inner == RustString {
+ quote!(#call.into_vec_string())
+ } else {
+ quote!(#call.into_vec())
+ }
+ }
+ Type::UniquePtr(_) => quote!(::cxx::UniquePtr::from_raw(#call)),
+ Type::Ref(ty) => match &ty.inner {
+ Type::Ident(ident) if ident.rust == RustString => match ty.mutability {
+ None => quote!(#call.as_string()),
+ Some(_) => quote!(#call.as_mut_string()),
+ },
+ Type::RustVec(vec) if vec.inner == RustString => match ty.mutability {
+ None => quote!(#call.as_vec_string()),
+ Some(_) => quote!(#call.as_mut_vec_string()),
+ },
+ Type::RustVec(_) => match ty.mutability {
+ None => quote!(#call.as_vec()),
+ Some(_) => quote!(#call.as_mut_vec()),
+ },
+ inner if types.is_considered_improper_ctype(inner) => {
+ let mutability = ty.mutability;
+ quote!(&#mutability *#call.cast())
+ }
+ _ => call,
+ },
+ Type::Str(_) => quote!(#call.as_str()),
+ Type::SliceRefU8(_) => quote!(#call.as_slice()),
+ _ => call,
},
- Type::Str(_) => Some(quote!(#call.as_str())),
- Type::SliceRefU8(_) => Some(quote!(#call.as_slice())),
- _ => None,
- })
+ };
+ if efn.throws {
+ expr = quote!(::std::result::Result::Ok(#expr));
+ }
+ };
+ let mut dispatch = quote!(#setup #expr);
+ let unsafety = &efn.sig.unsafety;
+ if unsafety.is_none() {
+ dispatch = quote!(unsafe { #dispatch });
}
- .unwrap_or(call);
+ let ident = &efn.name.rust;
let function_shim = quote! {
#doc
- pub fn #ident(#(#all_args,)*) #ret {
+ pub #unsafety fn #ident(#(#all_args,)*) #ret {
extern "C" {
#decl
}
#trampolines
- unsafe {
- #setup
- #expr
- }
+ #dispatch
}
};
match &efn.receiver {
@@ -385,16 +421,15 @@
}
fn expand_function_pointer_trampoline(
- namespace: &Namespace,
efn: &ExternFn,
var: &Ident,
sig: &Signature,
types: &Types,
) -> TokenStream {
- let c_trampoline = mangle::c_trampoline(namespace, efn, var);
- let r_trampoline = mangle::r_trampoline(namespace, efn, var);
+ let c_trampoline = mangle::c_trampoline(efn, var, types);
+ let r_trampoline = mangle::r_trampoline(efn, var, types);
let local_name = parse_quote!(__);
- let catch_unwind_label = format!("::{}::{}", efn.ident, var);
+ let catch_unwind_label = format!("::{}::{}", efn.name.rust, var);
let shim = expand_rust_function_shim_impl(
sig,
types,
@@ -420,18 +455,39 @@
}
fn expand_rust_type(ety: &ExternType) -> TokenStream {
- let ident = &ety.ident;
+ let ident = &ety.name.rust;
quote! {
use super::#ident;
}
}
-fn expand_rust_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
- let ident = &efn.ident;
- let link_name = mangle::extern_fn(namespace, efn);
- let local_name = format_ident!("__{}", ident);
- let catch_unwind_label = format!("::{}", ident);
- let invoke = Some(ident);
+fn expand_rust_type_assert_sized(ety: &ExternType) -> TokenStream {
+ // Rustc will render as follows if not sized:
+ //
+ // type TheirType;
+ // -----^^^^^^^^^-
+ // | |
+ // | doesn't have a size known at compile-time
+ // required by this bound in `ffi::_::__AssertSized`
+
+ let ident = &ety.name.rust;
+ let begin_span = Token![::](ety.type_token.span);
+ let sized = quote_spanned! {ety.semi_token.span=>
+ #begin_span std::marker::Sized
+ };
+ quote_spanned! {ident.span()=>
+ let _ = {
+ fn __AssertSized<T: ?#sized + #sized>() {}
+ __AssertSized::<#ident>
+ };
+ }
+}
+
+fn expand_rust_function_shim(efn: &ExternFn, types: &Types) -> TokenStream {
+ let link_name = mangle::extern_fn(efn, types);
+ let local_name = format_ident!("__{}", efn.name.rust);
+ let catch_unwind_label = format!("::{}", efn.name.rust);
+ let invoke = Some(&efn.name.rust);
expand_rust_function_shim_impl(
efn,
types,
@@ -460,7 +516,7 @@
});
let args = sig.args.iter().map(|arg| {
let ident = &arg.ident;
- let ty = expand_extern_type(&arg.ty);
+ let ty = expand_extern_type(&arg.ty, types, false);
if types.needs_indirect_abi(&arg.ty) {
quote!(#ident: *mut #ty)
} else {
@@ -472,15 +528,31 @@
let arg_vars = sig.args.iter().map(|arg| {
let ident = &arg.ident;
match &arg.ty {
- Type::Ident(i) if i == RustString => {
+ Type::Ident(i) if i.rust == RustString => {
quote!(::std::mem::take((*#ident).as_mut_string()))
}
Type::RustBox(_) => quote!(::std::boxed::Box::from_raw(#ident)),
- Type::RustVec(_) => quote!(::std::mem::take((*#ident).as_mut_vec())),
+ Type::RustVec(vec) => {
+ if vec.inner == RustString {
+ quote!(::std::mem::take((*#ident).as_mut_vec_string()))
+ } else {
+ quote!(::std::mem::take((*#ident).as_mut_vec()))
+ }
+ }
Type::UniquePtr(_) => quote!(::cxx::UniquePtr::from_raw(#ident)),
Type::Ref(ty) => match &ty.inner {
- Type::Ident(i) if i == RustString => quote!(#ident.as_string()),
- Type::RustVec(_) => quote!(#ident.as_vec()),
+ Type::Ident(i) if i.rust == RustString => match ty.mutability {
+ None => quote!(#ident.as_string()),
+ Some(_) => quote!(#ident.as_mut_string()),
+ },
+ Type::RustVec(vec) if vec.inner == RustString => match ty.mutability {
+ None => quote!(#ident.as_vec_string()),
+ Some(_) => quote!(#ident.as_mut_vec_string()),
+ },
+ Type::RustVec(_) => match ty.mutability {
+ None => quote!(#ident.as_vec()),
+ Some(_) => quote!(#ident.as_mut_vec()),
+ },
_ => quote!(#ident),
},
Type::Str(_) => quote!(#ident.as_str()),
@@ -491,45 +563,57 @@
});
let vars = receiver_var.into_iter().chain(arg_vars);
+ let wrap_super = invoke.map(|invoke| expand_rust_function_shim_super(sig, &local_name, invoke));
+
let mut call = match invoke {
- Some(ident) => match &sig.receiver {
- None => quote!(super::#ident),
- Some(receiver) => {
- let receiver_type = &receiver.ty;
- quote!(#receiver_type::#ident)
- }
- },
- None => quote!(__extern),
+ Some(_) => quote!(#local_name),
+ None => quote!(::std::mem::transmute::<*const (), #sig>(__extern)),
};
call.extend(quote! { (#(#vars),*) });
- let mut expr = sig
- .ret
- .as_ref()
- .and_then(|ret| match ret {
- Type::Ident(ident) if ident == RustString => {
- Some(quote!(::cxx::private::RustString::from(#call)))
+ let conversion = sig.ret.as_ref().and_then(|ret| match ret {
+ Type::Ident(ident) if ident.rust == RustString => {
+ Some(quote!(::cxx::private::RustString::from))
+ }
+ Type::RustBox(_) => Some(quote!(::std::boxed::Box::into_raw)),
+ Type::RustVec(vec) => {
+ if vec.inner == RustString {
+ Some(quote!(::cxx::private::RustVec::from_vec_string))
+ } else {
+ Some(quote!(::cxx::private::RustVec::from))
}
- Type::RustBox(_) => Some(quote!(::std::boxed::Box::into_raw(#call))),
- Type::RustVec(_) => Some(quote!(::cxx::private::RustVec::from(#call))),
- Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::into_raw(#call))),
- Type::Ref(ty) => match &ty.inner {
- Type::Ident(ident) if ident == RustString => {
- Some(quote!(::cxx::private::RustString::from_ref(#call)))
- }
- Type::RustVec(_) => Some(quote!(::cxx::private::RustVec::from_ref(#call))),
- _ => None,
+ }
+ Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::into_raw)),
+ Type::Ref(ty) => match &ty.inner {
+ Type::Ident(ident) if ident.rust == RustString => match ty.mutability {
+ None => Some(quote!(::cxx::private::RustString::from_ref)),
+ Some(_) => Some(quote!(::cxx::private::RustString::from_mut)),
},
- Type::Str(_) => Some(quote!(::cxx::private::RustStr::from(#call))),
- Type::SliceRefU8(_) => Some(quote!(::cxx::private::RustSliceU8::from(#call))),
+ Type::RustVec(vec) if vec.inner == RustString => match ty.mutability {
+ None => Some(quote!(::cxx::private::RustVec::from_ref_vec_string)),
+ Some(_) => Some(quote!(::cxx::private::RustVec::from_mut_vec_string)),
+ },
+ Type::RustVec(_) => match ty.mutability {
+ None => Some(quote!(::cxx::private::RustVec::from_ref)),
+ Some(_) => Some(quote!(::cxx::private::RustVec::from_mut)),
+ },
_ => None,
- })
- .unwrap_or(call);
+ },
+ Type::Str(_) => Some(quote!(::cxx::private::RustStr::from)),
+ Type::SliceRefU8(_) => Some(quote!(::cxx::private::RustSliceU8::from)),
+ _ => None,
+ });
+
+ let mut expr = match conversion {
+ None => call,
+ Some(conversion) if !sig.throws => quote!(#conversion(#call)),
+ Some(conversion) => quote!(::std::result::Result::map(#call, #conversion)),
+ };
let mut outparam = None;
let indirect_return = indirect_return(sig, types);
if indirect_return {
- let ret = expand_extern_type(sig.ret.as_ref().unwrap());
+ let ret = expand_extern_type(sig.ret.as_ref().unwrap(), types, false);
outparam = Some(quote!(__return: *mut #ret,));
}
if sig.throws {
@@ -547,11 +631,11 @@
let ret = if sig.throws {
quote!(-> ::cxx::private::Result)
} else {
- expand_extern_return_type(&sig.ret, types)
+ expand_extern_return_type(&sig.ret, types, false)
};
let pointer = match invoke {
- None => Some(quote!(__extern: #sig)),
+ None => Some(quote!(__extern: *const ())),
Some(_) => None,
};
@@ -560,51 +644,108 @@
#[export_name = #link_name]
unsafe extern "C" fn #local_name(#(#all_args,)* #outparam #pointer) #ret {
let __fn = concat!(module_path!(), #catch_unwind_label);
+ #wrap_super
#expr
}
}
}
+// A wrapper like `fn f(x: Arg) { super::f(x) }` just to ensure we have the
+// accurate unsafety declaration and no problematic elided lifetimes.
+fn expand_rust_function_shim_super(
+ sig: &Signature,
+ local_name: &Ident,
+ invoke: &Ident,
+) -> TokenStream {
+ let unsafety = sig.unsafety;
+
+ let receiver_var = sig
+ .receiver
+ .as_ref()
+ .map(|receiver| Ident::new("__self", receiver.var.span));
+ let receiver = sig.receiver.iter().map(|receiver| {
+ let receiver_type = receiver.ty();
+ quote!(#receiver_var: #receiver_type)
+ });
+ let args = sig.args.iter().map(|arg| quote!(#arg));
+ let all_args = receiver.chain(args);
+
+ let ret = if let Some((result, _langle, rangle)) = sig.throws_tokens {
+ let ok = match &sig.ret {
+ Some(ret) => quote!(#ret),
+ None => quote!(()),
+ };
+ let impl_trait = quote_spanned!(result.span=> impl);
+ let display = quote_spanned!(rangle.span=> ::std::fmt::Display);
+ quote!(-> ::std::result::Result<#ok, #impl_trait #display>)
+ } else {
+ expand_return_type(&sig.ret)
+ };
+
+ let arg_vars = sig.args.iter().map(|arg| &arg.ident);
+ let vars = receiver_var.iter().chain(arg_vars);
+
+ let span = invoke.span();
+ let call = match &sig.receiver {
+ None => quote_spanned!(span=> super::#invoke),
+ Some(receiver) => {
+ let receiver_type = &receiver.ty;
+ quote_spanned!(span=> #receiver_type::#invoke)
+ }
+ };
+
+ quote_spanned! {span=>
+ #unsafety fn #local_name(#(#all_args,)*) #ret {
+ #call(#(#vars,)*)
+ }
+ }
+}
+
fn expand_type_alias(alias: &TypeAlias) -> TokenStream {
- let ident = &alias.ident;
+ let doc = &alias.doc;
+ let ident = &alias.name.rust;
let ty = &alias.ty;
quote! {
+ #doc
pub type #ident = #ty;
}
}
-fn expand_type_alias_verify(namespace: &Namespace, alias: &TypeAlias) -> TokenStream {
- let ident = &alias.ident;
- let type_id = type_id(namespace, ident);
+fn expand_type_alias_verify(alias: &TypeAlias, types: &Types) -> TokenStream {
+ let ident = &alias.name.rust;
+ let type_id = type_id(&alias.name);
let begin_span = alias.type_token.span;
let end_span = alias.semi_token.span;
let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_type::<);
let end = quote_spanned!(end_span=> >);
- quote! {
+ let mut verify = quote! {
const _: fn() = #begin #ident, #type_id #end;
+ };
+
+ if types.required_trivial.contains_key(&alias.name.rust) {
+ let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_kind::<);
+ verify.extend(quote! {
+ const _: fn() = #begin #ident, ::cxx::kind::Trivial #end;
+ });
}
+
+ verify
}
-fn type_id(namespace: &Namespace, ident: &Ident) -> TokenStream {
- let mut path = String::new();
- for name in namespace {
- path += &name.to_string();
- path += "::";
- }
- path += &ident.to_string();
-
+fn type_id(name: &Pair) -> TokenStream {
+ let path = name.to_fully_qualified();
quote! {
::cxx::type_id!(#path)
}
}
-fn expand_rust_box(namespace: &Namespace, ident: &Ident) -> TokenStream {
- let link_prefix = format!("cxxbridge03$box${}{}$", namespace, ident);
+fn expand_rust_box(ident: &ResolvableName, types: &Types) -> TokenStream {
+ let link_prefix = format!("cxxbridge05$box${}$", types.resolve(ident).to_symbol());
let link_uninit = format!("{}uninit", link_prefix);
let link_drop = format!("{}drop", link_prefix);
- let local_prefix = format_ident!("{}__box_", ident);
+ let local_prefix = format_ident!("{}__box_", &ident.rust);
let local_uninit = format_ident!("{}uninit", local_prefix);
let local_drop = format_ident!("{}drop", local_prefix);
@@ -628,19 +769,23 @@
}
}
-fn expand_rust_vec(namespace: &Namespace, elem: &Ident) -> TokenStream {
- let link_prefix = format!("cxxbridge03$rust_vec${}{}$", namespace, elem);
+fn expand_rust_vec(elem: &ResolvableName, types: &Types) -> TokenStream {
+ let link_prefix = format!("cxxbridge05$rust_vec${}$", elem.to_symbol(types));
let link_new = format!("{}new", link_prefix);
let link_drop = format!("{}drop", link_prefix);
let link_len = format!("{}len", link_prefix);
let link_data = format!("{}data", link_prefix);
+ let link_reserve_total = format!("{}reserve_total", link_prefix);
+ let link_set_len = format!("{}set_len", link_prefix);
let link_stride = format!("{}stride", link_prefix);
- let local_prefix = format_ident!("{}__vec_", elem);
+ let local_prefix = format_ident!("{}__vec_", elem.rust);
let local_new = format_ident!("{}new", local_prefix);
let local_drop = format_ident!("{}drop", local_prefix);
let local_len = format_ident!("{}len", local_prefix);
let local_data = format_ident!("{}data", local_prefix);
+ let local_reserve_total = format_ident!("{}reserve_total", local_prefix);
+ let local_set_len = format_ident!("{}set_len", local_prefix);
let local_stride = format_ident!("{}stride", local_prefix);
let span = elem.span();
@@ -666,6 +811,16 @@
(*this).as_ptr()
}
#[doc(hidden)]
+ #[export_name = #link_reserve_total]
+ unsafe extern "C" fn #local_reserve_total(this: *mut ::cxx::private::RustVec<#elem>, cap: usize) {
+ (*this).reserve_total(cap);
+ }
+ #[doc(hidden)]
+ #[export_name = #link_set_len]
+ unsafe extern "C" fn #local_set_len(this: *mut ::cxx::private::RustVec<#elem>, len: usize) {
+ (*this).set_len(len);
+ }
+ #[doc(hidden)]
#[export_name = #link_stride]
unsafe extern "C" fn #local_stride() -> usize {
::std::mem::size_of::<#elem>()
@@ -673,9 +828,13 @@
}
}
-fn expand_unique_ptr(namespace: &Namespace, ident: &Ident, types: &Types) -> TokenStream {
- let name = ident.to_string();
- let prefix = format!("cxxbridge03$unique_ptr${}{}$", namespace, ident);
+fn expand_unique_ptr(
+ ident: &ResolvableName,
+ types: &Types,
+ explicit_impl: Option<&Impl>,
+) -> TokenStream {
+ let name = ident.rust.to_string();
+ let prefix = format!("cxxbridge05$unique_ptr${}$", ident.to_symbol(types));
let link_null = format!("{}null", prefix);
let link_new = format!("{}new", prefix);
let link_raw = format!("{}raw", prefix);
@@ -683,24 +842,30 @@
let link_release = format!("{}release", prefix);
let link_drop = format!("{}drop", prefix);
- let new_method = if types.structs.contains_key(ident) {
- Some(quote! {
- fn __new(mut value: Self) -> *mut ::std::ffi::c_void {
- extern "C" {
- #[link_name = #link_new]
- fn __new(this: *mut *mut ::std::ffi::c_void, value: *mut #ident);
+ let new_method =
+ if types.structs.contains_key(&ident.rust) || types.aliases.contains_key(&ident.rust) {
+ Some(quote! {
+ fn __new(mut value: Self) -> *mut ::std::ffi::c_void {
+ extern "C" {
+ #[link_name = #link_new]
+ fn __new(this: *mut *mut ::std::ffi::c_void, value: *mut #ident);
+ }
+ let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
+ unsafe { __new(&mut repr, &mut value) }
+ repr
}
- let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
- unsafe { __new(&mut repr, &mut value) }
- repr
- }
- })
- } else {
- None
- };
+ })
+ } else {
+ None
+ };
- quote! {
- unsafe impl ::cxx::private::UniquePtrTarget for #ident {
+ let begin_span =
+ explicit_impl.map_or_else(Span::call_site, |explicit| explicit.impl_token.span);
+ let end_span = explicit_impl.map_or_else(Span::call_site, |explicit| explicit.brace_token.span);
+ let unsafe_token = format_ident!("unsafe", span = begin_span);
+
+ quote_spanned! {end_span=>
+ #unsafe_token impl ::cxx::private::UniquePtrTarget for #ident {
const __NAME: &'static dyn ::std::fmt::Display = &#name;
fn __null() -> *mut ::std::ffi::c_void {
extern "C" {
@@ -746,20 +911,33 @@
}
}
-fn expand_cxx_vector(namespace: &Namespace, elem: &Ident) -> TokenStream {
- let name = elem.to_string();
- let prefix = format!("cxxbridge03$std$vector${}{}$", namespace, elem);
+fn expand_cxx_vector(
+ elem: &ResolvableName,
+ explicit_impl: Option<&Impl>,
+ types: &Types,
+) -> TokenStream {
+ let _ = explicit_impl;
+ let name = elem.rust.to_string();
+ let prefix = format!("cxxbridge05$std$vector${}$", elem.to_symbol(types));
let link_size = format!("{}size", prefix);
let link_get_unchecked = format!("{}get_unchecked", prefix);
- let unique_ptr_prefix = format!("cxxbridge03$unique_ptr$std$vector${}{}$", namespace, elem);
+ let unique_ptr_prefix = format!(
+ "cxxbridge05$unique_ptr$std$vector${}$",
+ elem.to_symbol(types)
+ );
let link_unique_ptr_null = format!("{}null", unique_ptr_prefix);
let link_unique_ptr_raw = format!("{}raw", unique_ptr_prefix);
let link_unique_ptr_get = format!("{}get", unique_ptr_prefix);
let link_unique_ptr_release = format!("{}release", unique_ptr_prefix);
let link_unique_ptr_drop = format!("{}drop", unique_ptr_prefix);
- quote! {
- unsafe impl ::cxx::private::VectorElement for #elem {
+ let begin_span =
+ explicit_impl.map_or_else(Span::call_site, |explicit| explicit.impl_token.span);
+ let end_span = explicit_impl.map_or_else(Span::call_site, |explicit| explicit.brace_token.span);
+ let unsafe_token = format_ident!("unsafe", span = begin_span);
+
+ quote_spanned! {end_span=>
+ #unsafe_token impl ::cxx::private::VectorElement for #elem {
const __NAME: &'static dyn ::std::fmt::Display = &#name;
fn __vector_size(v: &::cxx::CxxVector<Self>) -> usize {
extern "C" {
@@ -768,12 +946,12 @@
}
unsafe { __vector_size(v) }
}
- unsafe fn __get_unchecked(v: &::cxx::CxxVector<Self>, pos: usize) -> &Self {
+ unsafe fn __get_unchecked(v: &::cxx::CxxVector<Self>, pos: usize) -> *const Self {
extern "C" {
#[link_name = #link_get_unchecked]
fn __get_unchecked(_: &::cxx::CxxVector<#elem>, _: usize) -> *const #elem;
}
- &*__get_unchecked(v, pos)
+ __get_unchecked(v, pos)
}
fn __unique_ptr_null() -> *mut ::std::ffi::c_void {
extern "C" {
@@ -831,36 +1009,45 @@
.map_or(false, |ret| sig.throws || types.needs_indirect_abi(ret))
}
-fn expand_extern_type(ty: &Type) -> TokenStream {
+fn expand_extern_type(ty: &Type, types: &Types, proper: bool) -> TokenStream {
match ty {
- Type::Ident(ident) if ident == RustString => quote!(::cxx::private::RustString),
+ Type::Ident(ident) if ident.rust == RustString => quote!(::cxx::private::RustString),
Type::RustBox(ty) | Type::UniquePtr(ty) => {
- let inner = expand_extern_type(&ty.inner);
+ let inner = expand_extern_type(&ty.inner, types, proper);
quote!(*mut #inner)
}
Type::RustVec(ty) => {
- let elem = expand_extern_type(&ty.inner);
+ let elem = expand_extern_type(&ty.inner, types, proper);
quote!(::cxx::private::RustVec<#elem>)
}
- Type::Ref(ty) => match &ty.inner {
- Type::Ident(ident) if ident == RustString => quote!(&::cxx::private::RustString),
- Type::RustVec(ty) => {
- let inner = expand_extern_type(&ty.inner);
- quote!(&::cxx::private::RustVec<#inner>)
+ Type::Ref(ty) => {
+ let mutability = ty.mutability;
+ match &ty.inner {
+ Type::Ident(ident) if ident.rust == RustString => {
+ quote!(&#mutability ::cxx::private::RustString)
+ }
+ Type::RustVec(ty) => {
+ let inner = expand_extern_type(&ty.inner, types, proper);
+ quote!(&#mutability ::cxx::private::RustVec<#inner>)
+ }
+ inner if proper && types.is_considered_improper_ctype(inner) => match mutability {
+ None => quote!(*const ::std::ffi::c_void),
+ Some(_) => quote!(*#mutability ::std::ffi::c_void),
+ },
+ _ => quote!(#ty),
}
- _ => quote!(#ty),
- },
+ }
Type::Str(_) => quote!(::cxx::private::RustStr),
Type::SliceRefU8(_) => quote!(::cxx::private::RustSliceU8),
_ => quote!(#ty),
}
}
-fn expand_extern_return_type(ret: &Option<Type>, types: &Types) -> TokenStream {
+fn expand_extern_return_type(ret: &Option<Type>, types: &Types, proper: bool) -> TokenStream {
let ret = match ret {
Some(ret) if !types.needs_indirect_abi(ret) => ret,
_ => return TokenStream::new(),
};
- let ty = expand_extern_type(ret);
+ let ty = expand_extern_type(ret, types, proper);
quote!(-> #ty)
}
diff --git a/macro/src/lib.rs b/macro/src/lib.rs
index bc8d19b..e55a08a 100644
--- a/macro/src/lib.rs
+++ b/macro/src/lib.rs
@@ -9,13 +9,17 @@
extern crate proc_macro;
+mod derive;
mod expand;
mod syntax;
mod type_id;
+use crate::syntax::file::Module;
use crate::syntax::namespace::Namespace;
+use crate::syntax::qualified::QualifiedName;
use proc_macro::TokenStream;
-use syn::{parse_macro_input, ItemMod, LitStr};
+use syn::parse::{Parse, ParseStream, Parser, Result};
+use syn::parse_macro_input;
/// `#[cxx::bridge] mod ffi { ... }`
///
@@ -28,7 +32,7 @@
/// attribute macro.
///
/// ```
-/// #[cxx::bridge(namespace = mycompany::rust)]
+/// #[cxx::bridge(namespace = "mycompany::rust")]
/// # mod ffi {}
/// ```
///
@@ -38,16 +42,28 @@
pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream {
let _ = syntax::error::ERRORS;
- let namespace = parse_macro_input!(args as Namespace);
- let ffi = parse_macro_input!(input as ItemMod);
+ let namespace = match Namespace::parse_bridge_attr_namespace.parse(args) {
+ Ok(namespace) => namespace,
+ Err(err) => return err.to_compile_error().into(),
+ };
+ let mut ffi = parse_macro_input!(input as Module);
+ ffi.namespace = namespace;
- expand::bridge(&namespace, ffi)
+ expand::bridge(ffi)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
#[proc_macro]
pub fn type_id(input: TokenStream) -> TokenStream {
- let arg = parse_macro_input!(input as LitStr);
- type_id::expand(arg).into()
+ struct TypeId(QualifiedName);
+
+ impl Parse for TypeId {
+ fn parse(input: ParseStream) -> Result<Self> {
+ QualifiedName::parse_quoted_or_unquoted(input).map(TypeId)
+ }
+ }
+
+ let arg = parse_macro_input!(input as TypeId);
+ type_id::expand(arg.0).into()
}
diff --git a/macro/src/type_id.rs b/macro/src/type_id.rs
index 445da2b..5c5d9cc 100644
--- a/macro/src/type_id.rs
+++ b/macro/src/type_id.rs
@@ -1,16 +1,16 @@
+use crate::syntax::qualified::QualifiedName;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
-use syn::LitStr;
// "folly::File" => `(f, o, l, l, y, (), F, i, l, e)`
-pub fn expand(arg: LitStr) -> TokenStream {
+pub fn expand(arg: QualifiedName) -> TokenStream {
let mut ids = Vec::new();
- for word in arg.value().split("::") {
+ for word in arg.segments {
if !ids.is_empty() {
ids.push(quote!(()));
}
- for ch in word.chars() {
+ for ch in word.to_string().chars() {
ids.push(match ch {
'A'..='Z' | 'a'..='z' => {
let t = format_ident!("{}", ch);
diff --git a/src/cxx.cc b/src/cxx.cc
index da05ee2..cbaecbf 100644
--- a/src/cxx.cc
+++ b/src/cxx.cc
@@ -1,13 +1,41 @@
#include "../include/cxx.h"
+#include <cassert>
#include <cstring>
#include <exception>
#include <iostream>
#include <memory>
#include <stdexcept>
+#include <type_traits>
#include <vector>
+extern "C" {
+const char *cxxbridge05$cxx_string$data(const std::string &s) noexcept {
+ return s.data();
+}
+
+size_t cxxbridge05$cxx_string$length(const std::string &s) noexcept {
+ return s.length();
+}
+
+// rust::String
+void cxxbridge05$string$new(rust::String *self) noexcept;
+void cxxbridge05$string$clone(rust::String *self,
+ const rust::String &other) noexcept;
+bool cxxbridge05$string$from(rust::String *self, const char *ptr,
+ size_t len) noexcept;
+void cxxbridge05$string$drop(rust::String *self) noexcept;
+const char *cxxbridge05$string$ptr(const rust::String *self) noexcept;
+size_t cxxbridge05$string$len(const rust::String *self) noexcept;
+
+// rust::Str
+bool cxxbridge05$str$valid(const char *ptr, size_t len) noexcept;
+} // extern "C"
+
+namespace rust {
+inline namespace cxxbridge05 {
+
template <typename Exception>
-static void panic [[noreturn]] (const char *msg) {
+void panic [[noreturn]] (const char *msg) {
#if defined(RUST_CXX_NO_EXCEPTIONS)
std::cerr << "Error: " << msg << ". Aborting." << std::endl;
std::terminate();
@@ -16,68 +44,54 @@
#endif
}
-extern "C" {
-const char *cxxbridge03$cxx_string$data(const std::string &s) noexcept {
- return s.data();
-}
+template void panic<std::out_of_range>[[noreturn]] (const char *msg);
-size_t cxxbridge03$cxx_string$length(const std::string &s) noexcept {
- return s.length();
-}
-
-// rust::String
-void cxxbridge03$string$new(rust::String *self) noexcept;
-void cxxbridge03$string$clone(rust::String *self,
- const rust::String &other) noexcept;
-bool cxxbridge03$string$from(rust::String *self, const char *ptr,
- size_t len) noexcept;
-void cxxbridge03$string$drop(rust::String *self) noexcept;
-const char *cxxbridge03$string$ptr(const rust::String *self) noexcept;
-size_t cxxbridge03$string$len(const rust::String *self) noexcept;
-
-// rust::Str
-bool cxxbridge03$str$valid(const char *ptr, size_t len) noexcept;
-} // extern "C"
-
-namespace rust {
-inline namespace cxxbridge03 {
-
-String::String() noexcept { cxxbridge03$string$new(this); }
+String::String() noexcept { cxxbridge05$string$new(this); }
String::String(const String &other) noexcept {
- cxxbridge03$string$clone(this, other);
+ cxxbridge05$string$clone(this, other);
}
String::String(String &&other) noexcept {
this->repr = other.repr;
- cxxbridge03$string$new(&other);
+ cxxbridge05$string$new(&other);
}
-String::~String() noexcept { cxxbridge03$string$drop(this); }
+String::~String() noexcept { cxxbridge05$string$drop(this); }
-String::String(const std::string &s) : String(s.data(), s.length()) {}
-
-String::String(const char *s) : String(s, std::strlen(s)) {}
-
-String::String(const char *s, size_t len) {
- if (!cxxbridge03$string$from(this, s, len)) {
+static void initString(String *self, const char *s, size_t len) {
+ if (!cxxbridge05$string$from(self, s, len)) {
panic<std::invalid_argument>("data for rust::String is not utf-8");
}
}
+String::String(const std::string &s) { initString(this, s.data(), s.length()); }
+
+String::String(const char *s) {
+ assert(s != nullptr);
+ initString(this, s, std::strlen(s));
+}
+
+String::String(const char *s, size_t len) {
+ assert(s != nullptr || len == 0);
+ initString(this,
+ s == nullptr && len == 0 ? reinterpret_cast<const char *>(1) : s,
+ len);
+}
+
String &String::operator=(const String &other) noexcept {
if (this != &other) {
- cxxbridge03$string$drop(this);
- cxxbridge03$string$clone(this, other);
+ cxxbridge05$string$drop(this);
+ cxxbridge05$string$clone(this, other);
}
return *this;
}
String &String::operator=(String &&other) noexcept {
if (this != &other) {
- cxxbridge03$string$drop(this);
+ cxxbridge05$string$drop(this);
this->repr = other.repr;
- cxxbridge03$string$new(&other);
+ cxxbridge05$string$new(&other);
}
return *this;
}
@@ -87,12 +101,12 @@
}
const char *String::data() const noexcept {
- return cxxbridge03$string$ptr(this);
+ return cxxbridge05$string$ptr(this);
}
-size_t String::size() const noexcept { return cxxbridge03$string$len(this); }
+size_t String::size() const noexcept { return cxxbridge05$string$len(this); }
-size_t String::length() const noexcept { return cxxbridge03$string$len(this); }
+size_t String::length() const noexcept { return cxxbridge05$string$len(this); }
String::String(unsafe_bitcopy_t, const String &bits) noexcept
: repr(bits.repr) {}
@@ -102,160 +116,191 @@
return os;
}
-Str::Str() noexcept : repr(Repr{reinterpret_cast<const char *>(this), 0}) {}
+Str::Str() noexcept : ptr(reinterpret_cast<const char *>(1)), len(0) {}
-Str::Str(const Str &) noexcept = default;
-
-Str::Str(const std::string &s) : Str(s.data(), s.length()) {}
-
-Str::Str(const char *s) : Str(s, std::strlen(s)) {}
-
-Str::Str(const char *s, size_t len) : repr(Repr{s, len}) {
- if (!cxxbridge03$str$valid(this->repr.ptr, this->repr.len)) {
+static void initStr(const char *ptr, size_t len) {
+ if (!cxxbridge05$str$valid(ptr, len)) {
panic<std::invalid_argument>("data for rust::Str is not utf-8");
}
}
-Str &Str::operator=(Str other) noexcept {
- this->repr = other.repr;
- return *this;
+Str::Str(const std::string &s) : ptr(s.data()), len(s.length()) {
+ initStr(this->ptr, this->len);
+}
+
+Str::Str(const char *s) : ptr(s), len(std::strlen(s)) {
+ assert(s != nullptr);
+ initStr(this->ptr, this->len);
+}
+
+Str::Str(const char *s, size_t len)
+ : ptr(s == nullptr && len == 0 ? reinterpret_cast<const char *>(1) : s),
+ len(len) {
+ assert(s != nullptr || len == 0);
+ initStr(this->ptr, this->len);
}
Str::operator std::string() const {
return std::string(this->data(), this->size());
}
-const char *Str::data() const noexcept { return this->repr.ptr; }
-
-size_t Str::size() const noexcept { return this->repr.len; }
-
-size_t Str::length() const noexcept { return this->repr.len; }
-
-Str::Str(Repr repr_) noexcept : repr(repr_) {}
-
-Str::operator Repr() noexcept { return this->repr; }
-
std::ostream &operator<<(std::ostream &os, const Str &s) {
os.write(s.data(), s.size());
return os;
}
+static_assert(std::is_trivially_copy_constructible<Str>::value,
+ "trivial Str(const Str &)");
+static_assert(std::is_trivially_copy_assignable<Str>::value,
+ "trivial operator=(const Str &)");
+static_assert(std::is_trivially_destructible<Str>::value, "trivial ~Str()");
+
extern "C" {
-const char *cxxbridge03$error(const char *ptr, size_t len) {
+const char *cxxbridge05$error(const char *ptr, size_t len) {
char *copy = new char[len];
- strncpy(copy, ptr, len);
+ std::strncpy(copy, ptr, len);
return copy;
}
} // extern "C"
-Error::Error(Str::Repr msg) noexcept : msg(msg) {}
+Error::Error(const Error &other)
+ : std::exception(other), msg(cxxbridge05$error(other.msg, other.len)),
+ len(other.len) {}
-Error::Error(const Error &other) {
- this->msg.ptr = cxxbridge03$error(other.msg.ptr, other.msg.len);
- this->msg.len = other.msg.len;
+Error::Error(Error &&other) noexcept
+ : std::exception(std::move(other)), msg(other.msg), len(other.len) {
+ other.msg = nullptr;
+ other.len = 0;
}
-Error::Error(Error &&other) noexcept {
- delete[] this->msg.ptr;
- this->msg = other.msg;
- other.msg.ptr = nullptr;
- other.msg.len = 0;
+Error::~Error() noexcept { delete[] this->msg; }
+
+Error &Error::operator=(const Error &other) {
+ if (this != &other) {
+ std::exception::operator=(other);
+ delete[] this->msg;
+ this->msg = nullptr;
+ this->msg = cxxbridge05$error(other.msg, other.len);
+ this->len = other.len;
+ }
+ return *this;
}
-Error::~Error() noexcept { delete[] this->msg.ptr; }
+Error &Error::operator=(Error &&other) noexcept {
+ if (this != &other) {
+ std::exception::operator=(std::move(other));
+ this->msg = other.msg;
+ this->len = other.len;
+ other.msg = nullptr;
+ other.len = 0;
+ }
+ return *this;
+}
-const char *Error::what() const noexcept { return this->msg.ptr; }
+const char *Error::what() const noexcept { return this->msg; }
-} // namespace cxxbridge03
+} // namespace cxxbridge05
} // namespace rust
extern "C" {
-void cxxbridge03$unique_ptr$std$string$null(
+void cxxbridge05$unique_ptr$std$string$null(
std::unique_ptr<std::string> *ptr) noexcept {
new (ptr) std::unique_ptr<std::string>();
}
-void cxxbridge03$unique_ptr$std$string$raw(std::unique_ptr<std::string> *ptr,
+void cxxbridge05$unique_ptr$std$string$raw(std::unique_ptr<std::string> *ptr,
std::string *raw) noexcept {
new (ptr) std::unique_ptr<std::string>(raw);
}
-const std::string *cxxbridge03$unique_ptr$std$string$get(
+const std::string *cxxbridge05$unique_ptr$std$string$get(
const std::unique_ptr<std::string> &ptr) noexcept {
return ptr.get();
}
-std::string *cxxbridge03$unique_ptr$std$string$release(
+std::string *cxxbridge05$unique_ptr$std$string$release(
std::unique_ptr<std::string> &ptr) noexcept {
return ptr.release();
}
-void cxxbridge03$unique_ptr$std$string$drop(
+void cxxbridge05$unique_ptr$std$string$drop(
std::unique_ptr<std::string> *ptr) noexcept {
ptr->~unique_ptr();
}
} // extern "C"
#define STD_VECTOR_OPS(RUST_TYPE, CXX_TYPE) \
- size_t cxxbridge03$std$vector$##RUST_TYPE##$size( \
+ size_t cxxbridge05$std$vector$##RUST_TYPE##$size( \
const std::vector<CXX_TYPE> &s) noexcept { \
return s.size(); \
} \
- const CXX_TYPE *cxxbridge03$std$vector$##RUST_TYPE##$get_unchecked( \
+ const CXX_TYPE *cxxbridge05$std$vector$##RUST_TYPE##$get_unchecked( \
const std::vector<CXX_TYPE> &s, size_t pos) noexcept { \
return &s[pos]; \
} \
- void cxxbridge03$unique_ptr$std$vector$##RUST_TYPE##$null( \
+ void cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$null( \
std::unique_ptr<std::vector<CXX_TYPE>> *ptr) noexcept { \
new (ptr) std::unique_ptr<std::vector<CXX_TYPE>>(); \
} \
- void cxxbridge03$unique_ptr$std$vector$##RUST_TYPE##$raw( \
+ void cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$raw( \
std::unique_ptr<std::vector<CXX_TYPE>> *ptr, \
std::vector<CXX_TYPE> *raw) noexcept { \
new (ptr) std::unique_ptr<std::vector<CXX_TYPE>>(raw); \
} \
const std::vector<CXX_TYPE> \
- *cxxbridge03$unique_ptr$std$vector$##RUST_TYPE##$get( \
+ *cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$get( \
const std::unique_ptr<std::vector<CXX_TYPE>> &ptr) noexcept { \
return ptr.get(); \
} \
std::vector<CXX_TYPE> \
- *cxxbridge03$unique_ptr$std$vector$##RUST_TYPE##$release( \
+ *cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$release( \
std::unique_ptr<std::vector<CXX_TYPE>> &ptr) noexcept { \
return ptr.release(); \
} \
- void cxxbridge03$unique_ptr$std$vector$##RUST_TYPE##$drop( \
+ void cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$drop( \
std::unique_ptr<std::vector<CXX_TYPE>> *ptr) noexcept { \
ptr->~unique_ptr(); \
}
#define RUST_VEC_EXTERNS(RUST_TYPE, CXX_TYPE) \
- void cxxbridge03$rust_vec$##RUST_TYPE##$new( \
+ void cxxbridge05$rust_vec$##RUST_TYPE##$new( \
rust::Vec<CXX_TYPE> *ptr) noexcept; \
- void cxxbridge03$rust_vec$##RUST_TYPE##$drop( \
+ void cxxbridge05$rust_vec$##RUST_TYPE##$drop( \
rust::Vec<CXX_TYPE> *ptr) noexcept; \
- size_t cxxbridge03$rust_vec$##RUST_TYPE##$len( \
+ size_t cxxbridge05$rust_vec$##RUST_TYPE##$len( \
const rust::Vec<CXX_TYPE> *ptr) noexcept; \
- const CXX_TYPE *cxxbridge03$rust_vec$##RUST_TYPE##$data( \
+ const CXX_TYPE *cxxbridge05$rust_vec$##RUST_TYPE##$data( \
const rust::Vec<CXX_TYPE> *ptr) noexcept; \
- size_t cxxbridge03$rust_vec$##RUST_TYPE##$stride() noexcept;
+ void cxxbridge05$rust_vec$##RUST_TYPE##$reserve_total( \
+ rust::Vec<CXX_TYPE> *ptr, size_t cap) noexcept; \
+ void cxxbridge05$rust_vec$##RUST_TYPE##$set_len(rust::Vec<CXX_TYPE> *ptr, \
+ size_t len) noexcept; \
+ size_t cxxbridge05$rust_vec$##RUST_TYPE##$stride() noexcept;
#define RUST_VEC_OPS(RUST_TYPE, CXX_TYPE) \
template <> \
Vec<CXX_TYPE>::Vec() noexcept { \
- cxxbridge03$rust_vec$##RUST_TYPE##$new(this); \
+ cxxbridge05$rust_vec$##RUST_TYPE##$new(this); \
} \
template <> \
void Vec<CXX_TYPE>::drop() noexcept { \
- return cxxbridge03$rust_vec$##RUST_TYPE##$drop(this); \
+ return cxxbridge05$rust_vec$##RUST_TYPE##$drop(this); \
} \
template <> \
size_t Vec<CXX_TYPE>::size() const noexcept { \
- return cxxbridge03$rust_vec$##RUST_TYPE##$len(this); \
+ return cxxbridge05$rust_vec$##RUST_TYPE##$len(this); \
} \
template <> \
const CXX_TYPE *Vec<CXX_TYPE>::data() const noexcept { \
- return cxxbridge03$rust_vec$##RUST_TYPE##$data(this); \
+ return cxxbridge05$rust_vec$##RUST_TYPE##$data(this); \
+ } \
+ template <> \
+ void Vec<CXX_TYPE>::reserve_total(size_t cap) noexcept { \
+ cxxbridge05$rust_vec$##RUST_TYPE##$reserve_total(this, cap); \
+ } \
+ template <> \
+ void Vec<CXX_TYPE>::set_len(size_t len) noexcept { \
+ cxxbridge05$rust_vec$##RUST_TYPE##$set_len(this, len); \
} \
template <> \
size_t Vec<CXX_TYPE>::stride() noexcept { \
- return cxxbridge03$rust_vec$##RUST_TYPE##$stride(); \
+ return cxxbridge05$rust_vec$##RUST_TYPE##$stride(); \
}
// Usize and isize are the same type as one of the below.
@@ -274,11 +319,13 @@
#define FOR_EACH_STD_VECTOR(MACRO) \
FOR_EACH_NUMERIC(MACRO) \
MACRO(usize, size_t) \
- MACRO(isize, rust::isize)
+ MACRO(isize, rust::isize) \
+ MACRO(string, std::string)
#define FOR_EACH_RUST_VEC(MACRO) \
FOR_EACH_NUMERIC(MACRO) \
- MACRO(bool, bool)
+ MACRO(bool, bool) \
+ MACRO(string, rust::String)
extern "C" {
FOR_EACH_STD_VECTOR(STD_VECTOR_OPS)
@@ -286,7 +333,7 @@
} // extern "C"
namespace rust {
-inline namespace cxxbridge03 {
+inline namespace cxxbridge05 {
FOR_EACH_RUST_VEC(RUST_VEC_OPS)
-} // namespace cxxbridge03
+} // namespace cxxbridge05
} // namespace rust
diff --git a/src/cxx_string.rs b/src/cxx_string.rs
index ffd0c5c..7b47feb 100644
--- a/src/cxx_string.rs
+++ b/src/cxx_string.rs
@@ -1,12 +1,13 @@
-use std::borrow::Cow;
-use std::fmt::{self, Debug, Display};
-use std::slice;
-use std::str::{self, Utf8Error};
+use alloc::borrow::Cow;
+use alloc::string::String;
+use core::fmt::{self, Debug, Display};
+use core::slice;
+use core::str::{self, Utf8Error};
extern "C" {
- #[link_name = "cxxbridge03$cxx_string$data"]
+ #[link_name = "cxxbridge05$cxx_string$data"]
fn string_data(_: &CxxString) -> *const u8;
- #[link_name = "cxxbridge03$cxx_string$length"]
+ #[link_name = "cxxbridge05$cxx_string$length"]
fn string_length(_: &CxxString) -> usize;
}
diff --git a/src/cxx_vector.rs b/src/cxx_vector.rs
index bfb4a4e..5fb0807 100644
--- a/src/cxx_vector.rs
+++ b/src/cxx_vector.rs
@@ -1,8 +1,10 @@
-use std::ffi::c_void;
-use std::fmt::{self, Display};
-use std::marker::PhantomData;
-use std::mem;
-use std::ptr;
+use crate::cxx_string::CxxString;
+use core::ffi::c_void;
+use core::fmt::{self, Display};
+use core::marker::PhantomData;
+use core::mem;
+use core::ptr;
+use core::slice;
/// Binding to C++ `std::vector<T, std::allocator<T>>`.
///
@@ -43,7 +45,7 @@
/// out of bounds.
pub fn get(&self, pos: usize) -> Option<&T> {
if pos < self.len() {
- Some(unsafe { T::__get_unchecked(self, pos) })
+ Some(unsafe { self.get_unchecked(pos) })
} else {
None
}
@@ -60,7 +62,24 @@
///
/// [operator_at]: https://en.cppreference.com/w/cpp/container/vector/operator_at
pub unsafe fn get_unchecked(&self, pos: usize) -> &T {
- T::__get_unchecked(self, pos)
+ &*T::__get_unchecked(self, pos)
+ }
+
+ /// Returns a slice to the underlying contiguous array of elements.
+ pub fn as_slice(&self) -> &[T] {
+ let len = self.len();
+ if len == 0 {
+ // The slice::from_raw_parts in the other branch requires a nonnull
+ // and properly aligned data ptr. C++ standard does not guarantee
+ // that data() on a vector with size 0 would return a nonnull
+ // pointer or sufficiently aligned pointer, so using it would be
+ // undefined behavior. Create our own empty slice in Rust instead
+ // which upholds the invariants.
+ &[]
+ } else {
+ let ptr = unsafe { T::__get_unchecked(self, 0) };
+ unsafe { slice::from_raw_parts(ptr, len) }
+ }
}
}
@@ -121,7 +140,7 @@
pub unsafe trait VectorElement: Sized {
const __NAME: &'static dyn Display;
fn __vector_size(v: &CxxVector<Self>) -> usize;
- unsafe fn __get_unchecked(v: &CxxVector<Self>, pos: usize) -> &Self;
+ unsafe fn __get_unchecked(v: &CxxVector<Self>, pos: usize) -> *const Self;
fn __unique_ptr_null() -> *mut c_void;
unsafe fn __unique_ptr_raw(raw: *mut CxxVector<Self>) -> *mut c_void;
unsafe fn __unique_ptr_get(repr: *mut c_void) -> *const CxxVector<Self>;
@@ -129,34 +148,34 @@
unsafe fn __unique_ptr_drop(repr: *mut c_void);
}
-macro_rules! impl_vector_element_for_primitive {
- ($ty:ident) => {
+macro_rules! impl_vector_element {
+ ($segment:expr, $name:expr, $ty:ty) => {
const_assert_eq!(1, mem::align_of::<CxxVector<$ty>>());
unsafe impl VectorElement for $ty {
- const __NAME: &'static dyn Display = &stringify!($ty);
+ const __NAME: &'static dyn Display = &$name;
fn __vector_size(v: &CxxVector<$ty>) -> usize {
extern "C" {
attr! {
- #[link_name = concat!("cxxbridge03$std$vector$", stringify!($ty), "$size")]
+ #[link_name = concat!("cxxbridge05$std$vector$", $segment, "$size")]
fn __vector_size(_: &CxxVector<$ty>) -> usize;
}
}
unsafe { __vector_size(v) }
}
- unsafe fn __get_unchecked(v: &CxxVector<$ty>, pos: usize) -> &$ty {
+ unsafe fn __get_unchecked(v: &CxxVector<$ty>, pos: usize) -> *const $ty {
extern "C" {
attr! {
- #[link_name = concat!("cxxbridge03$std$vector$", stringify!($ty), "$get_unchecked")]
+ #[link_name = concat!("cxxbridge05$std$vector$", $segment, "$get_unchecked")]
fn __get_unchecked(_: &CxxVector<$ty>, _: usize) -> *const $ty;
}
}
- &*__get_unchecked(v, pos)
+ __get_unchecked(v, pos)
}
fn __unique_ptr_null() -> *mut c_void {
extern "C" {
attr! {
- #[link_name = concat!("cxxbridge03$unique_ptr$std$vector$", stringify!($ty), "$null")]
+ #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$null")]
fn __unique_ptr_null(this: *mut *mut c_void);
}
}
@@ -167,7 +186,7 @@
unsafe fn __unique_ptr_raw(raw: *mut CxxVector<Self>) -> *mut c_void {
extern "C" {
attr! {
- #[link_name = concat!("cxxbridge03$unique_ptr$std$vector$", stringify!($ty), "$raw")]
+ #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$raw")]
fn __unique_ptr_raw(this: *mut *mut c_void, raw: *mut CxxVector<$ty>);
}
}
@@ -178,7 +197,7 @@
unsafe fn __unique_ptr_get(repr: *mut c_void) -> *const CxxVector<Self> {
extern "C" {
attr! {
- #[link_name = concat!("cxxbridge03$unique_ptr$std$vector$", stringify!($ty), "$get")]
+ #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$get")]
fn __unique_ptr_get(this: *const *mut c_void) -> *const CxxVector<$ty>;
}
}
@@ -187,7 +206,7 @@
unsafe fn __unique_ptr_release(mut repr: *mut c_void) -> *mut CxxVector<Self> {
extern "C" {
attr! {
- #[link_name = concat!("cxxbridge03$unique_ptr$std$vector$", stringify!($ty), "$release")]
+ #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$release")]
fn __unique_ptr_release(this: *mut *mut c_void) -> *mut CxxVector<$ty>;
}
}
@@ -196,7 +215,7 @@
unsafe fn __unique_ptr_drop(mut repr: *mut c_void) {
extern "C" {
attr! {
- #[link_name = concat!("cxxbridge03$unique_ptr$std$vector$", stringify!($ty), "$drop")]
+ #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$drop")]
fn __unique_ptr_drop(this: *mut *mut c_void);
}
}
@@ -206,6 +225,12 @@
};
}
+macro_rules! impl_vector_element_for_primitive {
+ ($ty:ident) => {
+ impl_vector_element!(stringify!($ty), stringify!($ty), $ty);
+ };
+}
+
impl_vector_element_for_primitive!(u8);
impl_vector_element_for_primitive!(u16);
impl_vector_element_for_primitive!(u32);
@@ -218,3 +243,5 @@
impl_vector_element_for_primitive!(isize);
impl_vector_element_for_primitive!(f32);
impl_vector_element_for_primitive!(f64);
+
+impl_vector_element!("string", "CxxString", CxxString);
diff --git a/src/exception.rs b/src/exception.rs
index 125e484..0ffca66 100644
--- a/src/exception.rs
+++ b/src/exception.rs
@@ -1,4 +1,5 @@
-use std::fmt::{self, Debug, Display};
+use alloc::boxed::Box;
+use core::fmt::{self, Debug, Display};
/// Exception thrown from an `extern "C"` function.
#[derive(Debug)]
diff --git a/src/extern_type.rs b/src/extern_type.rs
index 6701ef5..f92ff40 100644
--- a/src/extern_type.rs
+++ b/src/extern_type.rs
@@ -1,3 +1,5 @@
+use self::kind::{Kind, Opaque, Trivial};
+
/// A type for which the layout is determined by its C++ definition.
///
/// This trait serves the following two related purposes.
@@ -26,7 +28,7 @@
/// ```no_run
/// // file1.rs
/// # mod file1 {
-/// #[cxx::bridge(namespace = example)]
+/// #[cxx::bridge(namespace = "example")]
/// pub mod ffi {
/// extern "C" {
/// type Demo;
@@ -37,7 +39,7 @@
/// # }
///
/// // file2.rs
-/// #[cxx::bridge(namespace = example)]
+/// #[cxx::bridge(namespace = "example")]
/// pub mod ffi {
/// extern "C" {
/// type Demo = crate::file1::ffi::Demo;
@@ -54,7 +56,7 @@
/// ## Integrating with bindgen-generated types
///
/// Handwritten `ExternType` impls make it possible to plug in a data structure
-/// emitted by bindgen as the definition of an opaque C++ type emitted by CXX.
+/// emitted by bindgen as the definition of a C++ type emitted by CXX.
///
/// By writing the unsafe `ExternType` impl, the programmer asserts that the C++
/// namespace and type name given in the type id refers to a C++ type that is
@@ -73,9 +75,10 @@
///
/// unsafe impl ExternType for folly_sys::StringPiece {
/// type Id = type_id!("folly::StringPiece");
+/// type Kind = cxx::kind::Opaque;
/// }
///
-/// #[cxx::bridge(namespace = folly)]
+/// #[cxx::bridge(namespace = "folly")]
/// pub mod ffi {
/// extern "C" {
/// include!("rust_cxx_bindings.h");
@@ -101,10 +104,80 @@
/// # struct TypeName;
/// # unsafe impl cxx::ExternType for TypeName {
/// type Id = cxx::type_id!("name::space::of::TypeName");
+ /// # type Kind = cxx::kind::Opaque;
/// # }
/// ```
type Id;
+
+ /// Either [`cxx::kind::Opaque`] or [`cxx::kind::Trivial`].
+ ///
+ /// [`cxx::kind::Opaque`]: kind::Opaque
+ /// [`cxx::kind::Trivial`]: kind::Trivial
+ ///
+ /// A C++ type is only okay to hold and pass around by value in Rust if its
+ /// [move constructor is trivial] and it has no destructor. In CXX, these
+ /// are called Trivial extern C++ types, while types with nontrivial move
+ /// behavior or a destructor must be considered Opaque and handled by Rust
+ /// only behind an indirection, such as a reference or UniquePtr.
+ ///
+ /// [move constructor is trivial]: https://en.cppreference.com/w/cpp/types/is_move_constructible
+ ///
+ /// If you believe your C++ type reflected by this ExternType impl is indeed
+ /// fine to hold by value and move in Rust, you can specify:
+ ///
+ /// ```
+ /// # struct TypeName;
+ /// # unsafe impl cxx::ExternType for TypeName {
+ /// # type Id = cxx::type_id!("name::space::of::TypeName");
+ /// type Kind = cxx::kind::Trivial;
+ /// # }
+ /// ```
+ ///
+ /// which will enable you to pass it into C++ functions by value, return it
+ /// by value, and include it in `struct`s that you have declared to
+ /// `cxx::bridge`. Your claim about the triviality of the C++ type will be
+ /// checked by a `static_assert` in the generated C++ side of the binding.
+ type Kind: Kind;
+}
+
+/// Marker types identifying Rust's knowledge about an extern C++ type.
+///
+/// These markers are used in the [`Kind`][ExternType::Kind] associated type in
+/// impls of the `ExternType` trait. Refer to the documentation of `Kind` for an
+/// overview of their purpose.
+pub mod kind {
+ use super::private;
+
+ /// An opaque type which cannot be passed or held by value within Rust.
+ ///
+ /// Rust's move semantics are such that every move is equivalent to a
+ /// memcpy. This is incompatible in general with C++'s constructor-based
+ /// move semantics, so a C++ type which has a destructor or nontrivial move
+ /// constructor must never exist by value in Rust. In CXX, such types are
+ /// called opaque C++ types.
+ ///
+ /// When passed across an FFI boundary, an opaque C++ type must be behind an
+ /// indirection such as a reference or UniquePtr.
+ pub enum Opaque {}
+
+ /// A type with trivial move constructor and no destructor, which can
+ /// therefore be owned and moved around in Rust code without requiring
+ /// indirection.
+ pub enum Trivial {}
+
+ pub trait Kind: private::Sealed {}
+ impl Kind for Opaque {}
+ impl Kind for Trivial {}
+}
+
+mod private {
+ pub trait Sealed {}
+ impl Sealed for super::Opaque {}
+ impl Sealed for super::Trivial {}
}
#[doc(hidden)]
pub fn verify_extern_type<T: ExternType<Id = Id>, Id>() {}
+
+#[doc(hidden)]
+pub fn verify_extern_kind<T: ExternType<Kind = Kind>, Kind: self::Kind>() {}
diff --git a/src/lib.rs b/src/lib.rs
index 30dd41f..17d7548 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -18,7 +18,7 @@
//!
//! <br>
//!
-//! *Compiler support: requires rustc 1.42+ and c++11 or newer*<br>
+//! *Compiler support: requires rustc 1.43+ and c++11 or newer*<br>
//! *[Release notes](https://github.com/dtolnay/cxx/releases)*
//!
//! <br>
@@ -57,66 +57,72 @@
//!
//! # Example
//!
-//! A runnable version of this example is provided under the *demo-rs* directory
-//! of [https://github.com/dtolnay/cxx] (with the C++ side of the implementation
-//! in the *demo-cxx* directory). To try it out, jump into demo-rs and run
-//! `cargo run`.
+//! In this example we are writing a Rust application that wishes to take
+//! advantage of an existing C++ client for a large-file blobstore service. The
+//! blobstore supports a `put` operation for a discontiguous buffer upload. For
+//! example we might be uploading snapshots of a circular buffer which would
+//! tend to consist of 2 chunks, or fragments of a file spread across memory for
+//! some other reason.
+//!
+//! A runnable version of this example is provided under the *demo* directory of
+//! <https://github.com/dtolnay/cxx>. To try it out, run `cargo run` from that
+//! directory.
//!
//! ```no_run
//! #[cxx::bridge]
//! mod ffi {
//! // Any shared structs, whose fields will be visible to both languages.
-//! struct SharedThing {
-//! z: i32,
-//! y: Box<ThingR>,
-//! x: UniquePtr<ThingC>,
-//! }
-//!
-//! extern "C" {
-//! // One or more headers with the matching C++ declarations. Our code
-//! // generators don't read it but it gets #include'd and used in static
-//! // assertions to ensure our picture of the FFI boundary is accurate.
-//! include!("demo-cxx/demo.h");
-//!
-//! // Zero or more opaque types which both languages can pass around but
-//! // only C++ can see the fields.
-//! type ThingC;
-//!
-//! // Functions implemented in C++.
-//! fn make_demo(appname: &str) -> UniquePtr<ThingC>;
-//! fn get_name(thing: &ThingC) -> &CxxString;
-//! fn do_thing(state: SharedThing);
+//! struct BlobMetadata {
+//! size: usize,
+//! tags: Vec<String>,
//! }
//!
//! extern "Rust" {
//! // Zero or more opaque types which both languages can pass around but
//! // only Rust can see the fields.
-//! type ThingR;
+//! type MultiBuf;
//!
//! // Functions implemented in Rust.
-//! fn print_r(r: &ThingR);
+//! fn next_chunk(buf: &mut MultiBuf) -> &[u8];
+//! }
+//!
+//! extern "C++" {
+//! // One or more headers with the matching C++ declarations. Our code
+//! // generators don't read it but it gets #include'd and used in static
+//! // assertions to ensure our picture of the FFI boundary is accurate.
+//! include!("demo/include/blobstore.h");
+//!
+//! // Zero or more opaque types which both languages can pass around but
+//! // only C++ can see the fields.
+//! type BlobstoreClient;
+//!
+//! // Functions implemented in C++.
+//! fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
+//! fn put(&self, parts: &mut MultiBuf) -> u64;
+//! fn tag(&self, blobid: u64, tag: &str);
+//! fn metadata(&self, blobid: u64) -> BlobMetadata;
//! }
//! }
//! #
-//! # pub struct ThingR(usize);
+//! # pub struct MultiBuf;
//! #
-//! # fn print_r(r: &ThingR) {
-//! # println!("called back with r={}", r.0);
+//! # fn next_chunk(_buf: &mut MultiBuf) -> &[u8] {
+//! # unimplemented!()
//! # }
//! #
//! # fn main() {}
//! ```
//!
-//! Now we simply provide C++ definitions of all the things in the `extern "C"`
-//! block and Rust definitions of all the things in the `extern "Rust"` block,
-//! and get to call back and forth safely.
+//! Now we simply provide Rust definitions of all the things in the `extern
+//! "Rust"` block and C++ definitions of all the things in the `extern "C++"`
+//! block, and get to call back and forth safely.
//!
//! Here are links to the complete set of source files involved in the demo:
//!
-//! - [demo-rs/src/main.rs](https://github.com/dtolnay/cxx/blob/master/demo-rs/src/main.rs)
-//! - [demo-rs/build.rs](https://github.com/dtolnay/cxx/blob/master/demo-rs/build.rs)
-//! - [demo-cxx/demo.h](https://github.com/dtolnay/cxx/blob/master/demo-cxx/demo.h)
-//! - [demo-cxx/demo.cc](https://github.com/dtolnay/cxx/blob/master/demo-cxx/demo.cc)
+//! - [demo/src/main.rs](https://github.com/dtolnay/cxx/blob/master/demo/src/main.rs)
+//! - [demo/build.rs](https://github.com/dtolnay/cxx/blob/master/demo/build.rs)
+//! - [demo/include/blobstore.h](https://github.com/dtolnay/cxx/blob/master/demo/include/blobstore.h)
+//! - [demo/src/blobstore.cc](https://github.com/dtolnay/cxx/blob/master/demo/src/blobstore.cc)
//!
//! To look at the code generated in both languages for the example by the CXX
//! code generators:
@@ -124,10 +130,10 @@
//! ```console
//! # run Rust code generator and print to stdout
//! # (requires https://github.com/dtolnay/cargo-expand)
-//! $ cargo expand --manifest-path demo-rs/Cargo.toml
+//! $ cargo expand --manifest-path demo/Cargo.toml
//!
//! # run C++ code generator and print to stdout
-//! $ cargo run --manifest-path gen/cmd/Cargo.toml -- demo-rs/src/main.rs
+//! $ cargo run --manifest-path gen/cmd/Cargo.toml -- demo/src/main.rs
//! ```
//!
//! <br>
@@ -229,7 +235,7 @@
//! # Cargo.toml
//!
//! [build-dependencies]
-//! cxx-build = "0.3"
+//! cxx-build = "0.5"
//! ```
//!
//! ```no_run
@@ -237,13 +243,13 @@
//!
//! fn main() {
//! cxx_build::bridge("src/main.rs") // returns a cc::Build
-//! .file("../demo-cxx/demo.cc")
+//! .file("src/demo.cc")
//! .flag_if_supported("-std=c++11")
//! .compile("cxxbridge-demo");
//!
//! println!("cargo:rerun-if-changed=src/main.rs");
-//! println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
-//! println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
+//! println!("cargo:rerun-if-changed=src/demo.cc");
+//! println!("cargo:rerun-if-changed=include/demo.h");
//! }
//! ```
//!
@@ -254,7 +260,7 @@
//! For use in non-Cargo builds like Bazel or Buck, CXX provides an alternate
//! way of invoking the C++ code generator as a standalone command line tool.
//! The tool is packaged as the `cxxbridge-cmd` crate on crates.io or can be
-//! built from the *cmd* directory of [https://github.com/dtolnay/cxx].
+//! built from the *gen/cmd* directory of <https://github.com/dtolnay/cxx>.
//!
//! ```bash
//! $ cargo install cxxbridge-cmd
@@ -329,7 +335,7 @@
//! </table>
//!
//! The C++ API of the `rust` namespace is defined by the *include/cxx.h* file
-//! in [https://github.com/dtolnay/cxx]. You will need to include this header in
+//! in <https://github.com/dtolnay/cxx>. You will need to include this header in
//! your C++ code when working with those types.
//!
//! The following types are intended to be supported "soon" but are just not
@@ -341,14 +347,14 @@
//! <tr><td>BTreeMap<K, V></td><td><sup><i>tbd</i></sup></td></tr>
//! <tr><td>HashMap<K, V></td><td><sup><i>tbd</i></sup></td></tr>
//! <tr><td>Arc<T></td><td><sup><i>tbd</i></sup></td></tr>
+//! <tr><td>Option<T></td><td><sup><i>tbd</i></sup></td></tr>
//! <tr><td><sup><i>tbd</i></sup></td><td>std::map<K, V></td></tr>
//! <tr><td><sup><i>tbd</i></sup></td><td>std::unordered_map<K, V></td></tr>
//! <tr><td><sup><i>tbd</i></sup></td><td>std::shared_ptr<T></td></tr>
//! </table>
-//!
-//! [https://github.com/dtolnay/cxx]: https://github.com/dtolnay/cxx
-#![doc(html_root_url = "https://docs.rs/cxx/0.3.4")]
+#![no_std]
+#![doc(html_root_url = "https://docs.rs/cxx/0.5.9")]
#![deny(improper_ctypes)]
#![allow(non_camel_case_types)]
#![allow(
@@ -367,6 +373,12 @@
clippy::useless_let_if_seq
)]
+#[cfg(built_with_cargo)]
+extern crate link_cplusplus;
+
+extern crate alloc;
+extern crate std;
+
#[macro_use]
mod macros;
@@ -381,27 +393,41 @@
mod rust_str;
mod rust_string;
mod rust_vec;
+mod symbols;
mod unique_ptr;
mod unwind;
-#[cfg(not(no_export_symbols))]
-mod symbols;
-
pub use crate::cxx_string::CxxString;
pub use crate::cxx_vector::CxxVector;
pub use crate::exception::Exception;
-pub use crate::extern_type::ExternType;
+pub use crate::extern_type::{kind, ExternType};
pub use crate::unique_ptr::UniquePtr;
pub use cxxbridge_macro::bridge;
/// For use in impls of the `ExternType` trait. See [`ExternType`].
+///
+/// [`ExternType`]: trait.ExternType.html
pub use cxxbridge_macro::type_id;
+/// Synonym for `CxxString`.
+///
+/// To avoid confusion with Rust's standard library string you probably
+/// shouldn't import this type with `use`. Instead, write `cxx::String`, or
+/// import and use `CxxString`.
+pub type String = CxxString;
+
+/// Synonym for `CxxVector`.
+///
+/// To avoid confusion with Rust's standard library vector you probably
+/// shouldn't import this type with `use`. Instead, write `cxx::Vector<T>`, or
+/// import and use `CxxVector`.
+pub type Vector<T> = CxxVector<T>;
+
// Not public API.
#[doc(hidden)]
pub mod private {
pub use crate::cxx_vector::VectorElement;
- pub use crate::extern_type::verify_extern_type;
+ pub use crate::extern_type::{verify_extern_kind, verify_extern_type};
pub use crate::function::FatFunction;
pub use crate::opaque::Opaque;
pub use crate::result::{r#try, Result};
diff --git a/src/macros/assert.rs b/src/macros/assert.rs
index 738e5bb..5d5ea9e 100644
--- a/src/macros/assert.rs
+++ b/src/macros/assert.rs
@@ -1,3 +1,5 @@
+#[macro_export]
+#[doc(hidden)]
macro_rules! const_assert_eq {
($left:expr, $right:expr $(,)?) => {
const _: [(); $left] = [(); $right];
diff --git a/src/macros/concat.rs b/src/macros/concat.rs
index e67e50d..5ee77c5 100644
--- a/src/macros/concat.rs
+++ b/src/macros/concat.rs
@@ -1,3 +1,5 @@
+#[macro_export]
+#[doc(hidden)]
macro_rules! attr {
(#[$name:ident = $value:expr] $($rest:tt)*) => {
#[$name = $value]
diff --git a/src/opaque.rs b/src/opaque.rs
index 0ff6bb9..bad57e7 100644
--- a/src/opaque.rs
+++ b/src/opaque.rs
@@ -1,4 +1,4 @@
-use std::mem;
+use core::mem;
// . size = 0
// . align = 1
diff --git a/src/result.rs b/src/result.rs
index 0373bd8..fcced76 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -1,10 +1,12 @@
use crate::exception::Exception;
use crate::rust_str::RustStr;
-use std::fmt::Display;
-use std::ptr;
-use std::result::Result as StdResult;
-use std::slice;
-use std::str;
+use alloc::boxed::Box;
+use alloc::string::{String, ToString};
+use core::fmt::Display;
+use core::ptr;
+use core::result::Result as StdResult;
+use core::slice;
+use core::str;
#[repr(C)]
pub union Result {
@@ -32,7 +34,7 @@
let len = msg.len();
extern "C" {
- #[link_name = "cxxbridge03$error"]
+ #[link_name = "cxxbridge05$error"]
fn error(ptr: *const u8, len: usize) -> *const u8;
}
diff --git a/src/rust_sliceu8.rs b/src/rust_sliceu8.rs
index f509c7f..32f8798 100644
--- a/src/rust_sliceu8.rs
+++ b/src/rust_sliceu8.rs
@@ -1,6 +1,6 @@
-use std::mem;
-use std::ptr::NonNull;
-use std::slice;
+use core::mem;
+use core::ptr::NonNull;
+use core::slice;
// Not necessarily ABI compatible with &[u8]. Codegen performs the translation.
#[repr(C)]
diff --git a/src/rust_str.rs b/src/rust_str.rs
index b944ede..38e5cab 100644
--- a/src/rust_str.rs
+++ b/src/rust_str.rs
@@ -1,7 +1,7 @@
-use std::mem;
-use std::ptr::NonNull;
-use std::slice;
-use std::str;
+use core::mem;
+use core::ptr::NonNull;
+use core::slice;
+use core::str;
// Not necessarily ABI compatible with &str. Codegen performs the translation.
#[repr(C)]
diff --git a/src/rust_string.rs b/src/rust_string.rs
index a923ced..a5fa3f4 100644
--- a/src/rust_string.rs
+++ b/src/rust_string.rs
@@ -1,4 +1,5 @@
-use std::mem;
+use alloc::string::String;
+use core::mem;
#[repr(C)]
pub struct RustString {
@@ -14,6 +15,10 @@
unsafe { &*(s as *const String as *const RustString) }
}
+ pub fn from_mut(s: &mut String) -> &mut Self {
+ unsafe { &mut *(s as *mut String as *mut RustString) }
+ }
+
pub fn into_string(self) -> String {
self.repr
}
diff --git a/src/rust_vec.rs b/src/rust_vec.rs
index 4c5035d..8ddc4a7 100644
--- a/src/rust_vec.rs
+++ b/src/rust_vec.rs
@@ -1,6 +1,11 @@
+use crate::rust_string::RustString;
+use alloc::string::String;
+use alloc::vec::Vec;
+use core::mem::ManuallyDrop;
+
#[repr(C)]
pub struct RustVec<T> {
- repr: Vec<T>,
+ pub(crate) repr: Vec<T>,
}
impl<T> RustVec<T> {
@@ -16,6 +21,10 @@
unsafe { &*(v as *const Vec<T> as *const RustVec<T>) }
}
+ pub fn from_mut(v: &mut Vec<T>) -> &mut Self {
+ unsafe { &mut *(v as *mut Vec<T> as *mut RustVec<T>) }
+ }
+
pub fn into_vec(self) -> Vec<T> {
self.repr
}
@@ -35,4 +44,49 @@
pub fn as_ptr(&self) -> *const T {
self.repr.as_ptr()
}
+
+ pub fn reserve_total(&mut self, cap: usize) {
+ let len = self.repr.len();
+ if cap > len {
+ self.repr.reserve(cap - len);
+ }
+ }
+
+ pub unsafe fn set_len(&mut self, len: usize) {
+ self.repr.set_len(len);
+ }
+}
+
+impl RustVec<RustString> {
+ pub fn from_vec_string(v: Vec<String>) -> Self {
+ let mut v = ManuallyDrop::new(v);
+ let ptr = v.as_mut_ptr().cast::<RustString>();
+ let len = v.len();
+ let cap = v.capacity();
+ Self::from(unsafe { Vec::from_raw_parts(ptr, len, cap) })
+ }
+
+ pub fn from_ref_vec_string(v: &Vec<String>) -> &Self {
+ Self::from_ref(unsafe { &*(v as *const Vec<String> as *const Vec<RustString>) })
+ }
+
+ pub fn from_mut_vec_string(v: &mut Vec<String>) -> &mut Self {
+ Self::from_mut(unsafe { &mut *(v as *mut Vec<String> as *mut Vec<RustString>) })
+ }
+
+ pub fn into_vec_string(self) -> Vec<String> {
+ let mut v = ManuallyDrop::new(self.repr);
+ let ptr = v.as_mut_ptr().cast::<String>();
+ let len = v.len();
+ let cap = v.capacity();
+ unsafe { Vec::from_raw_parts(ptr, len, cap) }
+ }
+
+ pub fn as_vec_string(&self) -> &Vec<String> {
+ unsafe { &*(&self.repr as *const Vec<RustString> as *const Vec<String>) }
+ }
+
+ pub fn as_mut_vec_string(&mut self) -> &mut Vec<String> {
+ unsafe { &mut *(&mut self.repr as *mut Vec<RustString> as *mut Vec<String>) }
+ }
}
diff --git a/src/symbols/exception.rs b/src/symbols/exception.rs
index 849db3b..b408f25 100644
--- a/src/symbols/exception.rs
+++ b/src/symbols/exception.rs
@@ -1,6 +1,8 @@
-use std::slice;
+use alloc::boxed::Box;
+use alloc::string::String;
+use core::slice;
-#[export_name = "cxxbridge03$exception"]
+#[export_name = "cxxbridge05$exception"]
unsafe extern "C" fn exception(ptr: *const u8, len: usize) -> *const u8 {
let slice = slice::from_raw_parts(ptr, len);
let boxed = String::from_utf8_lossy(slice).into_owned().into_boxed_str();
diff --git a/src/symbols/rust_str.rs b/src/symbols/rust_str.rs
index 6dc04ac..b655381 100644
--- a/src/symbols/rust_str.rs
+++ b/src/symbols/rust_str.rs
@@ -1,7 +1,7 @@
-use std::slice;
-use std::str;
+use core::slice;
+use core::str;
-#[export_name = "cxxbridge03$str$valid"]
+#[export_name = "cxxbridge05$str$valid"]
unsafe extern "C" fn str_valid(ptr: *const u8, len: usize) -> bool {
let slice = slice::from_raw_parts(ptr, len);
str::from_utf8(slice).is_ok()
diff --git a/src/symbols/rust_string.rs b/src/symbols/rust_string.rs
index 63d4ba7..e5ab9ea 100644
--- a/src/symbols/rust_string.rs
+++ b/src/symbols/rust_string.rs
@@ -1,19 +1,21 @@
-use std::mem::{ManuallyDrop, MaybeUninit};
-use std::ptr;
-use std::slice;
-use std::str;
+use alloc::borrow::ToOwned;
+use alloc::string::String;
+use core::mem::{ManuallyDrop, MaybeUninit};
+use core::ptr;
+use core::slice;
+use core::str;
-#[export_name = "cxxbridge03$string$new"]
+#[export_name = "cxxbridge05$string$new"]
unsafe extern "C" fn string_new(this: &mut MaybeUninit<String>) {
ptr::write(this.as_mut_ptr(), String::new());
}
-#[export_name = "cxxbridge03$string$clone"]
+#[export_name = "cxxbridge05$string$clone"]
unsafe extern "C" fn string_clone(this: &mut MaybeUninit<String>, other: &String) {
ptr::write(this.as_mut_ptr(), other.clone());
}
-#[export_name = "cxxbridge03$string$from"]
+#[export_name = "cxxbridge05$string$from"]
unsafe extern "C" fn string_from(
this: &mut MaybeUninit<String>,
ptr: *const u8,
@@ -29,17 +31,17 @@
}
}
-#[export_name = "cxxbridge03$string$drop"]
+#[export_name = "cxxbridge05$string$drop"]
unsafe extern "C" fn string_drop(this: &mut ManuallyDrop<String>) {
ManuallyDrop::drop(this);
}
-#[export_name = "cxxbridge03$string$ptr"]
+#[export_name = "cxxbridge05$string$ptr"]
unsafe extern "C" fn string_ptr(this: &String) -> *const u8 {
this.as_ptr()
}
-#[export_name = "cxxbridge03$string$len"]
+#[export_name = "cxxbridge05$string$len"]
unsafe extern "C" fn string_len(this: &String) -> usize {
this.len()
}
diff --git a/src/symbols/rust_vec.rs b/src/symbols/rust_vec.rs
index 9ce87ab..90fbc54 100644
--- a/src/symbols/rust_vec.rs
+++ b/src/symbols/rust_vec.rs
@@ -1,50 +1,53 @@
-use std::mem;
-use std::ptr;
+use crate::rust_string::RustString;
+use crate::rust_vec::RustVec;
+use alloc::vec::Vec;
+use core::mem;
+use core::ptr;
-#[repr(C)]
-pub struct RustVec<T> {
- repr: Vec<T>,
-}
-
-macro_rules! attr {
- (#[$name:ident = $value:expr] $($rest:tt)*) => {
- #[$name = $value]
- $($rest)*
- };
-}
-
-macro_rules! rust_vec_shims_for_primitive {
- ($ty:ident) => {
+macro_rules! rust_vec_shims {
+ ($segment:expr, $ty:ty) => {
const_assert_eq!(mem::size_of::<[usize; 3]>(), mem::size_of::<Vec<$ty>>());
const_assert_eq!(mem::align_of::<usize>(), mem::align_of::<Vec<$ty>>());
const _: () = {
attr! {
- #[export_name = concat!("cxxbridge03$rust_vec$", stringify!($ty), "$new")]
+ #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$new")]
unsafe extern "C" fn __new(this: *mut RustVec<$ty>) {
ptr::write(this, RustVec { repr: Vec::new() });
}
}
attr! {
- #[export_name = concat!("cxxbridge03$rust_vec$", stringify!($ty), "$drop")]
+ #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$drop")]
unsafe extern "C" fn __drop(this: *mut RustVec<$ty>) {
ptr::drop_in_place(this);
}
}
attr! {
- #[export_name = concat!("cxxbridge03$rust_vec$", stringify!($ty), "$len")]
+ #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$len")]
unsafe extern "C" fn __len(this: *const RustVec<$ty>) -> usize {
(*this).repr.len()
}
}
attr! {
- #[export_name = concat!("cxxbridge03$rust_vec$", stringify!($ty), "$data")]
+ #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$data")]
unsafe extern "C" fn __data(this: *const RustVec<$ty>) -> *const $ty {
(*this).repr.as_ptr()
}
}
attr! {
- #[export_name = concat!("cxxbridge03$rust_vec$", stringify!($ty), "$stride")]
+ #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$reserve_total")]
+ unsafe extern "C" fn __reserve_total(this: *mut RustVec<$ty>, cap: usize) {
+ (*this).reserve_total(cap);
+ }
+ }
+ attr! {
+ #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$set_len")]
+ unsafe extern "C" fn __set_len(this: *mut RustVec<$ty>, len: usize) {
+ (*this).repr.set_len(len);
+ }
+ }
+ attr! {
+ #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$stride")]
unsafe extern "C" fn __stride() -> usize {
mem::size_of::<$ty>()
}
@@ -53,6 +56,12 @@
};
}
+macro_rules! rust_vec_shims_for_primitive {
+ ($ty:ident) => {
+ rust_vec_shims!(stringify!($ty), $ty);
+ };
+}
+
rust_vec_shims_for_primitive!(bool);
rust_vec_shims_for_primitive!(u8);
rust_vec_shims_for_primitive!(u16);
@@ -64,3 +73,5 @@
rust_vec_shims_for_primitive!(i64);
rust_vec_shims_for_primitive!(f32);
rust_vec_shims_for_primitive!(f64);
+
+rust_vec_shims!("string", RustString);
diff --git a/src/symbols/symbols.rs b/src/symbols/symbols.rs
deleted file mode 100644
index 2c052ec..0000000
--- a/src/symbols/symbols.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-#[path = "../macros/mod.rs"]
-#[macro_use]
-mod macros;
-
-include!("mod.rs");
diff --git a/src/unique_ptr.rs b/src/unique_ptr.rs
index 34a1f0e..3a5fc66 100644
--- a/src/unique_ptr.rs
+++ b/src/unique_ptr.rs
@@ -1,11 +1,13 @@
use crate::cxx_string::CxxString;
use crate::cxx_vector::{self, CxxVector, VectorElement};
-use std::ffi::c_void;
-use std::fmt::{self, Debug, Display};
-use std::marker::PhantomData;
-use std::mem;
-use std::ops::{Deref, DerefMut};
-use std::ptr;
+use crate::kind::Trivial;
+use crate::ExternType;
+use core::ffi::c_void;
+use core::fmt::{self, Debug, Display};
+use core::marker::PhantomData;
+use core::mem;
+use core::ops::{Deref, DerefMut};
+use core::ptr;
/// Binding to C++ `std::unique_ptr<T, std::default_delete<T>>`.
#[repr(C)]
@@ -32,7 +34,10 @@
}
/// Allocates memory on the heap and makes a UniquePtr pointing to it.
- pub fn new(value: T) -> Self {
+ pub fn new(value: T) -> Self
+ where
+ T: ExternType<Kind = Trivial>,
+ {
UniquePtr {
repr: T::__new(value),
ty: PhantomData,
@@ -174,15 +179,15 @@
}
extern "C" {
- #[link_name = "cxxbridge03$unique_ptr$std$string$null"]
+ #[link_name = "cxxbridge05$unique_ptr$std$string$null"]
fn unique_ptr_std_string_null(this: *mut *mut c_void);
- #[link_name = "cxxbridge03$unique_ptr$std$string$raw"]
+ #[link_name = "cxxbridge05$unique_ptr$std$string$raw"]
fn unique_ptr_std_string_raw(this: *mut *mut c_void, raw: *mut CxxString);
- #[link_name = "cxxbridge03$unique_ptr$std$string$get"]
+ #[link_name = "cxxbridge05$unique_ptr$std$string$get"]
fn unique_ptr_std_string_get(this: *const *mut c_void) -> *const CxxString;
- #[link_name = "cxxbridge03$unique_ptr$std$string$release"]
+ #[link_name = "cxxbridge05$unique_ptr$std$string$release"]
fn unique_ptr_std_string_release(this: *mut *mut c_void) -> *mut CxxString;
- #[link_name = "cxxbridge03$unique_ptr$std$string$drop"]
+ #[link_name = "cxxbridge05$unique_ptr$std$string$drop"]
fn unique_ptr_std_string_drop(this: *mut *mut c_void);
}
@@ -190,7 +195,9 @@
const __NAME: &'static dyn Display = &"CxxString";
fn __null() -> *mut c_void {
let mut repr = ptr::null_mut::<c_void>();
- unsafe { unique_ptr_std_string_null(&mut repr) }
+ unsafe {
+ unique_ptr_std_string_null(&mut repr);
+ }
repr
}
unsafe fn __raw(raw: *mut Self) -> *mut c_void {
diff --git a/syntax/atom.rs b/syntax/atom.rs
index 6e5fa88..7d0ef6b 100644
--- a/syntax/atom.rs
+++ b/syntax/atom.rs
@@ -81,7 +81,7 @@
impl PartialEq<Atom> for Type {
fn eq(&self, atom: &Atom) -> bool {
match self {
- Type::Ident(ident) => ident == atom,
+ Type::Ident(ident) => ident.rust == atom,
_ => false,
}
}
diff --git a/syntax/attrs.rs b/syntax/attrs.rs
index 4c76641..af73b7a 100644
--- a/syntax/attrs.rs
+++ b/syntax/attrs.rs
@@ -1,3 +1,4 @@
+use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::Atom::{self, *};
use crate::syntax::{Derive, Doc};
@@ -10,19 +11,9 @@
pub doc: Option<&'a mut Doc>,
pub derives: Option<&'a mut Vec<Derive>>,
pub repr: Option<&'a mut Option<Atom>>,
-}
-
-pub(super) fn parse_doc(cx: &mut Errors, attrs: &[Attribute]) -> Doc {
- let mut doc = Doc::new();
- parse(
- cx,
- attrs,
- Parser {
- doc: Some(&mut doc),
- ..Parser::default()
- },
- );
- doc
+ pub cxx_name: Option<&'a mut Option<Ident>>,
+ pub rust_name: Option<&'a mut Option<Ident>>,
+ pub namespace: Option<&'a mut Namespace>,
}
pub(super) fn parse(cx: &mut Errors, attrs: &[Attribute], mut parser: Parser) {
@@ -57,6 +48,36 @@
}
Err(err) => return cx.push(err),
}
+ } else if attr.path.is_ident("cxx_name") {
+ match parse_function_alias_attribute.parse2(attr.tokens.clone()) {
+ Ok(attr) => {
+ if let Some(cxx_name) = &mut parser.cxx_name {
+ **cxx_name = Some(attr);
+ continue;
+ }
+ }
+ Err(err) => return cx.push(err),
+ }
+ } else if attr.path.is_ident("rust_name") {
+ match parse_function_alias_attribute.parse2(attr.tokens.clone()) {
+ Ok(attr) => {
+ if let Some(rust_name) = &mut parser.rust_name {
+ **rust_name = Some(attr);
+ continue;
+ }
+ }
+ Err(err) => return cx.push(err),
+ }
+ } 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) => return cx.push(err),
+ }
}
return cx.error(attr, "unsupported attribute");
}
@@ -99,3 +120,19 @@
"unrecognized repr",
))
}
+
+fn parse_function_alias_attribute(input: ParseStream) -> Result<Ident> {
+ input.parse::<Token![=]>()?;
+ if input.peek(LitStr) {
+ let lit: LitStr = input.parse()?;
+ lit.parse()
+ } else {
+ input.parse()
+ }
+}
+
+fn parse_namespace_attribute(input: ParseStream) -> Result<Namespace> {
+ input.parse::<Token![=]>()?;
+ let namespace = input.parse::<Namespace>()?;
+ Ok(namespace)
+}
diff --git a/syntax/check.rs b/syntax/check.rs
index c54430c..e8a6448 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -1,24 +1,22 @@
use crate::syntax::atom::Atom::{self, *};
-use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
+use crate::syntax::types::TrivialReason;
use crate::syntax::{
- error, ident, Api, Enum, ExternFn, ExternType, Lang, Receiver, Ref, Slice, Struct, Ty1, Type,
- Types,
+ error, ident, Api, Enum, ExternFn, ExternType, Impl, Lang, Receiver, Ref, Slice, Struct, Ty1,
+ Type, Types,
};
use proc_macro2::{Delimiter, Group, Ident, TokenStream};
use quote::{quote, ToTokens};
use std::fmt::Display;
pub(crate) struct Check<'a> {
- namespace: &'a Namespace,
apis: &'a [Api],
types: &'a Types<'a>,
errors: &'a mut Errors,
}
-pub(crate) fn typecheck(cx: &mut Errors, namespace: &Namespace, apis: &[Api], types: &Types) {
+pub(crate) fn typecheck(cx: &mut Errors, apis: &[Api], types: &Types) {
do_typecheck(&mut Check {
- namespace,
apis,
types,
errors: cx,
@@ -26,11 +24,11 @@
}
fn do_typecheck(cx: &mut Check) {
- ident::check_all(cx, cx.namespace, cx.apis);
+ ident::check_all(cx, cx.apis);
for ty in cx.types {
match ty {
- Type::Ident(ident) => check_type_ident(cx, ident),
+ Type::Ident(ident) => check_type_ident(cx, &ident.rust),
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),
@@ -45,8 +43,9 @@
match api {
Api::Struct(strct) => check_api_struct(cx, strct),
Api::Enum(enm) => check_api_enum(cx, enm),
- Api::CxxType(ty) | Api::RustType(ty) => check_api_type(cx, ty),
+ Api::CxxType(ety) | Api::RustType(ety) => check_api_type(cx, ety),
Api::CxxFunction(efn) | Api::RustFunction(efn) => check_api_fn(cx, efn),
+ Api::Impl(imp) => check_api_impl(cx, imp),
_ => {}
}
}
@@ -65,17 +64,21 @@
&& !cx.types.cxx.contains(ident)
&& !cx.types.rust.contains(ident)
{
- cx.error(ident, "unsupported type");
+ 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) {
+ if cx.types.cxx.contains(&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).is_none() {
+ if Atom::from(&ident.rust).is_none() {
return;
}
}
@@ -85,15 +88,19 @@
fn check_type_rust_vec(cx: &mut Check, ty: &Ty1) {
if let Type::Ident(ident) = &ty.inner {
- if cx.types.cxx.contains(ident) {
+ if cx.types.cxx.contains(&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) {
+ 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) => return,
- Some(Bool) | Some(RustString) => { /* todo */ }
+ | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32) | Some(F64)
+ | Some(RustString) => return,
+ Some(Bool) => { /* todo */ }
Some(CxxString) => {}
}
}
@@ -103,11 +110,11 @@
fn check_type_unique_ptr(cx: &mut Check, ptr: &Ty1) {
if let Type::Ident(ident) = &ptr.inner {
- if cx.types.rust.contains(ident) {
+ if cx.types.rust.contains(&ident.rust) {
cx.error(ptr, "unique_ptr of a Rust type is not supported yet");
}
- match Atom::from(ident) {
+ match Atom::from(&ident.rust) {
None | Some(CxxString) => return,
_ => {}
}
@@ -120,17 +127,17 @@
fn check_type_cxx_vector(cx: &mut Check, ptr: &Ty1) {
if let Type::Ident(ident) = &ptr.inner {
- if cx.types.rust.contains(ident) {
+ if cx.types.rust.contains(&ident.rust) {
cx.error(
ptr,
"C++ vector containing a Rust type is not supported yet",
);
}
- match Atom::from(ident) {
+ 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) => return,
- Some(CxxString) => { /* todo */ }
+ | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32) | Some(F64)
+ | Some(CxxString) => return,
Some(Bool) | Some(RustString) => {}
}
}
@@ -160,13 +167,21 @@
}
fn check_api_struct(cx: &mut Check, strct: &Struct) {
- check_reserved_name(cx, &strct.ident);
+ let name = &strct.name;
+ check_reserved_name(cx, &name.rust);
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 field in &strct.fields {
if is_unsized(cx, &field.ty) {
let desc = describe(cx, &field.ty);
@@ -183,7 +198,7 @@
}
fn check_api_enum(cx: &mut Check, enm: &Enum) {
- check_reserved_name(cx, &enm.ident);
+ check_reserved_name(cx, &enm.name.rust);
if enm.variants.is_empty() {
let span = span_for_enum_error(enm);
@@ -191,15 +206,28 @@
}
}
-fn check_api_type(cx: &mut Check, ty: &ExternType) {
- check_reserved_name(cx, &ty.ident);
+fn check_api_type(cx: &mut Check, ety: &ExternType) {
+ check_reserved_name(cx, &ety.name.rust);
+
+ if let Some(reason) = cx.types.required_trivial.get(&ety.name.rust) {
+ let what = match reason {
+ TrivialReason::StructField(strct) => format!("a field of `{}`", strct.name.rust),
+ TrivialReason::FunctionArgument(efn) => format!("an argument of `{}`", efn.name.rust),
+ TrivialReason::FunctionReturn(efn) => format!("a return value of `{}`", efn.name.rust),
+ };
+ let msg = format!(
+ "needs a cxx::ExternType impl in order to be used as {}",
+ what,
+ );
+ cx.error(ety, msg);
+ }
}
fn check_api_fn(cx: &mut Check, efn: &ExternFn) {
if let Some(receiver) = &efn.receiver {
let ref span = span_for_receiver_error(receiver);
- if receiver.ty == "Self" {
+ if receiver.ty.is_self() {
let mutability = match receiver.mutability {
Some(_) => "mut ",
None => "",
@@ -211,9 +239,9 @@
mutability = mutability,
);
cx.error(span, msg);
- } else if !cx.types.structs.contains_key(&receiver.ty)
- && !cx.types.cxx.contains(&receiver.ty)
- && !cx.types.rust.contains(&receiver.ty)
+ } 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");
}
@@ -257,6 +285,18 @@
check_multiple_arg_lifetimes(cx, efn);
}
+fn check_api_impl(cx: &mut Check, imp: &Impl) {
+ if let Type::UniquePtr(ty) | Type::CxxVector(ty) = &imp.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) {
match &efn.ret {
Some(Type::Ref(ty)) if ty.mutability.is_some() => {}
@@ -315,11 +355,17 @@
fn is_unsized(cx: &mut Check, ty: &Type) -> bool {
let ident = match ty {
- Type::Ident(ident) => ident,
+ Type::Ident(ident) => &ident.rust,
Type::CxxVector(_) | Type::Slice(_) | Type::Void(_) => return true,
_ => return false,
};
- ident == CxxString || cx.types.cxx.contains(ident) || cx.types.rust.contains(ident)
+ ident == CxxString
+ || cx.types.cxx.contains(ident)
+ && !cx.types.structs.contains_key(ident)
+ && !cx.types.enums.contains_key(ident)
+ && !(cx.types.aliases.contains_key(ident)
+ && cx.types.required_trivial.contains_key(ident))
+ || cx.types.rust.contains(ident)
}
fn span_for_struct_error(strct: &Struct) -> TokenStream {
@@ -352,16 +398,20 @@
fn describe(cx: &mut Check, ty: &Type) -> String {
match ty {
Type::Ident(ident) => {
- if cx.types.structs.contains_key(ident) {
+ if cx.types.structs.contains_key(&ident.rust) {
"struct".to_owned()
- } else if cx.types.cxx.contains(ident) {
+ } 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.rust.contains(ident) {
+ } 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) == Some(CxxString) {
+ } else if Atom::from(&ident.rust) == Some(CxxString) {
"C++ string".to_owned()
} else {
- ident.to_string()
+ ident.rust.to_string()
}
}
Type::RustBox(_) => "Box".to_owned(),
diff --git a/syntax/error.rs b/syntax/error.rs
index a60b7da..d0ae021 100644
--- a/syntax/error.rs
+++ b/syntax/error.rs
@@ -19,8 +19,10 @@
CXX_STRING_BY_VALUE,
CXX_TYPE_BY_VALUE,
DISCRIMINANT_OVERFLOW,
+ DOT_INCLUDE,
DOUBLE_UNDERSCORE,
RUST_TYPE_BY_VALUE,
+ UNSUPPORTED_TYPE,
USE_NOT_ALLOWED,
];
@@ -54,6 +56,12 @@
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"),
@@ -66,6 +74,12 @@
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"),
diff --git a/syntax/file.rs b/syntax/file.rs
new file mode 100644
index 0000000..fd6dd98
--- /dev/null
+++ b/syntax/file.rs
@@ -0,0 +1,108 @@
+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(item) => Ok(Item::Struct(ItemStruct { attrs, ..item })),
+ RustItem::Enum(item) => Ok(Item::Enum(ItemEnum { attrs, ..item })),
+ RustItem::ForeignMod(item) => Ok(Item::ForeignMod(ItemForeignMod {
+ attrs,
+ unsafety,
+ abi: item.abi,
+ brace_token: item.brace_token,
+ items: item.items,
+ })),
+ RustItem::Impl(item) => Ok(Item::Impl(ItemImpl { attrs, ..item })),
+ RustItem::Use(item) => Ok(Item::Use(ItemUse { attrs, ..item })),
+ other => Ok(Item::Other(other)),
+ }
+ }
+}
diff --git a/syntax/ident.rs b/syntax/ident.rs
index 7545e92..aaf7832 100644
--- a/syntax/ident.rs
+++ b/syntax/ident.rs
@@ -1,6 +1,5 @@
use crate::syntax::check::Check;
-use crate::syntax::namespace::Namespace;
-use crate::syntax::{error, Api};
+use crate::syntax::{error, Api, Pair};
use proc_macro2::Ident;
fn check(cx: &mut Check, ident: &Ident) {
@@ -13,37 +12,40 @@
}
}
-pub(crate) fn check_all(cx: &mut Check, namespace: &Namespace, apis: &[Api]) {
- for segment in namespace {
+fn check_ident(cx: &mut Check, name: &Pair) {
+ for segment in &name.namespace {
check(cx, segment);
}
+ check(cx, &name.cxx);
+}
+pub(crate) fn check_all(cx: &mut Check, apis: &[Api]) {
for api in apis {
match api {
- Api::Include(_) => {}
+ Api::Include(_) | Api::Impl(_) => {}
Api::Struct(strct) => {
- check(cx, &strct.ident);
+ check_ident(cx, &strct.name);
for field in &strct.fields {
check(cx, &field.ident);
}
}
Api::Enum(enm) => {
- check(cx, &enm.ident);
+ check_ident(cx, &enm.name);
for variant in &enm.variants {
check(cx, &variant.ident);
}
}
Api::CxxType(ety) | Api::RustType(ety) => {
- check(cx, &ety.ident);
+ check_ident(cx, &ety.name);
}
Api::CxxFunction(efn) | Api::RustFunction(efn) => {
- check(cx, &efn.ident);
+ check(cx, &efn.name.rust);
for arg in &efn.args {
check(cx, &arg.ident);
}
}
Api::TypeAlias(alias) => {
- check(cx, &alias.ident);
+ check_ident(cx, &alias.name);
}
}
}
diff --git a/syntax/impls.rs b/syntax/impls.rs
index c34e3e5..a4b393a 100644
--- a/syntax/impls.rs
+++ b/syntax/impls.rs
@@ -1,8 +1,27 @@
-use crate::syntax::{ExternFn, Receiver, Ref, Signature, Slice, Ty1, Type};
+use crate::syntax::{ExternFn, Impl, Include, Receiver, Ref, Signature, Slice, Ty1, Type};
+use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::mem;
use std::ops::{Deref, DerefMut};
+impl PartialEq for Include {
+ fn eq(&self, other: &Include) -> 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;
@@ -149,6 +168,7 @@
impl PartialEq for Signature {
fn eq(&self, other: &Signature) -> bool {
let Signature {
+ unsafety,
fn_token: _,
receiver,
args,
@@ -158,6 +178,7 @@
throws_tokens: _,
} = self;
let Signature {
+ unsafety: unsafety2,
fn_token: _,
receiver: receiver2,
args: args2,
@@ -166,7 +187,8 @@
paren_token: _,
throws_tokens: _,
} = other;
- receiver == receiver2
+ unsafety.is_some() == unsafety2.is_some()
+ && receiver == receiver2
&& ret == ret2
&& throws == throws2
&& args.len() == args2.len()
@@ -177,6 +199,7 @@
impl Hash for Signature {
fn hash<H: Hasher>(&self, state: &mut H) {
let Signature {
+ unsafety,
fn_token: _,
receiver,
args,
@@ -185,6 +208,7 @@
paren_token: _,
throws_tokens: _,
} = self;
+ unsafety.is_some().hash(state);
receiver.hash(state);
for arg in args {
arg.hash(state);
@@ -233,3 +257,38 @@
ty.hash(state);
}
}
+
+impl Hash for Impl {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ let Impl {
+ impl_token: _,
+ ty,
+ brace_token: _,
+ } = self;
+ ty.hash(state);
+ }
+}
+
+impl Eq for Impl {}
+
+impl PartialEq for Impl {
+ fn eq(&self, other: &Impl) -> bool {
+ let Impl {
+ impl_token: _,
+ ty,
+ brace_token: _,
+ } = self;
+ let Impl {
+ impl_token: _,
+ ty: ty2,
+ brace_token: _,
+ } = other;
+ ty == ty2
+ }
+}
+
+impl Borrow<Type> for &Impl {
+ fn borrow(&self) -> &Type {
+ &self.ty
+ }
+}
diff --git a/syntax/improper.rs b/syntax/improper.rs
new file mode 100644
index 0000000..6fd3162
--- /dev/null
+++ b/syntax/improper.rs
@@ -0,0 +1,36 @@
+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))
+ }
+ }
+ Type::RustBox(_)
+ | Type::RustVec(_)
+ | Type::Str(_)
+ | Type::Fn(_)
+ | Type::Void(_)
+ | Type::Slice(_)
+ | Type::SliceRefU8(_) => Definite(true),
+ Type::UniquePtr(_) | Type::CxxVector(_) => Definite(false),
+ Type::Ref(ty) => self.determine_improper_ctype(&ty.inner),
+ }
+ }
+}
diff --git a/syntax/mangle.rs b/syntax/mangle.rs
index c9392db..cc5115a 100644
--- a/syntax/mangle.rs
+++ b/syntax/mangle.rs
@@ -1,9 +1,8 @@
-use crate::syntax::namespace::Namespace;
use crate::syntax::symbol::{self, Symbol};
-use crate::syntax::ExternFn;
+use crate::syntax::{ExternFn, Types};
use proc_macro2::Ident;
-const CXXBRIDGE: &str = "cxxbridge03";
+const CXXBRIDGE: &str = "cxxbridge05";
macro_rules! join {
($($segment:expr),*) => {
@@ -11,19 +10,27 @@
};
}
-pub fn extern_fn(namespace: &Namespace, efn: &ExternFn) -> Symbol {
+pub fn extern_fn(efn: &ExternFn, types: &Types) -> Symbol {
match &efn.receiver {
- Some(receiver) => join!(namespace, CXXBRIDGE, receiver.ty, efn.ident),
- None => join!(namespace, CXXBRIDGE, efn.ident),
+ Some(receiver) => {
+ let receiver_ident = types.resolve(&receiver.ty);
+ join!(
+ efn.name.namespace,
+ CXXBRIDGE,
+ receiver_ident.cxx,
+ efn.name.rust
+ )
+ }
+ None => join!(efn.name.namespace, CXXBRIDGE, efn.name.rust),
}
}
// The C half of a function pointer trampoline.
-pub fn c_trampoline(namespace: &Namespace, efn: &ExternFn, var: &Ident) -> Symbol {
- join!(extern_fn(namespace, efn), var, 0)
+pub fn c_trampoline(efn: &ExternFn, var: &Ident, types: &Types) -> Symbol {
+ join!(extern_fn(efn, types), var, 0)
}
// The Rust half of a function pointer trampoline.
-pub fn r_trampoline(namespace: &Namespace, efn: &ExternFn, var: &Ident) -> Symbol {
- join!(extern_fn(namespace, efn), var, 1)
+pub fn r_trampoline(efn: &ExternFn, var: &Ident, types: &Types) -> Symbol {
+ join!(extern_fn(efn, types), var, 1)
}
diff --git a/syntax/mod.rs b/syntax/mod.rs
index 88c96e8..33ed31c 100644
--- a/syntax/mod.rs
+++ b/syntax/mod.rs
@@ -7,19 +7,26 @@
mod discriminant;
mod doc;
pub mod error;
+pub mod file;
pub mod ident;
mod impls;
+mod improper;
pub mod mangle;
+mod names;
pub mod namespace;
mod parse;
+pub mod qualified;
pub mod report;
pub mod set;
pub mod symbol;
mod tokens;
+mod toposort;
pub mod types;
use self::discriminant::Discriminant;
+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};
@@ -32,7 +39,7 @@
pub use self::types::Types;
pub enum Api {
- Include(String),
+ Include(Include),
Struct(Struct),
Enum(Enum),
CxxType(ExternType),
@@ -40,19 +47,38 @@
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 doc: Doc,
pub type_token: Token![type],
- pub ident: Ident,
+ pub name: Pair,
+ pub semi_token: Token![;],
+ pub trusted: bool,
}
pub struct Struct {
pub doc: Doc,
pub derives: Vec<Derive>,
pub struct_token: Token![struct],
- pub ident: Ident,
+ pub name: Pair,
pub brace_token: Brace,
pub fields: Vec<Var>,
}
@@ -60,7 +86,7 @@
pub struct Enum {
pub doc: Doc,
pub enum_token: Token![enum],
- pub ident: Ident,
+ pub name: Pair,
pub brace_token: Brace,
pub variants: Vec<Variant>,
pub repr: Atom,
@@ -69,20 +95,28 @@
pub struct ExternFn {
pub lang: Lang,
pub doc: Doc,
- pub ident: Ident,
+ pub name: Pair,
pub sig: Signature,
pub semi_token: Token![;],
}
pub struct TypeAlias {
+ pub doc: Doc,
pub type_token: Token![type],
- pub ident: Ident,
+ pub name: Pair,
pub eq_token: Token![=],
pub ty: RustType,
pub semi_token: Token![;],
}
+pub struct Impl {
+ pub impl_token: Token![impl],
+ pub ty: Type,
+ pub brace_token: Brace,
+}
+
pub struct Signature {
+ pub unsafety: Option<Token![unsafe]>,
pub fn_token: Token![fn],
pub receiver: Option<Receiver>,
pub args: Punctuated<Var, Token![,]>,
@@ -103,7 +137,7 @@
pub lifetime: Option<Lifetime>,
pub mutability: Option<Token![mut]>,
pub var: Token![self],
- pub ty: Ident,
+ pub ty: ResolvableName,
pub shorthand: bool,
}
@@ -114,7 +148,7 @@
}
pub enum Type {
- Ident(Ident),
+ Ident(ResolvableName),
RustBox(Box<Ty1>),
RustVec(Box<Ty1>),
UniquePtr(Box<Ty1>),
@@ -151,3 +185,19 @@
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: Ident,
+ pub rust: Ident,
+}
+
+// Wrapper for a type which needs to be resolved before it can be printed in
+// C++.
+#[derive(Clone, PartialEq, Hash)]
+pub struct ResolvableName {
+ pub rust: Ident,
+}
diff --git a/syntax/names.rs b/syntax/names.rs
new file mode 100644
index 0000000..25ae9f4
--- /dev/null
+++ b/syntax/names.rs
@@ -0,0 +1,73 @@
+use crate::syntax::{Namespace, Pair, ResolvableName, Symbol, Types};
+use proc_macro2::{Ident, Span};
+use std::iter;
+use syn::Token;
+
+impl Pair {
+ // Use this constructor when the item can't have a different name in Rust
+ // and C++.
+ pub fn new(namespace: Namespace, ident: Ident) -> Self {
+ Self {
+ namespace,
+ cxx: ident.clone(),
+ rust: ident,
+ }
+ }
+
+ // Use this constructor when attributes such as #[rust_name] can be used to
+ // potentially give a different name in Rust vs C++.
+ pub fn new_from_differing_names(
+ namespace: Namespace,
+ cxx_ident: Ident,
+ rust_ident: Ident,
+ ) -> Self {
+ Self {
+ namespace,
+ cxx: cxx_ident,
+ rust: rust_ident,
+ }
+ }
+
+ pub fn to_symbol(&self) -> Symbol {
+ Symbol::from_idents(self.iter_all_segments())
+ }
+
+ pub fn to_fully_qualified(&self) -> String {
+ format!("::{}", self.join("::"))
+ }
+
+ fn iter_all_segments(&self) -> impl Iterator<Item = &Ident> {
+ self.namespace.iter().chain(iter::once(&self.cxx))
+ }
+
+ fn join(&self, sep: &str) -> String {
+ self.iter_all_segments()
+ .map(|s| s.to_string())
+ .collect::<Vec<_>>()
+ .join(sep)
+ }
+}
+
+impl ResolvableName {
+ pub fn new(ident: Ident) -> Self {
+ Self { rust: ident }
+ }
+
+ pub fn make_self(span: Span) -> Self {
+ Self {
+ rust: Token![Self](span).into(),
+ }
+ }
+
+ pub fn is_self(&self) -> bool {
+ self.rust == "Self"
+ }
+
+ pub fn span(&self) -> Span {
+ self.rust.span()
+ }
+
+ pub fn to_symbol(&self, types: &Types) -> Symbol {
+ types.resolve(self).to_symbol()
+ }
+}
diff --git a/syntax/namespace.rs b/syntax/namespace.rs
index e2dce18..07185e1 100644
--- a/syntax/namespace.rs
+++ b/syntax/namespace.rs
@@ -1,42 +1,52 @@
+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, Path, Token};
+use syn::{Ident, Token};
mod kw {
syn::custom_keyword!(namespace);
}
-#[derive(Clone)]
+#[derive(Clone, Default)]
pub struct Namespace {
segments: Vec<Ident>,
}
impl Namespace {
- pub fn none() -> Self {
- Namespace {
- segments: Vec::new(),
- }
- }
+ 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 mut segments = Vec::new();
- if !input.is_empty() {
- input.parse::<kw::namespace>()?;
- input.parse::<Token![=]>()?;
- let path = input.call(Path::parse_mod_style)?;
- for segment in path.segments {
- segments.push(segment.ident);
- }
- input.parse::<Option<Token![,]>>()?;
- }
+ let segments = QualifiedName::parse_quoted_or_unquoted(input)?.segments;
Ok(Namespace { segments })
}
}
@@ -63,3 +73,13 @@
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/syntax/parse.rs b/syntax/parse.rs
index a1d31cf..a5f8bd1 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -1,45 +1,58 @@
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, Doc, Enum, ExternFn, ExternType, Lang, Receiver, Ref, Signature, Slice,
- Struct, Ty1, Type, TypeAlias, Var, Variant,
+ attrs, error, Api, Doc, Enum, ExternFn, ExternType, Impl, Include, IncludeKind, Lang,
+ Namespace, Pair, Receiver, Ref, ResolvableName, Signature, Slice, Struct, Ty1, Type, TypeAlias,
+ Var, Variant,
};
-use proc_macro2::{TokenStream, TokenTree};
+use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use syn::parse::{ParseStream, Parser};
use syn::punctuated::Punctuated;
use syn::{
Abi, Attribute, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType,
- GenericArgument, Ident, Item, ItemEnum, ItemForeignMod, ItemStruct, LitStr, Pat, PathArguments,
- Result, ReturnType, Token, Type as RustType, TypeBareFn, TypePath, TypeReference, TypeSlice,
+ GenericArgument, Ident, ItemEnum, ItemImpl, ItemStruct, LitStr, Pat, PathArguments, Result,
+ ReturnType, Token, Type as RustType, TypeBareFn, TypePath, TypeReference, TypeSlice,
};
pub mod kw {
syn::custom_keyword!(Result);
}
-pub fn parse_items(cx: &mut Errors, items: Vec<Item>) -> Vec<Api> {
+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) {
+ Item::Struct(item) => match parse_struct(cx, item, namespace) {
Ok(strct) => apis.push(strct),
Err(err) => cx.push(err),
},
- Item::Enum(item) => match parse_enum(cx, item) {
+ Item::Enum(item) => match parse_enum(cx, item, namespace) {
Ok(enm) => apis.push(enm),
Err(err) => cx.push(err),
},
- Item::ForeignMod(foreign_mod) => parse_foreign_mod(cx, foreign_mod, &mut apis),
+ Item::ForeignMod(foreign_mod) => {
+ parse_foreign_mod(cx, foreign_mod, &mut apis, trusted, namespace)
+ }
+ Item::Impl(item) => match parse_impl(item, namespace) {
+ Ok(imp) => apis.push(imp),
+ Err(err) => cx.push(err),
+ },
Item::Use(item) => cx.error(item, error::USE_NOT_ALLOWED),
- _ => cx.error(item, "unsupported item"),
+ Item::Other(item) => cx.error(item, "unsupported item"),
}
}
apis
}
-fn parse_struct(cx: &mut Errors, item: ItemStruct) -> Result<Api> {
+fn parse_struct(cx: &mut Errors, item: ItemStruct, namespace: &Namespace) -> Result<Api> {
let generics = &item.generics;
if !generics.params.is_empty() || generics.where_clause.is_some() {
let struct_token = item.struct_token;
@@ -54,44 +67,48 @@
let mut doc = Doc::new();
let mut derives = Vec::new();
+ let mut namespace = namespace.clone();
attrs::parse(
cx,
&item.attrs,
attrs::Parser {
doc: Some(&mut doc),
derives: Some(&mut derives),
+ namespace: Some(&mut namespace),
..Default::default()
},
);
- let fields = match item.fields {
+ 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"))
+ return Err(Error::new_spanned(item, "tuple structs are not supported"));
}
};
+ let fields = named_fields
+ .named
+ .into_iter()
+ .map(|field| {
+ Ok(Var {
+ ident: field.ident.unwrap(),
+ ty: parse_type(&field.ty, &namespace)?,
+ })
+ })
+ .collect::<Result<_>>()?;
+
Ok(Api::Struct(Struct {
doc,
derives,
struct_token: item.struct_token,
- ident: item.ident,
- brace_token: fields.brace_token,
- fields: fields
- .named
- .into_iter()
- .map(|field| {
- Ok(Var {
- ident: field.ident.unwrap(),
- ty: parse_type(&field.ty)?,
- })
- })
- .collect::<Result<_>>()?,
+ name: Pair::new(namespace, item.ident),
+ brace_token: named_fields.brace_token,
+ fields,
}))
}
-fn parse_enum(cx: &mut Errors, item: ItemEnum) -> Result<Api> {
+fn parse_enum(cx: &mut Errors, item: ItemEnum, namespace: &Namespace) -> Result<Api> {
let generics = &item.generics;
if !generics.params.is_empty() || generics.where_clause.is_some() {
let enum_token = item.enum_token;
@@ -106,12 +123,14 @@
let mut doc = Doc::new();
let mut repr = None;
+ let mut namespace = namespace.clone();
attrs::parse(
cx,
&item.attrs,
attrs::Parser {
doc: Some(&mut doc),
repr: Some(&mut repr),
+ namespace: Some(&mut namespace),
..Default::default()
},
);
@@ -162,27 +181,59 @@
Ok(Api::Enum(Enum {
doc,
enum_token,
- ident: item.ident,
+ name: Pair::new(namespace, item.ident),
brace_token,
variants,
repr,
}))
}
-fn parse_foreign_mod(cx: &mut Errors, foreign_mod: ItemForeignMod, out: &mut Vec<Api>) {
- let lang = match parse_lang(foreign_mod.abi) {
+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) => match parse_extern_type(cx, foreign, lang) {
- Ok(ety) => items.push(ety),
- Err(err) => cx.push(err),
- },
- ForeignItem::Fn(foreign) => match parse_extern_fn(cx, foreign, lang) {
+ ForeignItem::Type(foreign) => {
+ match parse_extern_type(cx, foreign, lang, trusted, &namespace) {
+ Ok(ety) => items.push(ety),
+ Err(err) => cx.push(err),
+ }
+ }
+ ForeignItem::Fn(foreign) => match parse_extern_fn(cx, foreign, lang, &namespace) {
Ok(efn) => items.push(efn),
Err(err) => cx.push(err),
},
@@ -192,17 +243,19 @@
Err(err) => cx.push(err),
}
}
- ForeignItem::Verbatim(tokens) => match parse_extern_verbatim(cx, tokens, lang) {
- Ok(api) => items.push(api),
- Err(err) => cx.push(err),
- },
+ ForeignItem::Verbatim(tokens) => {
+ match parse_extern_verbatim(cx, tokens, lang, &namespace) {
+ Ok(api) => items.push(api),
+ Err(err) => cx.push(err),
+ }
+ }
_ => cx.error(foreign, "unsupported foreign item"),
}
}
let mut types = items.iter().filter_map(|item| match item {
- Api::CxxType(ty) | Api::RustType(ty) => Some(&ty.ident),
- Api::TypeAlias(alias) => Some(&alias.ident),
+ 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()) {
@@ -210,8 +263,8 @@
for item in &mut items {
if let Api::CxxFunction(efn) | Api::RustFunction(efn) = item {
if let Some(receiver) = &mut efn.receiver {
- if receiver.ty == "Self" {
- receiver.ty = single_type.clone();
+ if receiver.ty.is_self() {
+ receiver.ty = ResolvableName::new(single_type.rust.clone());
}
}
}
@@ -221,7 +274,7 @@
out.extend(items);
}
-fn parse_lang(abi: Abi) -> Result<Lang> {
+fn parse_lang(abi: &Abi) -> Result<Lang> {
let name = match &abi.name {
Some(name) => name,
None => {
@@ -238,10 +291,27 @@
}
}
-fn parse_extern_type(cx: &mut Errors, foreign_type: &ForeignItemType, lang: Lang) -> Result<Api> {
- let doc = attrs::parse_doc(cx, &foreign_type.attrs);
+fn parse_extern_type(
+ cx: &mut Errors,
+ foreign_type: &ForeignItemType,
+ lang: Lang,
+ trusted: bool,
+ namespace: &Namespace,
+) -> Result<Api> {
+ let mut doc = Doc::new();
+ let mut namespace = namespace.clone();
+ attrs::parse(
+ cx,
+ &foreign_type.attrs,
+ attrs::Parser {
+ doc: Some(&mut doc),
+ namespace: Some(&mut namespace),
+ ..Default::default()
+ },
+ );
let type_token = foreign_type.type_token;
let ident = foreign_type.ident.clone();
+ let semi_token = foreign_type.semi_token;
let api_type = match lang {
Lang::Cxx => Api::CxxType,
Lang::Rust => Api::RustType,
@@ -249,11 +319,18 @@
Ok(api_type(ExternType {
doc,
type_token,
- ident,
+ name: Pair::new(namespace, ident),
+ semi_token,
+ trusted,
}))
}
-fn parse_extern_fn(cx: &mut Errors, foreign_fn: &ForeignItemFn, lang: Lang) -> Result<Api> {
+fn parse_extern_fn(
+ cx: &mut Errors,
+ foreign_fn: &ForeignItemFn,
+ lang: Lang,
+ namespace: &Namespace,
+) -> Result<Api> {
let generics = &foreign_fn.sig.generics;
if !generics.params.is_empty() || generics.where_clause.is_some() {
return Err(Error::new_spanned(
@@ -268,6 +345,22 @@
));
}
+ let mut doc = Doc::new();
+ let mut cxx_name = None;
+ let mut rust_name = None;
+ let mut namespace = namespace.clone();
+ attrs::parse(
+ cx,
+ &foreign_fn.attrs,
+ attrs::Parser {
+ doc: Some(&mut doc),
+ cxx_name: Some(&mut cxx_name),
+ rust_name: Some(&mut rust_name),
+ namespace: Some(&mut namespace),
+ ..Default::default()
+ },
+ );
+
let mut receiver = None;
let mut args = Punctuated::new();
for arg in foreign_fn.sig.inputs.pairs() {
@@ -280,7 +373,7 @@
lifetime: lifetime.clone(),
mutability: arg.mutability,
var: arg.self_token,
- ty: Token![Self](arg.self_token.span).into(),
+ ty: ResolvableName::make_self(arg.self_token.span),
shorthand: true,
});
continue;
@@ -295,7 +388,7 @@
}
_ => return Err(Error::new_spanned(arg, "unsupported signature")),
};
- let ty = parse_type(&arg.ty)?;
+ let ty = parse_type(&arg.ty, &namespace)?;
if ident != "self" {
args.push_value(Var { ident, ty });
if let Some(comma) = comma {
@@ -309,7 +402,7 @@
ampersand: reference.ampersand,
lifetime: reference.lifetime,
mutability: reference.mutability,
- var: Token![self](ident.span()),
+ var: Token![self](ident.rust.span()),
ty: ident,
shorthand: false,
});
@@ -322,11 +415,15 @@
}
let mut throws_tokens = None;
- let ret = parse_return_type(&foreign_fn.sig.output, &mut throws_tokens)?;
+ let ret = parse_return_type(&foreign_fn.sig.output, &mut throws_tokens, &namespace)?;
let throws = throws_tokens.is_some();
- let doc = attrs::parse_doc(cx, &foreign_fn.attrs);
+ let unsafety = foreign_fn.sig.unsafety;
let fn_token = foreign_fn.sig.fn_token;
- let ident = foreign_fn.sig.ident.clone();
+ let name = Pair::new_from_differing_names(
+ namespace,
+ cxx_name.unwrap_or(foreign_fn.sig.ident.clone()),
+ rust_name.unwrap_or(foreign_fn.sig.ident.clone()),
+ );
let paren_token = foreign_fn.sig.paren_token;
let semi_token = foreign_fn.semi_token;
let api_function = match lang {
@@ -337,8 +434,9 @@
Ok(api_function(ExternFn {
lang,
doc,
- ident,
+ name,
sig: Signature {
+ unsafety,
fn_token,
receiver,
args,
@@ -351,7 +449,12 @@
}))
}
-fn parse_extern_verbatim(cx: &mut Errors, tokens: &TokenStream, lang: Lang) -> Result<Api> {
+fn parse_extern_verbatim(
+ cx: &mut Errors,
+ tokens: &TokenStream,
+ lang: Lang,
+ namespace: &Namespace,
+) -> Result<Api> {
// type Alias = crate::path::to::Type;
let parse = |input: ParseStream| -> Result<TypeAlias> {
let attrs = input.call(Attribute::parse_outer)?;
@@ -366,11 +469,22 @@
let eq_token: Token![=] = input.parse()?;
let ty: RustType = input.parse()?;
let semi_token: Token![;] = input.parse()?;
- attrs::parse_doc(cx, &attrs);
+ let mut doc = Doc::new();
+ let mut namespace = namespace.clone();
+ attrs::parse(
+ cx,
+ &attrs,
+ attrs::Parser {
+ doc: Some(&mut doc),
+ namespace: Some(&mut namespace),
+ ..Default::default()
+ },
+ );
Ok(TypeAlias {
+ doc,
type_token,
- ident,
+ name: Pair::new(namespace, ident),
eq_token,
ty,
semi_token,
@@ -389,15 +503,53 @@
}
}
-fn parse_include(input: ParseStream) -> Result<String> {
+fn parse_impl(imp: ItemImpl, namespace: &Namespace) -> Result<Api> {
+ 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"));
+ }
+
+ let self_ty = &imp.self_ty;
+ if let Some((bang, path, for_token)) = &imp.trait_ {
+ let span = quote!(#bang #path #for_token #self_ty);
+ return Err(Error::new_spanned(
+ span,
+ "unexpected impl, expected something like `impl UniquePtr<T> {}`",
+ ));
+ }
+
+ let generics = &imp.generics;
+ if !generics.params.is_empty() || generics.where_clause.is_some() {
+ return Err(Error::new_spanned(
+ imp,
+ "generic parameters on an impl is not supported",
+ ));
+ }
+
+ Ok(Api::Impl(Impl {
+ impl_token: imp.impl_token,
+ ty: parse_type(&self_ty, namespace)?,
+ brace_token: imp.brace_token,
+ }))
+}
+
+fn parse_include(input: ParseStream) -> Result<Include> {
if input.peek(LitStr) {
- return Ok(input.parse::<LitStr>()?.value());
+ 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();
- input.parse::<Token![<]>()?;
- path.push('<');
+
+ let langle: Token![<] = input.parse()?;
while !input.is_empty() && !input.peek(Token![>]) {
let token: TokenTree = input.parse()?;
match token {
@@ -413,29 +565,34 @@
_ => return Err(Error::new(token.span(), "unexpected token in include path")),
}
}
- input.parse::<Token![>]>()?;
- path.push('>');
- return Ok(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> {
+fn parse_type(ty: &RustType, namespace: &Namespace) -> Result<Type> {
match ty {
- RustType::Reference(ty) => parse_type_reference(ty),
- RustType::Path(ty) => parse_type_path(ty),
- RustType::Slice(ty) => parse_type_slice(ty),
- RustType::BareFn(ty) => parse_type_fn(ty),
+ RustType::Reference(ty) => parse_type_reference(ty, namespace),
+ RustType::Path(ty) => parse_type_path(ty, namespace),
+ RustType::Slice(ty) => parse_type_slice(ty, namespace),
+ RustType::BareFn(ty) => parse_type_fn(ty, namespace),
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 inner = parse_type(&ty.elem)?;
+fn parse_type_reference(ty: &TypeReference, namespace: &Namespace) -> Result<Type> {
+ let inner = parse_type(&ty.elem, namespace)?;
let which = match &inner {
- Type::Ident(ident) if ident == "str" => {
+ Type::Ident(ident) if ident.rust == "str" => {
if ty.mutability.is_some() {
return Err(Error::new_spanned(ty, "unsupported type"));
} else {
@@ -443,7 +600,7 @@
}
}
Type::Slice(slice) => match &slice.inner {
- Type::Ident(ident) if ident == U8 && ty.mutability.is_none() => Type::SliceRefU8,
+ Type::Ident(ident) if ident.rust == U8 && ty.mutability.is_none() => Type::SliceRefU8,
_ => Type::Ref,
},
_ => Type::Ref,
@@ -456,17 +613,17 @@
})))
}
-fn parse_type_path(ty: &TypePath) -> Result<Type> {
+fn parse_type_path(ty: &TypePath, namespace: &Namespace) -> 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(ident)),
+ PathArguments::None => return Ok(Type::Ident(ResolvableName::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)?;
+ let inner = parse_type(arg, namespace)?;
return Ok(Type::UniquePtr(Box::new(Ty1 {
name: ident,
langle: generic.lt_token,
@@ -476,7 +633,7 @@
}
} else if ident == "CxxVector" && generic.args.len() == 1 {
if let GenericArgument::Type(arg) = &generic.args[0] {
- let inner = parse_type(arg)?;
+ let inner = parse_type(arg, namespace)?;
return Ok(Type::CxxVector(Box::new(Ty1 {
name: ident,
langle: generic.lt_token,
@@ -486,7 +643,7 @@
}
} else if ident == "Box" && generic.args.len() == 1 {
if let GenericArgument::Type(arg) = &generic.args[0] {
- let inner = parse_type(arg)?;
+ let inner = parse_type(arg, namespace)?;
return Ok(Type::RustBox(Box::new(Ty1 {
name: ident,
langle: generic.lt_token,
@@ -496,7 +653,7 @@
}
} else if ident == "Vec" && generic.args.len() == 1 {
if let GenericArgument::Type(arg) = &generic.args[0] {
- let inner = parse_type(arg)?;
+ let inner = parse_type(arg, namespace)?;
return Ok(Type::RustVec(Box::new(Ty1 {
name: ident,
langle: generic.lt_token,
@@ -512,15 +669,15 @@
Err(Error::new_spanned(ty, "unsupported type"))
}
-fn parse_type_slice(ty: &TypeSlice) -> Result<Type> {
- let inner = parse_type(&ty.elem)?;
+fn parse_type_slice(ty: &TypeSlice, namespace: &Namespace) -> Result<Type> {
+ let inner = parse_type(&ty.elem, namespace)?;
Ok(Type::Slice(Box::new(Slice {
bracket: ty.bracket_token,
inner,
})))
}
-fn parse_type_fn(ty: &TypeBareFn) -> Result<Type> {
+fn parse_type_fn(ty: &TypeBareFn, namespace: &Namespace) -> Result<Type> {
if ty.lifetimes.is_some() {
return Err(Error::new_spanned(
ty,
@@ -538,7 +695,7 @@
.iter()
.enumerate()
.map(|(i, arg)| {
- let ty = parse_type(&arg.ty)?;
+ let ty = parse_type(&arg.ty, namespace)?;
let ident = match &arg.name {
Some(ident) => ident.0.clone(),
None => format_ident!("_{}", i),
@@ -547,9 +704,10 @@
})
.collect::<Result<_>>()?;
let mut throws_tokens = None;
- let ret = parse_return_type(&ty.output, &mut throws_tokens)?;
+ let ret = parse_return_type(&ty.output, &mut throws_tokens, namespace)?;
let throws = throws_tokens.is_some();
Ok(Type::Fn(Box::new(Signature {
+ unsafety: ty.unsafety,
fn_token: ty.fn_token,
receiver: None,
args,
@@ -563,6 +721,7 @@
fn parse_return_type(
ty: &ReturnType,
throws_tokens: &mut Option<(kw::Result, Token![<], Token![>])>,
+ namespace: &Namespace,
) -> Result<Option<Type>> {
let mut ret = match ty {
ReturnType::Default => return Ok(None),
@@ -584,7 +743,7 @@
}
}
}
- match parse_type(ret)? {
+ match parse_type(ret, namespace)? {
Type::Void(_) => Ok(None),
ty => Ok(Some(ty)),
}
diff --git a/syntax/qualified.rs b/syntax/qualified.rs
new file mode 100644
index 0000000..5eefb8d
--- /dev/null
+++ b/syntax/qualified.rs
@@ -0,0 +1,36 @@
+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;
+ input.parse::<Option<Token![::]>>()?;
+ 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() {
+ 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()?;
+ lit.parse_with(Self::parse_unquoted)
+ } else {
+ Self::parse_unquoted(input)
+ }
+ }
+}
diff --git a/syntax/set.rs b/syntax/set.rs
index 688d1c0..6508f55 100644
--- a/syntax/set.rs
+++ b/syntax/set.rs
@@ -1,41 +1,100 @@
-use std::collections::HashSet;
-use std::hash::Hash;
+use std::fmt::{self, Debug};
use std::slice;
-pub struct OrderedSet<'a, T> {
- set: HashSet<&'a T>,
- vec: Vec<&'a T>,
-}
+pub use self::ordered::OrderedSet;
+pub use self::unordered::UnorderedSet;
-impl<'a, T> OrderedSet<'a, T>
-where
- T: Hash + Eq,
-{
- pub fn new() -> Self {
- OrderedSet {
- set: HashSet::new(),
- vec: Vec::new(),
+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()
}
}
- pub fn insert(&mut self, value: &'a T) -> bool {
- let new = self.set.insert(value);
- if new {
- self.vec.push(value);
+ 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 {
+ Iter(self.vec.iter())
}
- new
- }
-
- pub fn contains(&self, value: &T) -> bool {
- self.set.contains(value)
}
}
-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 {
- Iter(self.vec.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)
+ }
}
}
@@ -47,3 +106,12 @@
self.0.next().copied()
}
}
+
+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/syntax/symbol.rs b/syntax/symbol.rs
index 066d238..253f57d 100644
--- a/syntax/symbol.rs
+++ b/syntax/symbol.rs
@@ -1,10 +1,11 @@
use crate::syntax::namespace::Namespace;
+use crate::syntax::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: cxxbridge03$string$new
+// For example: cxxbridge05$string$new
pub struct Symbol(String);
impl Display for Symbol {
@@ -19,12 +20,6 @@
}
}
-impl From<&Ident> for Symbol {
- fn from(ident: &Ident) -> Self {
- Symbol(ident.to_string())
- }
-}
-
impl Symbol {
fn push(&mut self, segment: &dyn Display) {
let len_before = self.0.len();
@@ -34,18 +29,47 @@
self.0.write_fmt(format_args!("{}", segment)).unwrap();
assert!(self.0.len() > len_before);
}
+
+ pub fn from_idents<'a, T: Iterator<Item = &'a Ident>>(it: T) -> 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.to_string()))
+ }
}
-pub trait Segment: Display {
+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 str {}
-impl Segment for usize {}
-impl Segment for Ident {}
-impl Segment for Symbol {}
+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) {
@@ -55,9 +79,16 @@
}
}
+impl Segment for Pair {
+ fn write(&self, symbol: &mut Symbol) {
+ self.namespace.write(symbol);
+ self.cxx.write(symbol);
+ }
+}
+
impl<T> Segment for &'_ T
where
- T: ?Sized + Segment,
+ T: ?Sized + Segment + Display,
{
fn write(&self, symbol: &mut Symbol) {
(**self).write(symbol);
diff --git a/syntax/tokens.rs b/syntax/tokens.rs
index 4ed264a..500ea0b 100644
--- a/syntax/tokens.rs
+++ b/syntax/tokens.rs
@@ -1,7 +1,7 @@
use crate::syntax::atom::Atom::*;
use crate::syntax::{
- Atom, Derive, Enum, ExternFn, ExternType, Receiver, Ref, Signature, Slice, Struct, Ty1, Type,
- TypeAlias, Var,
+ Atom, Derive, Enum, ExternFn, ExternType, Impl, Receiver, Ref, ResolvableName, Signature,
+ Slice, Struct, Ty1, Type, TypeAlias, Var,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote_spanned, ToTokens};
@@ -11,11 +11,11 @@
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Type::Ident(ident) => {
- if ident == CxxString {
- let span = ident.span();
+ if ident.rust == CxxString {
+ let span = ident.rust.span();
tokens.extend(quote_spanned!(span=> ::cxx::));
}
- ident.to_tokens(tokens);
+ ident.rust.to_tokens(tokens);
}
Type::RustBox(ty) | Type::UniquePtr(ty) | Type::CxxVector(ty) | Type::RustVec(ty) => {
ty.to_tokens(tokens)
@@ -85,7 +85,7 @@
fn to_tokens(&self, tokens: &mut TokenStream) {
// Notional token range for error reporting purposes.
self.type_token.to_tokens(tokens);
- self.ident.to_tokens(tokens);
+ self.name.rust.to_tokens(tokens);
}
}
@@ -93,7 +93,7 @@
fn to_tokens(&self, tokens: &mut TokenStream) {
// Notional token range for error reporting purposes.
self.type_token.to_tokens(tokens);
- self.ident.to_tokens(tokens);
+ self.name.rust.to_tokens(tokens);
}
}
@@ -101,7 +101,7 @@
fn to_tokens(&self, tokens: &mut TokenStream) {
// Notional token range for error reporting purposes.
self.struct_token.to_tokens(tokens);
- self.ident.to_tokens(tokens);
+ self.name.rust.to_tokens(tokens);
}
}
@@ -109,7 +109,7 @@
fn to_tokens(&self, tokens: &mut TokenStream) {
// Notional token range for error reporting purposes.
self.enum_token.to_tokens(tokens);
- self.ident.to_tokens(tokens);
+ self.name.rust.to_tokens(tokens);
}
}
@@ -121,6 +121,14 @@
}
}
+impl ToTokens for Impl {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ self.impl_token.to_tokens(tokens);
+ self.ty.to_tokens(tokens);
+ self.brace_token.surround(tokens, |_tokens| {});
+ }
+}
+
impl ToTokens for Signature {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.fn_token.to_tokens(tokens);
@@ -141,6 +149,12 @@
}
}
+impl ToTokens for ResolvableName {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ self.rust.to_tokens(tokens);
+ }
+}
+
pub struct ReceiverType<'a>(&'a Receiver);
impl Receiver {
diff --git a/syntax/toposort.rs b/syntax/toposort.rs
new file mode 100644
index 0000000..876f9ad
--- /dev/null
+++ b/syntax/toposort.rs
@@ -0,0 +1,51 @@
+use crate::syntax::report::Errors;
+use crate::syntax::{Api, Struct, Type, Types};
+use std::collections::btree_map::{BTreeMap as Map, Entry};
+
+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/syntax/types.rs b/syntax/types.rs
index ba9bc78..90a8221 100644
--- a/syntax/types.rs
+++ b/syntax/types.rs
@@ -1,18 +1,28 @@
use crate::syntax::atom::Atom::{self, *};
+use crate::syntax::improper::ImproperCtype;
use crate::syntax::report::Errors;
-use crate::syntax::set::OrderedSet as Set;
-use crate::syntax::{Api, Derive, Enum, Struct, Type, TypeAlias};
+use crate::syntax::set::{OrderedSet as Set, UnorderedSet};
+use crate::syntax::{
+ toposort, Api, Derive, Enum, ExternFn, ExternType, Impl, Pair, ResolvableName, Struct, Type,
+ TypeAlias,
+};
use proc_macro2::Ident;
use quote::ToTokens;
-use std::collections::{BTreeMap as Map, HashSet as UnorderedSet};
+use std::collections::BTreeMap as Map;
pub struct Types<'a> {
- pub all: Set<'a, Type>,
- pub structs: Map<Ident, &'a Struct>,
- pub enums: Map<Ident, &'a Enum>,
- pub cxx: Set<'a, Ident>,
- pub rust: Set<'a, Ident>,
- pub aliases: Map<Ident, &'a TypeAlias>,
+ pub all: Set<&'a Type>,
+ pub structs: Map<&'a Ident, &'a Struct>,
+ pub enums: Map<&'a Ident, &'a Enum>,
+ pub cxx: Set<&'a Ident>,
+ pub rust: Set<&'a Ident>,
+ pub aliases: Map<&'a Ident, &'a TypeAlias>,
+ pub untrusted: Map<&'a Ident, &'a ExternType>,
+ pub required_trivial: Map<&'a Ident, TrivialReason<'a>>,
+ pub explicit_impls: Set<&'a Impl>,
+ pub resolutions: Map<&'a Ident, &'a Pair>,
+ pub struct_improper_ctypes: UnorderedSet<&'a Ident>,
+ pub toposorted_structs: Vec<&'a Struct>,
}
impl<'a> Types<'a> {
@@ -23,8 +33,13 @@
let mut cxx = Set::new();
let mut rust = Set::new();
let mut aliases = Map::new();
+ let mut untrusted = Map::new();
+ let mut explicit_impls = Set::new();
+ let mut resolutions = Map::new();
+ let struct_improper_ctypes = UnorderedSet::new();
+ let toposorted_structs = Vec::new();
- fn visit<'a>(all: &mut Set<'a, Type>, ty: &'a Type) {
+ fn visit<'a>(all: &mut Set<&'a Type>, ty: &'a Type) {
all.insert(ty);
match ty {
Type::Ident(_) | Type::Str(_) | Type::Void(_) | Type::SliceRefU8(_) => {}
@@ -45,51 +60,84 @@
}
}
+ let mut add_resolution = |pair: &'a Pair| {
+ resolutions.insert(&pair.rust, pair);
+ };
+
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.ident;
- if type_names.insert(ident) {
- structs.insert(ident.clone(), strct);
- } else {
+ 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);
}
Api::Enum(enm) => {
- let ident = &enm.ident;
- // We allow declaring the same type as a shared enum and as a Cxxtype, as this
- // means not to emit the C++ enum definition.
- if !type_names.insert(ident) && !cxx.contains(ident) {
+ 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.clone(), enm);
+ enums.insert(ident, enm);
+ add_resolution(&enm.name);
}
Api::CxxType(ety) => {
- let ident = &ety.ident;
- // We allow declaring the same type as a shared enum and as a Cxxtype, as this
- // means not to emit the C++ enum definition.
- if !type_names.insert(ident) && !enums.contains_key(ident) {
+ 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);
}
Api::RustType(ety) => {
- let ident = &ety.ident;
+ let ident = &ety.name.rust;
if !type_names.insert(ident) {
duplicate_name(cx, ety, ident);
}
rust.insert(ident);
+ add_resolution(&ety.name);
}
Api::CxxFunction(efn) | Api::RustFunction(efn) => {
- let ident = &efn.ident;
- if !function_names.insert((&efn.receiver, ident)) {
- duplicate_name(cx, efn, ident);
+ // 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);
@@ -99,33 +147,106 @@
}
}
Api::TypeAlias(alias) => {
- let ident = &alias.ident;
+ let ident = &alias.name.rust;
if !type_names.insert(ident) {
duplicate_name(cx, alias, ident);
}
cxx.insert(ident);
- aliases.insert(ident.clone(), alias);
+ aliases.insert(ident, alias);
+ add_resolution(&alias.name);
+ }
+ Api::Impl(imp) => {
+ visit(&mut all, &imp.ty);
+ explicit_impls.insert(imp);
}
}
}
- Types {
+ // 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 mut required_trivial = Map::new();
+ let mut insist_alias_types_are_trivial = |ty: &'a Type, reason| {
+ if let Type::Ident(ident) = ty {
+ if cxx.contains(&ident.rust) {
+ required_trivial.entry(&ident.rust).or_insert(reason);
+ }
+ }
+ };
+ for api in apis {
+ match api {
+ Api::Struct(strct) => {
+ let reason = TrivialReason::StructField(strct);
+ for field in &strct.fields {
+ insist_alias_types_are_trivial(&field.ty, reason);
+ }
+ }
+ Api::CxxFunction(efn) | Api::RustFunction(efn) => {
+ let reason = TrivialReason::FunctionArgument(efn);
+ for arg in &efn.args {
+ insist_alias_types_are_trivial(&arg.ty, reason);
+ }
+ if let Some(ret) = &efn.ret {
+ let reason = TrivialReason::FunctionReturn(efn);
+ insist_alias_types_are_trivial(&ret, reason);
+ }
+ }
+ _ => {}
+ }
+ }
+
+ let mut types = Types {
all,
structs,
enums,
cxx,
rust,
aliases,
+ untrusted,
+ required_trivial,
+ explicit_impls,
+ resolutions,
+ struct_improper_ctypes,
+ toposorted_structs,
+ };
+
+ types.toposorted_structs = toposort::sort(cx, apis, &types);
+
+ let mut unresolved_structs: Vec<&Ident> = types.structs.keys().copied().collect();
+ 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::Ident(ident) => {
- if let Some(strct) = self.structs.get(ident) {
+ if let Some(strct) = self.structs.get(&ident.rust) {
!self.is_pod(strct)
} else {
- Atom::from(ident) == Some(RustString)
+ Atom::from(&ident.rust) == Some(RustString)
}
}
Type::RustVec(_) => true,
@@ -141,6 +262,25 @@
}
false
}
+
+ // 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),
+ }
+ }
+
+ pub fn resolve(&self, ident: &ResolvableName) -> &Pair {
+ self.resolutions
+ .get(&ident.rust)
+ .expect("Unable to resolve type")
+ }
}
impl<'t, 'a> IntoIterator for &'t Types<'a> {
@@ -151,6 +291,13 @@
}
}
+#[derive(Copy, Clone)]
+pub enum TrivialReason<'a> {
+ StructField(&'a Struct),
+ FunctionArgument(&'a ExternFn),
+ FunctionReturn(&'a ExternFn),
+}
+
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/tests/BUCK b/tests/BUCK
index 41781c8..5bbe500 100644
--- a/tests/BUCK
+++ b/tests/BUCK
@@ -1,12 +1,18 @@
+load("//tools/buck:rust_cxx_bridge.bzl", "rust_cxx_bridge")
+
rust_test(
name = "test",
srcs = ["test.rs"],
- deps = [":ffi"],
+ deps = [
+ ":ffi",
+ "//:cxx",
+ ],
)
rust_library(
name = "ffi",
srcs = [
+ "ffi/extra.rs",
"ffi/lib.rs",
"ffi/module.rs",
],
@@ -21,33 +27,28 @@
name = "impl",
srcs = [
"ffi/tests.cc",
- ":gen-lib-source",
- ":gen-module-source",
+ ":bridge/source",
+ ":extra/source",
+ ":module/source",
],
headers = {
- "ffi/lib.rs.h": ":gen-lib-header",
+ "ffi/lib.rs.h": ":bridge/header",
"ffi/tests.h": "ffi/tests.h",
},
deps = ["//:core"],
)
-genrule(
- name = "gen-lib-header",
- srcs = ["ffi/lib.rs"],
- cmd = "$(exe //:codegen) --header ${SRCS} > ${OUT}",
- out = "lib.rs.h",
+rust_cxx_bridge(
+ name = "bridge",
+ src = "ffi/lib.rs",
)
-genrule(
- name = "gen-lib-source",
- srcs = ["ffi/lib.rs"],
- cmd = "$(exe //:codegen) ${SRCS} > ${OUT}",
- out = "lib.rs.cc",
+rust_cxx_bridge(
+ name = "extra",
+ src = "ffi/extra.rs",
)
-genrule(
- name = "gen-module-source",
- srcs = ["ffi/module.rs"],
- cmd = "$(exe //:codegen) ${SRCS} > ${OUT}",
- out = "module.rs.cc",
+rust_cxx_bridge(
+ name = "module",
+ src = "ffi/module.rs",
)
diff --git a/tests/BUILD b/tests/BUILD
index e1f1637..57ffab9 100644
--- a/tests/BUILD
+++ b/tests/BUILD
@@ -1,14 +1,21 @@
+load("@rules_cc//cc:defs.bzl", "cc_library")
load("//tools/bazel:rust.bzl", "rust_library", "rust_test")
+load("//tools/bazel:rust_cxx_bridge.bzl", "rust_cxx_bridge")
rust_test(
name = "test",
+ size = "small",
srcs = ["test.rs"],
- deps = [":cxx_test_suite"],
+ deps = [
+ ":cxx_test_suite",
+ "//:cxx",
+ ],
)
rust_library(
name = "cxx_test_suite",
srcs = [
+ "ffi/extra.rs",
"ffi/lib.rs",
"ffi/module.rs",
],
@@ -22,42 +29,31 @@
name = "impl",
srcs = [
"ffi/tests.cc",
- ":gen-lib-source",
- ":gen-module-source",
+ ":bridge/source",
+ ":extra/source",
+ ":module/source",
],
hdrs = ["ffi/tests.h"],
deps = [
- ":lib-include",
+ ":bridge/include",
"//:core",
],
)
-genrule(
- name = "gen-lib-header",
- srcs = ["ffi/lib.rs"],
- outs = ["lib.rs.h"],
- cmd = "$(location //:codegen) --header $< > $@",
- tools = ["//:codegen"],
+rust_cxx_bridge(
+ name = "bridge",
+ src = "ffi/lib.rs",
+ deps = [":impl"],
)
-genrule(
- name = "gen-lib-source",
- srcs = ["ffi/lib.rs"],
- outs = ["lib.rs.cc"],
- cmd = "$(location //:codegen) $< > $@",
- tools = ["//:codegen"],
+rust_cxx_bridge(
+ name = "extra",
+ src = "ffi/extra.rs",
+ deps = [":impl"],
)
-cc_library(
- name = "lib-include",
- hdrs = [":gen-lib-header"],
- include_prefix = "tests/ffi",
-)
-
-genrule(
- name = "gen-module-source",
- srcs = ["ffi/module.rs"],
- outs = ["module.rs.cc"],
- cmd = "$(location //:codegen) $< > $@",
- tools = ["//:codegen"],
+rust_cxx_bridge(
+ name = "module",
+ src = "ffi/module.rs",
+ deps = [":impl"],
)
diff --git a/tests/compiletest.rs b/tests/compiletest.rs
index f9aea23..d2b516f 100644
--- a/tests/compiletest.rs
+++ b/tests/compiletest.rs
@@ -1,4 +1,5 @@
#[rustversion::attr(not(nightly), ignore)]
+#[cfg_attr(skip_ui_tests, ignore)]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
diff --git a/tests/cxx_gen.rs b/tests/cxx_gen.rs
new file mode 100644
index 0000000..06d7bc7
--- /dev/null
+++ b/tests/cxx_gen.rs
@@ -0,0 +1,34 @@
+#![allow(clippy::field_reassign_with_default)]
+
+use cxx_gen::{generate_header_and_cc, Opt};
+use std::str;
+
+const BRIDGE0: &str = r#"
+ #[cxx::bridge]
+ mod ffi {
+ extern "C" {
+ pub fn do_cpp_thing(foo: &str);
+ }
+ }
+"#;
+
+#[test]
+fn test_extern_c_function() {
+ let opt = Opt::default();
+ let source = BRIDGE0.parse().unwrap();
+ let generated = generate_header_and_cc(source, &opt).unwrap();
+ let output = str::from_utf8(&generated.implementation).unwrap();
+ // To avoid continual breakage we won't test every byte.
+ // Let's look for the major features.
+ assert!(output.contains("void cxxbridge05$do_cpp_thing(::rust::repr::PtrLen foo)"));
+}
+
+#[test]
+fn test_impl_annotation() {
+ let mut opt = Opt::default();
+ opt.cxx_impl_annotations = Some("ANNOTATION".to_owned());
+ let source = BRIDGE0.parse().unwrap();
+ let generated = generate_header_and_cc(source, &opt).unwrap();
+ let output = str::from_utf8(&generated.implementation).unwrap();
+ assert!(output.contains("ANNOTATION void cxxbridge05$do_cpp_thing(::rust::repr::PtrLen foo)"));
+}
diff --git a/tests/ffi/Cargo.toml b/tests/ffi/Cargo.toml
index c2d8227..e62d090 100644
--- a/tests/ffi/Cargo.toml
+++ b/tests/ffi/Cargo.toml
@@ -12,3 +12,4 @@
[build-dependencies]
cxx-build = { path = "../../gen/build" }
+cxxbridge-flags = { path = "../../flags" }
diff --git a/tests/ffi/build.rs b/tests/ffi/build.rs
index f6fa59e..4b2cbdf 100644
--- a/tests/ffi/build.rs
+++ b/tests/ffi/build.rs
@@ -1,11 +1,14 @@
+use cxx_build::CFG;
+
fn main() {
if cfg!(trybuild) {
return;
}
- let sources = vec!["lib.rs", "module.rs"];
+ CFG.include_prefix = "tests/ffi";
+ let sources = vec!["lib.rs", "extra.rs", "module.rs"];
cxx_build::bridges(sources)
.file("tests.cc")
- .flag_if_supported("-std=c++11")
+ .flag_if_supported(cxxbridge_flags::STD)
.compile("cxx-test-suite");
}
diff --git a/tests/ffi/extra.rs b/tests/ffi/extra.rs
new file mode 100644
index 0000000..cd76a7d
--- /dev/null
+++ b/tests/ffi/extra.rs
@@ -0,0 +1,63 @@
+// Separate mod so that &self in the lib.rs mod has an unambiguous receiver. At
+// the moment, the cxx C++ codegen can't convert more than one cxx::bridge mod
+// per file, so that's why we need to put this outside of lib.rs. All of this
+// could go into module.rs instead, but for now its purpose is narrowly scoped
+// for testing aliasing between cxx::bridge mods, so we'll keep it that way and
+// start a new mod here.
+
+// Rustfmt mangles the extern type alias.
+// https://github.com/rust-lang/rustfmt/issues/4159
+#[rustfmt::skip]
+#[cxx::bridge(namespace = "tests")]
+pub mod ffi2 {
+ impl UniquePtr<D> {}
+ impl UniquePtr<E> {}
+ impl UniquePtr<F> {}
+ impl UniquePtr<G> {}
+
+ extern "C" {
+ include!("tests/ffi/tests.h");
+
+ type D = crate::other::D;
+ type E = crate::other::E;
+ #[namespace = "F"]
+ type F = crate::other::f::F;
+ #[namespace = "G"]
+ type G = crate::other::G;
+
+ #[namespace = "H"]
+ type H;
+
+ fn c_take_trivial_ptr(d: UniquePtr<D>);
+ fn c_take_trivial_ref(d: &D);
+ fn c_take_trivial(d: D);
+ fn c_take_trivial_ns_ptr(g: UniquePtr<G>);
+ fn c_take_trivial_ns_ref(g: &G);
+ fn c_take_trivial_ns(g: G);
+ fn c_take_opaque_ptr(e: UniquePtr<E>);
+ fn c_take_opaque_ref(e: &E);
+ fn c_take_opaque_ns_ptr(e: UniquePtr<F>);
+ fn c_take_opaque_ns_ref(e: &F);
+ fn c_return_trivial_ptr() -> UniquePtr<D>;
+ fn c_return_trivial() -> D;
+ fn c_return_trivial_ns_ptr() -> UniquePtr<G>;
+ fn c_return_trivial_ns() -> G;
+ fn c_return_opaque_ptr() -> UniquePtr<E>;
+ fn c_return_ns_opaque_ptr() -> UniquePtr<F>;
+ fn c_return_ns_unique_ptr() -> UniquePtr<H>;
+ fn c_take_ref_ns_c(h: &H);
+
+ #[namespace = "other"]
+ fn ns_c_take_trivial(d: D);
+ #[namespace = "other"]
+ fn ns_c_return_trivial() -> D;
+
+ #[namespace = "I"]
+ type I;
+
+ fn get(self: &I) -> u32;
+
+ #[namespace = "I"]
+ fn ns_c_return_unique_ptr_ns() -> UniquePtr<I>;
+ }
+}
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 84a746b..4c7cbe4 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -1,26 +1,121 @@
#![allow(
clippy::boxed_local,
+ clippy::just_underscores_and_digits,
clippy::ptr_arg,
clippy::trivially_copy_pass_by_ref
)]
+pub mod extra;
pub mod module;
-use cxx::{CxxString, UniquePtr};
+use cxx::{CxxString, CxxVector, UniquePtr};
use std::fmt::{self, Display};
-#[cxx::bridge(namespace = tests)]
+mod other {
+ use cxx::kind::{Opaque, Trivial};
+ use cxx::{type_id, CxxString, ExternType};
+
+ #[repr(C)]
+ pub struct D {
+ pub d: u64,
+ }
+
+ #[repr(C)]
+ pub struct E {
+ e: u64,
+ e_str: CxxString,
+ }
+
+ pub mod f {
+ use cxx::kind::Opaque;
+ use cxx::{type_id, CxxString, ExternType};
+
+ #[repr(C)]
+ pub struct F {
+ e: u64,
+ e_str: CxxString,
+ }
+
+ unsafe impl ExternType for F {
+ type Id = type_id!("F::F");
+ type Kind = Opaque;
+ }
+ }
+
+ #[repr(C)]
+ pub struct G {
+ pub g: u64,
+ }
+
+ unsafe impl ExternType for G {
+ type Id = type_id!("G::G");
+ type Kind = Trivial;
+ }
+
+ unsafe impl ExternType for D {
+ type Id = type_id!("tests::D");
+ type Kind = Trivial;
+ }
+
+ unsafe impl ExternType for E {
+ type Id = type_id!("tests::E");
+ type Kind = Opaque;
+ }
+}
+
+#[cxx::bridge(namespace = "tests")]
pub mod ffi {
+ #[derive(Clone)]
struct Shared {
z: usize,
}
+ struct SharedString {
+ msg: String,
+ }
+
enum Enum {
AVal,
BVal = 2020,
CVal,
}
+ #[namespace = "A"]
+ #[derive(Clone)]
+ struct AShared {
+ z: usize,
+ }
+
+ #[namespace = "A"]
+ enum AEnum {
+ AAVal,
+ ABVal = 2020,
+ ACVal,
+ }
+
+ #[namespace = "A::B"]
+ enum ABEnum {
+ ABAVal,
+ ABBVal = 2020,
+ ABCVal,
+ }
+
+ #[namespace = "A::B"]
+ #[derive(Clone)]
+ struct ABShared {
+ z: usize,
+ }
+
+ #[namespace = "first"]
+ struct First {
+ second: Box<Second>,
+ }
+
+ #[namespace = "second"]
+ struct Second {
+ i: i32,
+ }
+
extern "C" {
include!("tests/ffi/tests.h");
@@ -31,20 +126,29 @@
fn c_return_box() -> Box<R>;
fn c_return_unique_ptr() -> UniquePtr<C>;
fn c_return_ref(shared: &Shared) -> &usize;
+ fn c_return_mut(shared: &mut Shared) -> &mut usize;
fn c_return_str(shared: &Shared) -> &str;
fn c_return_sliceu8(shared: &Shared) -> &[u8];
fn c_return_rust_string() -> String;
fn c_return_unique_ptr_string() -> UniquePtr<CxxString>;
fn c_return_unique_ptr_vector_u8() -> UniquePtr<CxxVector<u8>>;
fn c_return_unique_ptr_vector_f64() -> UniquePtr<CxxVector<f64>>;
+ fn c_return_unique_ptr_vector_string() -> UniquePtr<CxxVector<CxxString>>;
fn c_return_unique_ptr_vector_shared() -> UniquePtr<CxxVector<Shared>>;
fn c_return_unique_ptr_vector_opaque() -> UniquePtr<CxxVector<C>>;
fn c_return_ref_vector(c: &C) -> &CxxVector<u8>;
+ fn c_return_mut_vector(c: &mut C) -> &mut CxxVector<u8>;
fn c_return_rust_vec() -> Vec<u8>;
fn c_return_ref_rust_vec(c: &C) -> &Vec<u8>;
+ fn c_return_mut_rust_vec(c: &mut C) -> &mut Vec<u8>;
+ fn c_return_rust_vec_string() -> Vec<String>;
fn c_return_identity(_: usize) -> usize;
fn c_return_sum(_: usize, _: usize) -> usize;
fn c_return_enum(n: u16) -> Enum;
+ fn c_return_ns_ref(shared: &AShared) -> &usize;
+ fn c_return_nested_ns_ref(shared: &ABShared) -> &usize;
+ fn c_return_ns_enum(n: u16) -> AEnum;
+ fn c_return_nested_ns_enum(n: u16) -> ABEnum;
fn c_take_primitive(n: usize);
fn c_take_shared(shared: Shared);
@@ -57,18 +161,29 @@
fn c_take_unique_ptr_string(s: UniquePtr<CxxString>);
fn c_take_unique_ptr_vector_u8(v: UniquePtr<CxxVector<u8>>);
fn c_take_unique_ptr_vector_f64(v: UniquePtr<CxxVector<f64>>);
+ fn c_take_unique_ptr_vector_string(v: UniquePtr<CxxVector<CxxString>>);
fn c_take_unique_ptr_vector_shared(v: UniquePtr<CxxVector<Shared>>);
fn c_take_ref_vector(v: &CxxVector<u8>);
fn c_take_rust_vec(v: Vec<u8>);
fn c_take_rust_vec_shared(v: Vec<Shared>);
+ fn c_take_rust_vec_string(v: Vec<String>);
+ fn c_take_rust_vec_index(v: Vec<u8>);
+ fn c_take_rust_vec_shared_index(v: Vec<Shared>);
+ fn c_take_rust_vec_shared_push(v: Vec<Shared>);
fn c_take_rust_vec_shared_forward_iterator(v: Vec<Shared>);
fn c_take_ref_rust_vec(v: &Vec<u8>);
+ fn c_take_ref_rust_vec_string(v: &Vec<String>);
+ fn c_take_ref_rust_vec_index(v: &Vec<u8>);
fn c_take_ref_rust_vec_copy(v: &Vec<u8>);
- /*
- // https://github.com/dtolnay/cxx/issues/232
+ fn c_take_ref_shared_string(s: &SharedString) -> &SharedString;
fn c_take_callback(callback: fn(String) -> usize);
- */
fn c_take_enum(e: Enum);
+ fn c_take_ns_enum(e: AEnum);
+ fn c_take_nested_ns_enum(e: ABEnum);
+ fn c_take_ns_shared(shared: AShared);
+ fn c_take_nested_ns_shared(shared: ABShared);
+ fn c_take_rust_vec_ns_shared(v: Vec<AShared>);
+ fn c_take_rust_vec_nested_ns_shared(v: Vec<ABShared>);
fn c_try_return_void() -> Result<()>;
fn c_try_return_primitive() -> Result<usize>;
@@ -80,6 +195,7 @@
fn c_try_return_rust_string() -> Result<String>;
fn c_try_return_unique_ptr_string() -> Result<UniquePtr<CxxString>>;
fn c_try_return_rust_vec() -> Result<Vec<u8>>;
+ fn c_try_return_rust_vec_string() -> Result<Vec<String>>;
fn c_try_return_ref_rust_vec(c: &C) -> Result<&Vec<u8>>;
fn get(self: &C) -> usize;
@@ -88,6 +204,19 @@
fn set2(&mut self, n: usize) -> usize;
fn set_succeed(&mut self, n: usize) -> Result<usize>;
fn get_fail(&mut self) -> Result<usize>;
+ fn c_method_on_shared(self: &Shared) -> usize;
+
+ #[rust_name = "i32_overloaded_method"]
+ fn cOverloadedMethod(&self, x: i32) -> String;
+ #[rust_name = "str_overloaded_method"]
+ fn cOverloadedMethod(&self, x: &str) -> String;
+ #[rust_name = "i32_overloaded_function"]
+ fn cOverloadedFunction(x: i32) -> String;
+ #[rust_name = "str_overloaded_function"]
+ fn cOverloadedFunction(x: &str) -> String;
+
+ #[namespace = "other"]
+ fn ns_c_take_ns_shared(shared: AShared);
}
extern "C" {
@@ -109,11 +238,14 @@
fn r_return_box() -> Box<R>;
fn r_return_unique_ptr() -> UniquePtr<C>;
fn r_return_ref(shared: &Shared) -> &usize;
+ fn r_return_mut(shared: &mut Shared) -> &mut usize;
fn r_return_str(shared: &Shared) -> &str;
fn r_return_rust_string() -> String;
fn r_return_unique_ptr_string() -> UniquePtr<CxxString>;
fn r_return_rust_vec() -> Vec<u8>;
+ fn r_return_rust_vec_string() -> Vec<String>;
fn r_return_ref_rust_vec(shared: &Shared) -> &Vec<u8>;
+ fn r_return_mut_rust_vec(shared: &mut Shared) -> &mut Vec<u8>;
fn r_return_identity(_: usize) -> usize;
fn r_return_sum(_: usize, _: usize) -> usize;
fn r_return_enum(n: u32) -> Enum;
@@ -128,17 +260,47 @@
fn r_take_sliceu8(s: &[u8]);
fn r_take_rust_string(s: String);
fn r_take_unique_ptr_string(s: UniquePtr<CxxString>);
+ fn r_take_ref_vector(v: &CxxVector<u8>);
+ fn r_take_ref_empty_vector(v: &CxxVector<u64>);
fn r_take_rust_vec(v: Vec<u8>);
+ fn r_take_rust_vec_string(v: Vec<String>);
fn r_take_ref_rust_vec(v: &Vec<u8>);
+ fn r_take_ref_rust_vec_string(v: &Vec<String>);
fn r_take_enum(e: Enum);
fn r_try_return_void() -> Result<()>;
fn r_try_return_primitive() -> Result<usize>;
+ fn r_try_return_box() -> Result<Box<R>>;
fn r_fail_return_primitive() -> Result<usize>;
fn r_return_r2(n: usize) -> Box<R2>;
fn get(self: &R2) -> usize;
fn set(self: &mut R2, n: usize) -> usize;
+ fn r_method_on_shared(self: &Shared) -> String;
+
+ #[cxx_name = "rAliasedFunction"]
+ fn r_aliased_function(x: i32) -> String;
+ }
+
+ struct Dag0 {
+ i: i32,
+ }
+
+ struct Dag1 {
+ dag2: Dag2,
+ vec: Vec<Dag3>,
+ }
+
+ struct Dag2 {
+ dag4: Dag4,
+ }
+
+ struct Dag3 {
+ dag1: Dag1,
+ }
+
+ struct Dag4 {
+ dag0: Dag0,
}
}
@@ -157,6 +319,12 @@
}
}
+impl ffi::Shared {
+ fn r_method_on_shared(&self) -> String {
+ "2020".to_owned()
+ }
+}
+
#[derive(Debug)]
struct Error;
@@ -191,6 +359,10 @@
&shared.z
}
+fn r_return_mut(shared: &mut ffi::Shared) -> &mut usize {
+ &mut shared.z
+}
+
fn r_return_str(shared: &ffi::Shared) -> &str {
let _ = shared;
"2020"
@@ -211,11 +383,20 @@
Vec::new()
}
+fn r_return_rust_vec_string() -> Vec<String> {
+ Vec::new()
+}
+
fn r_return_ref_rust_vec(shared: &ffi::Shared) -> &Vec<u8> {
let _ = shared;
unimplemented!()
}
+fn r_return_mut_rust_vec(shared: &mut ffi::Shared) -> &mut Vec<u8> {
+ let _ = shared;
+ unimplemented!()
+}
+
fn r_return_identity(n: usize) -> usize {
n
}
@@ -275,14 +456,32 @@
assert_eq!(s.as_ref().unwrap().to_str().unwrap(), "2020");
}
+fn r_take_ref_vector(v: &CxxVector<u8>) {
+ let slice = v.as_slice();
+ assert_eq!(slice, [20, 2, 0]);
+}
+
+fn r_take_ref_empty_vector(v: &CxxVector<u64>) {
+ assert!(v.as_slice().is_empty());
+ assert!(v.is_empty());
+}
+
fn r_take_rust_vec(v: Vec<u8>) {
let _ = v;
}
+fn r_take_rust_vec_string(v: Vec<String>) {
+ let _ = v;
+}
+
fn r_take_ref_rust_vec(v: &Vec<u8>) {
let _ = v;
}
+fn r_take_ref_rust_vec_string(v: &Vec<String>) {
+ let _ = v;
+}
+
fn r_take_enum(e: ffi::Enum) {
let _ = e;
}
@@ -295,6 +494,10 @@
Ok(2020)
}
+fn r_try_return_box() -> Result<Box<R>, Error> {
+ Ok(Box::new(2020))
+}
+
fn r_fail_return_primitive() -> Result<usize, Error> {
Err(Error)
}
@@ -302,3 +505,7 @@
fn r_return_r2(n: usize) -> Box<R2> {
Box::new(R2(n))
}
+
+fn r_aliased_function(x: i32) -> String {
+ x.to_string()
+}
diff --git a/tests/ffi/module.rs b/tests/ffi/module.rs
index 77bae06..899d45b 100644
--- a/tests/ffi/module.rs
+++ b/tests/ffi/module.rs
@@ -1,7 +1,7 @@
// Rustfmt mangles the extern type alias.
// https://github.com/rust-lang/rustfmt/issues/4159
#[rustfmt::skip]
-#[cxx::bridge(namespace = tests)]
+#[cxx::bridge(namespace = "tests")]
pub mod ffi {
extern "C" {
include!("tests/ffi/tests.h");
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index d70d5a0..2305766 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -1,8 +1,11 @@
#include "tests/ffi/tests.h"
#include "tests/ffi/lib.rs.h"
#include <cstring>
+#include <iterator>
+#include <memory>
#include <numeric>
#include <stdexcept>
+#include <string>
extern "C" void cxx_test_suite_set_correct() noexcept;
extern "C" tests::R *cxx_test_suite_get_box() noexcept;
@@ -32,12 +35,20 @@
size_t C::get_fail() { throw std::runtime_error("unimplemented"); }
+size_t Shared::c_method_on_shared() const noexcept { return 2021; }
+
const std::vector<uint8_t> &C::get_v() const { return this->v; }
+std::vector<uint8_t> &C::get_v() { return this->v; }
+
size_t c_return_primitive() { return 2020; }
Shared c_return_shared() { return Shared{2020}; }
+::A::AShared c_return_ns_shared() { return ::A::AShared{2020}; }
+
+::A::B::ABShared c_return_nested_ns_shared() { return ::A::B::ABShared{2020}; }
+
rust::Box<R> c_return_box() {
return rust::Box<R>::from_raw(cxx_test_suite_get_box());
}
@@ -46,8 +57,20 @@
return std::unique_ptr<C>(new C{2020});
}
+std::unique_ptr<::H::H> c_return_ns_unique_ptr() {
+ return std::unique_ptr<::H::H>(new ::H::H{"hello"});
+}
+
const size_t &c_return_ref(const Shared &shared) { return shared.z; }
+const size_t &c_return_ns_ref(const ::A::AShared &shared) { return shared.z; }
+
+const size_t &c_return_nested_ns_ref(const ::A::B::ABShared &shared) {
+ return shared.z;
+}
+
+size_t &c_return_mut(Shared &shared) { return shared.z; }
+
rust::Str c_return_str(const Shared &shared) {
(void)shared;
return "2020";
@@ -83,6 +106,11 @@
return vec;
}
+std::unique_ptr<std::vector<std::string>> c_return_unique_ptr_vector_string() {
+ return std::unique_ptr<std::vector<std::string>>(
+ new std::vector<std::string>());
+}
+
std::unique_ptr<std::vector<Shared>> c_return_unique_ptr_vector_shared() {
auto vec = std::unique_ptr<std::vector<Shared>>(new std::vector<Shared>());
vec->push_back(Shared{1010});
@@ -98,6 +126,8 @@
return c.get_v();
}
+std::vector<uint8_t> &c_return_mut_vector(C &c) { return c.get_v(); }
+
rust::Vec<uint8_t> c_return_rust_vec() {
throw std::runtime_error("unimplemented");
}
@@ -107,6 +137,15 @@
throw std::runtime_error("unimplemented");
}
+rust::Vec<uint8_t> &c_return_mut_rust_vec(C &c) {
+ (void)c;
+ throw std::runtime_error("unimplemented");
+}
+
+rust::Vec<rust::String> c_return_rust_vec_string() {
+ throw std::runtime_error("unimplemented");
+}
+
size_t c_return_identity(size_t n) { return n; }
size_t c_return_sum(size_t n1, size_t n2) { return n1 + n2; }
@@ -121,6 +160,26 @@
}
}
+::A::AEnum c_return_ns_enum(uint16_t n) {
+ if (n <= static_cast<uint16_t>(::A::AEnum::AAVal)) {
+ return ::A::AEnum::AAVal;
+ } else if (n <= static_cast<uint16_t>(::A::AEnum::ABVal)) {
+ return ::A::AEnum::ABVal;
+ } else {
+ return ::A::AEnum::ACVal;
+ }
+}
+
+::A::B::ABEnum c_return_nested_ns_enum(uint16_t n) {
+ if (n <= static_cast<uint16_t>(::A::B::ABEnum::ABAVal)) {
+ return ::A::B::ABEnum::ABAVal;
+ } else if (n <= static_cast<uint16_t>(::A::B::ABEnum::ABBVal)) {
+ return ::A::B::ABEnum::ABBVal;
+ } else {
+ return ::A::B::ABEnum::ABCVal;
+ }
+}
+
void c_take_primitive(size_t n) {
if (n == 2020) {
cxx_test_suite_set_correct();
@@ -133,6 +192,18 @@
}
}
+void c_take_ns_shared(::A::AShared shared) {
+ if (shared.z == 2020) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_nested_ns_shared(::A::B::ABShared shared) {
+ if (shared.z == 2020) {
+ cxx_test_suite_set_correct();
+ }
+}
+
void c_take_box(rust::Box<R> r) {
if (cxx_test_suite_r_is_correct(&*r)) {
cxx_test_suite_set_correct();
@@ -157,6 +228,12 @@
}
}
+void c_take_ref_ns_c(const ::H::H &h) {
+ if (h.h == "hello") {
+ cxx_test_suite_set_correct();
+ }
+}
+
void c_take_str(rust::Str s) {
if (std::string(s) == "2020") {
cxx_test_suite_set_correct();
@@ -194,6 +271,12 @@
}
}
+void c_take_unique_ptr_vector_string(
+ std::unique_ptr<std::vector<std::string>> v) {
+ (void)v;
+ cxx_test_suite_set_correct();
+}
+
void c_take_unique_ptr_vector_shared(std::unique_ptr<std::vector<Shared>> v) {
if (v->size() == 2) {
cxx_test_suite_set_correct();
@@ -208,6 +291,17 @@
void c_take_rust_vec(rust::Vec<uint8_t> v) { c_take_ref_rust_vec(v); }
+void c_take_rust_vec_index(rust::Vec<uint8_t> v) {
+ try {
+ v.at(100);
+ } catch (const std::out_of_range &ex) {
+ std::string expected = "rust::Vec index out of range";
+ if (ex.what() == expected) {
+ cxx_test_suite_set_correct();
+ }
+ }
+}
+
void c_take_rust_vec_shared(rust::Vec<Shared> v) {
uint32_t sum = 0;
for (auto i : v) {
@@ -218,6 +312,31 @@
}
}
+void c_take_rust_vec_ns_shared(rust::Vec<::A::AShared> v) {
+ uint32_t sum = 0;
+ for (auto i : v) {
+ sum += i.z;
+ }
+ if (sum == 2021) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_rust_vec_nested_ns_shared(rust::Vec<::A::B::ABShared> v) {
+ uint32_t sum = 0;
+ for (auto i : v) {
+ sum += i.z;
+ }
+ if (sum == 2021) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_rust_vec_string(rust::Vec<rust::String> v) {
+ (void)v;
+ cxx_test_suite_set_correct();
+}
+
void c_take_rust_vec_shared_forward_iterator(rust::Vec<Shared> v) {
// Exercise requirements of ForwardIterator
// https://en.cppreference.com/w/cpp/named_req/ForwardIterator
@@ -230,6 +349,21 @@
}
}
+void c_take_rust_vec_shared_index(rust::Vec<Shared> v) {
+ if (v[0].z == 1010 && v.at(0).z == 1010 && v.front().z == 1010 &&
+ v[1].z == 1011 && v.at(1).z == 1011 && v.back().z == 1011) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_rust_vec_shared_push(rust::Vec<Shared> v) {
+ v.push_back(Shared{3});
+ v.emplace_back(Shared{2});
+ if (v[v.size() - 2].z == 3 && v.back().z == 2) {
+ cxx_test_suite_set_correct();
+ }
+}
+
void c_take_ref_rust_vec(const rust::Vec<uint8_t> &v) {
uint8_t sum = std::accumulate(v.begin(), v.end(), 0);
if (sum == 200) {
@@ -237,6 +371,18 @@
}
}
+void c_take_ref_rust_vec_string(const rust::Vec<rust::String> &v) {
+ (void)v;
+ cxx_test_suite_set_correct();
+}
+
+void c_take_ref_rust_vec_index(const rust::Vec<uint8_t> &v) {
+ if (v[0] == 86 && v.at(0) == 86 && v.front() == 86 && v[1] == 75 &&
+ v.at(1) == 75 && v[3] == 9 && v.at(3) == 9 && v.back() == 9) {
+ cxx_test_suite_set_correct();
+ }
+}
+
void c_take_ref_rust_vec_copy(const rust::Vec<uint8_t> &v) {
// The std::copy() will make sure rust::Vec<>::const_iterator satisfies the
// requirements for std::iterator_traits.
@@ -249,12 +395,16 @@
}
}
-/*
-// https://github.com/dtolnay/cxx/issues/232
+const SharedString &c_take_ref_shared_string(const SharedString &s) {
+ if (std::string(s.msg) == "2020") {
+ cxx_test_suite_set_correct();
+ }
+ return s;
+}
+
void c_take_callback(rust::Fn<size_t(rust::String)> callback) {
callback("2020");
}
-*/
void c_take_enum(Enum e) {
if (e == Enum::AVal) {
@@ -262,6 +412,18 @@
}
}
+void c_take_ns_enum(::A::AEnum e) {
+ if (e == ::A::AEnum::AAVal) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_nested_ns_enum(::A::B::ABEnum e) {
+ if (e == ::A::B::ABEnum::ABAVal) {
+ cxx_test_suite_set_correct();
+ }
+}
+
void c_try_return_void() {}
size_t c_try_return_primitive() { return 2020; }
@@ -286,6 +448,10 @@
throw std::runtime_error("unimplemented");
}
+rust::Vec<rust::String> c_try_return_rust_vec_string() {
+ throw std::runtime_error("unimplemented");
+}
+
const rust::Vec<uint8_t> &c_try_return_ref_rust_vec(const C &c) {
(void)c;
throw std::runtime_error("unimplemented");
@@ -299,6 +465,120 @@
return std::unique_ptr<std::string>(new std::string("2020")).release();
}
+rust::String C::cOverloadedMethod(int32_t x) const {
+ return rust::String(std::to_string(x));
+}
+
+rust::String C::cOverloadedMethod(rust::Str x) const {
+ return rust::String(std::string(x));
+}
+
+rust::String cOverloadedFunction(int x) {
+ return rust::String(std::to_string(x));
+}
+
+rust::String cOverloadedFunction(rust::Str x) {
+ return rust::String(std::string(x));
+}
+
+void c_take_trivial_ptr(std::unique_ptr<D> d) {
+ if (d->d == 30) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_trivial_ref(const D &d) {
+ if (d.d == 30) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_trivial(D d) {
+ if (d.d == 30) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_trivial_ns_ptr(std::unique_ptr<::G::G> g) {
+ if (g->g == 30) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_trivial_ns_ref(const ::G::G &g) {
+ if (g.g == 30) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_trivial_ns(::G::G g) {
+ if (g.g == 30) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_opaque_ptr(std::unique_ptr<E> e) {
+ if (e->e == 40) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_opaque_ns_ptr(std::unique_ptr<::F::F> f) {
+ if (f->f == 40) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_opaque_ref(const E &e) {
+ if (e.e == 40 && e.e_str == "hello") {
+ cxx_test_suite_set_correct();
+ }
+}
+
+void c_take_opaque_ns_ref(const ::F::F &f) {
+ if (f.f == 40 && f.f_str == "hello") {
+ cxx_test_suite_set_correct();
+ }
+}
+
+std::unique_ptr<D> c_return_trivial_ptr() {
+ auto d = std::unique_ptr<D>(new D());
+ d->d = 30;
+ return d;
+}
+
+D c_return_trivial() {
+ D d;
+ d.d = 30;
+ return d;
+}
+
+std::unique_ptr<::G::G> c_return_trivial_ns_ptr() {
+ auto g = std::unique_ptr<::G::G>(new ::G::G());
+ g->g = 30;
+ return g;
+}
+
+::G::G c_return_trivial_ns() {
+ ::G::G g;
+ g.g = 30;
+ return g;
+}
+
+std::unique_ptr<E> c_return_opaque_ptr() {
+ auto e = std::unique_ptr<E>(new E());
+ e->e = 40;
+ e->e_str = std::string("hello");
+ return e;
+}
+
+std::unique_ptr<::F::F> c_return_ns_opaque_ptr() {
+ auto f = std::unique_ptr<::F::F>(new ::F::F());
+ f->f = 40;
+ f->f_str = std::string("hello");
+ return f;
+}
+
extern "C" const char *cxx_run_test() noexcept {
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
@@ -333,6 +613,11 @@
r_take_rust_string(rust::String("2020"));
r_take_unique_ptr_string(
std::unique_ptr<std::string>(new std::string("2020")));
+ r_take_ref_vector(std::vector<uint8_t>{20, 2, 0});
+ std::vector<uint64_t> empty_vector;
+ r_take_ref_empty_vector(empty_vector);
+ empty_vector.reserve(10);
+ r_take_ref_empty_vector(empty_vector);
r_take_enum(Enum::AVal);
ASSERT(r_try_return_primitive() == 2020);
@@ -349,9 +634,40 @@
ASSERT(r2->get() == 2021);
ASSERT(r2->set(2020) == 2020);
ASSERT(r2->get() == 2020);
+ ASSERT(std::string(Shared{0}.r_method_on_shared()) == "2020");
+
+ ASSERT(std::string(rAliasedFunction(2020)) == "2020");
cxx_test_suite_set_correct();
return nullptr;
}
} // namespace tests
+
+namespace other {
+void ns_c_take_trivial(::tests::D d) {
+ if (d.d == 30) {
+ cxx_test_suite_set_correct();
+ }
+}
+
+::tests::D ns_c_return_trivial() {
+ ::tests::D d;
+ d.d = 30;
+ return d;
+}
+
+void ns_c_take_ns_shared(::A::AShared shared) {
+ if (shared.z == 2020) {
+ cxx_test_suite_set_correct();
+ }
+}
+} // namespace other
+
+namespace I {
+uint32_t I::get() const { return a; }
+
+std::unique_ptr<I> ns_c_return_unique_ptr_ns() {
+ return std::unique_ptr<I>(new I());
+}
+} // namespace I
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index 7121cb2..2217c36 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -3,10 +3,40 @@
#include <memory>
#include <string>
+namespace A {
+struct AShared;
+enum class AEnum : uint16_t;
+namespace B {
+struct ABShared;
+enum class ABEnum : uint16_t;
+} // namespace B
+} // namespace A
+
+namespace F {
+struct F {
+ uint64_t f;
+ std::string f_str;
+};
+} // namespace F
+
+namespace G {
+struct G {
+ uint64_t g;
+};
+} // namespace G
+
+namespace H {
+class H {
+public:
+ std::string h;
+};
+} // namespace H
+
namespace tests {
struct R;
struct Shared;
+struct SharedString;
enum class Enum : uint16_t;
class C {
@@ -19,12 +49,24 @@
size_t set_succeed(size_t n);
size_t get_fail();
const std::vector<uint8_t> &get_v() const;
+ std::vector<uint8_t> &get_v();
+ rust::String cOverloadedMethod(int32_t x) const;
+ rust::String cOverloadedMethod(rust::Str x) const;
private:
size_t n;
std::vector<uint8_t> v;
};
+struct D {
+ uint64_t d;
+};
+
+struct E {
+ uint64_t e;
+ std::string e_str;
+};
+
enum COwnedEnum {
CVal1,
CVal2,
@@ -32,48 +74,73 @@
size_t c_return_primitive();
Shared c_return_shared();
+::A::AShared c_return_ns_shared();
+::A::B::ABShared c_return_nested_ns_shared();
rust::Box<R> c_return_box();
std::unique_ptr<C> c_return_unique_ptr();
+std::unique_ptr<::H::H> c_return_ns_unique_ptr();
const size_t &c_return_ref(const Shared &shared);
+const size_t &c_return_ns_ref(const ::A::AShared &shared);
+const size_t &c_return_nested_ns_ref(const ::A::B::ABShared &shared);
+size_t &c_return_mut(Shared &shared);
rust::Str c_return_str(const Shared &shared);
rust::Slice<uint8_t> c_return_sliceu8(const Shared &shared);
rust::String c_return_rust_string();
std::unique_ptr<std::string> c_return_unique_ptr_string();
std::unique_ptr<std::vector<uint8_t>> c_return_unique_ptr_vector_u8();
std::unique_ptr<std::vector<double>> c_return_unique_ptr_vector_f64();
+std::unique_ptr<std::vector<std::string>> c_return_unique_ptr_vector_string();
std::unique_ptr<std::vector<Shared>> c_return_unique_ptr_vector_shared();
std::unique_ptr<std::vector<C>> c_return_unique_ptr_vector_opaque();
const std::vector<uint8_t> &c_return_ref_vector(const C &c);
+std::vector<uint8_t> &c_return_mut_vector(C &c);
rust::Vec<uint8_t> c_return_rust_vec();
const rust::Vec<uint8_t> &c_return_ref_rust_vec(const C &c);
+rust::Vec<uint8_t> &c_return_mut_rust_vec(C &c);
+rust::Vec<rust::String> c_return_rust_vec_string();
size_t c_return_identity(size_t n);
size_t c_return_sum(size_t n1, size_t n2);
Enum c_return_enum(uint16_t n);
+::A::AEnum c_return_ns_enum(uint16_t n);
+::A::B::ABEnum c_return_nested_ns_enum(uint16_t n);
void c_take_primitive(size_t n);
void c_take_shared(Shared shared);
+void c_take_ns_shared(::A::AShared shared);
+void c_take_nested_ns_shared(::A::B::ABShared shared);
void c_take_box(rust::Box<R> r);
void c_take_unique_ptr(std::unique_ptr<C> c);
void c_take_ref_r(const R &r);
void c_take_ref_c(const C &c);
+void c_take_ref_ns_c(const ::H::H &h);
void c_take_str(rust::Str s);
void c_take_sliceu8(rust::Slice<uint8_t> s);
void c_take_rust_string(rust::String s);
void c_take_unique_ptr_string(std::unique_ptr<std::string> s);
void c_take_unique_ptr_vector_u8(std::unique_ptr<std::vector<uint8_t>> v);
void c_take_unique_ptr_vector_f64(std::unique_ptr<std::vector<double>> v);
+void c_take_unique_ptr_vector_string(
+ std::unique_ptr<std::vector<std::string>> v);
void c_take_unique_ptr_vector_shared(std::unique_ptr<std::vector<Shared>> v);
void c_take_ref_vector(const std::vector<uint8_t> &v);
void c_take_rust_vec(rust::Vec<uint8_t> v);
+void c_take_rust_vec_index(rust::Vec<uint8_t> v);
void c_take_rust_vec_shared(rust::Vec<Shared> v);
+void c_take_rust_vec_ns_shared(rust::Vec<::A::AShared> v);
+void c_take_rust_vec_nested_ns_shared(rust::Vec<::A::B::ABShared> v);
+void c_take_rust_vec_string(rust::Vec<rust::String> v);
+void c_take_rust_vec_shared_index(rust::Vec<Shared> v);
+void c_take_rust_vec_shared_push(rust::Vec<Shared> v);
void c_take_rust_vec_shared_forward_iterator(rust::Vec<Shared> v);
void c_take_ref_rust_vec(const rust::Vec<uint8_t> &v);
+void c_take_ref_rust_vec_string(const rust::Vec<rust::String> &v);
+void c_take_ref_rust_vec_index(const rust::Vec<uint8_t> &v);
void c_take_ref_rust_vec_copy(const rust::Vec<uint8_t> &v);
-/*
-// https://github.com/dtolnay/cxx/issues/232
+const SharedString &c_take_ref_shared_string(const SharedString &s);
void c_take_callback(rust::Fn<size_t(rust::String)> callback);
-*/
void c_take_enum(Enum e);
+void c_take_ns_enum(::A::AEnum e);
+void c_take_nested_ns_enum(::A::B::ABEnum e);
void c_try_return_void();
size_t c_try_return_primitive();
@@ -85,6 +152,47 @@
rust::String c_try_return_rust_string();
std::unique_ptr<std::string> c_try_return_unique_ptr_string();
rust::Vec<uint8_t> c_try_return_rust_vec();
+rust::Vec<rust::String> c_try_return_rust_vec_string();
const rust::Vec<uint8_t> &c_try_return_ref_rust_vec(const C &c);
+void c_take_trivial_ptr(std::unique_ptr<D> d);
+void c_take_trivial_ref(const D &d);
+void c_take_trivial(D d);
+
+void c_take_trivial_ns_ptr(std::unique_ptr<::G::G> g);
+void c_take_trivial_ns_ref(const ::G::G &g);
+void c_take_trivial_ns(::G::G g);
+void c_take_opaque_ptr(std::unique_ptr<E> e);
+void c_take_opaque_ns_ptr(std::unique_ptr<::F::F> f);
+void c_take_opaque_ref(const E &e);
+void c_take_opaque_ns_ref(const ::F::F &f);
+std::unique_ptr<D> c_return_trivial_ptr();
+D c_return_trivial();
+std::unique_ptr<::G::G> c_return_trivial_ns_ptr();
+::G::G c_return_trivial_ns();
+std::unique_ptr<E> c_return_opaque_ptr();
+std::unique_ptr<::F::F> c_return_ns_opaque_ptr();
+
+rust::String cOverloadedFunction(int32_t x);
+rust::String cOverloadedFunction(rust::Str x);
+
} // namespace tests
+
+namespace other {
+void ns_c_take_trivial(::tests::D d);
+::tests::D ns_c_return_trivial();
+void ns_c_take_ns_shared(::A::AShared shared);
+} // namespace other
+
+namespace I {
+class I {
+private:
+ uint32_t a;
+
+public:
+ I() : a(1000) {}
+ uint32_t get() const;
+};
+
+std::unique_ptr<I> ns_c_return_unique_ptr_ns();
+} // namespace I
diff --git a/tests/test.rs b/tests/test.rs
index f2f670c..2bae4d8 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -1,3 +1,6 @@
+#![allow(clippy::assertions_on_constants, clippy::float_cmp, clippy::unit_cmp)]
+
+use cxx_test_suite::extra::ffi2;
use cxx_test_suite::ffi;
use std::cell::Cell;
use std::ffi::CStr;
@@ -22,12 +25,17 @@
#[test]
fn test_c_return() {
let shared = ffi::Shared { z: 2020 };
+ let ns_shared = ffi::AShared { z: 2020 };
+ let nested_ns_shared = ffi::ABShared { z: 2020 };
assert_eq!(2020, ffi::c_return_primitive());
assert_eq!(2020, ffi::c_return_shared().z);
assert_eq!(2020, *ffi::c_return_box());
ffi::c_return_unique_ptr();
+ ffi2::c_return_ns_unique_ptr();
assert_eq!(2020, *ffi::c_return_ref(&shared));
+ assert_eq!(2020, *ffi::c_return_ns_ref(&ns_shared));
+ assert_eq!(2020, *ffi::c_return_nested_ns_ref(&nested_ns_shared));
assert_eq!("2020", ffi::c_return_str(&shared));
assert_eq!(b"2020\0", ffi::c_return_sliceu8(&shared));
assert_eq!("2020", ffi::c_return_rust_string());
@@ -63,6 +71,14 @@
enm @ ffi::Enum::CVal => assert_eq!(2021, enm.repr),
_ => assert!(false),
}
+ match ffi::c_return_ns_enum(0) {
+ enm @ ffi::AEnum::AAVal => assert_eq!(0, enm.repr),
+ _ => assert!(false),
+ }
+ match ffi::c_return_nested_ns_enum(0) {
+ enm @ ffi::ABEnum::ABAVal => assert_eq!(0, enm.repr),
+ _ => assert!(false),
+ }
}
#[test]
@@ -84,11 +100,16 @@
#[test]
fn test_c_take() {
let unique_ptr = ffi::c_return_unique_ptr();
+ let unique_ptr_ns = ffi2::c_return_ns_unique_ptr();
check!(ffi::c_take_primitive(2020));
check!(ffi::c_take_shared(ffi::Shared { z: 2020 }));
+ check!(ffi::c_take_ns_shared(ffi::AShared { z: 2020 }));
+ check!(ffi::ns_c_take_ns_shared(ffi::AShared { z: 2020 }));
+ check!(ffi::c_take_nested_ns_shared(ffi::ABShared { z: 2020 }));
check!(ffi::c_take_box(Box::new(2020)));
check!(ffi::c_take_ref_c(&unique_ptr));
+ check!(ffi2::c_take_ref_ns_c(&unique_ptr_ns));
check!(cxx_test_suite::module::ffi::c_take_unique_ptr(unique_ptr));
check!(ffi::c_take_str("2020"));
check!(ffi::c_take_sliceu8(b"2020"));
@@ -108,21 +129,32 @@
check!(ffi::c_take_ref_vector(&ffi::c_return_unique_ptr_vector_u8()));
let test_vec = [86_u8, 75_u8, 30_u8, 9_u8].to_vec();
check!(ffi::c_take_rust_vec(test_vec.clone()));
- check!(ffi::c_take_rust_vec_shared(vec![
- ffi::Shared { z: 1010 },
- ffi::Shared { z: 1011 }
- ]));
- check!(ffi::c_take_rust_vec_shared_forward_iterator(vec![
- ffi::Shared { z: 1010 },
- ffi::Shared { z: 1011 }
- ]));
+ check!(ffi::c_take_rust_vec_index(test_vec.clone()));
+ let shared_test_vec = vec![ffi::Shared { z: 1010 }, ffi::Shared { z: 1011 }];
+ check!(ffi::c_take_rust_vec_shared(shared_test_vec.clone()));
+ check!(ffi::c_take_rust_vec_shared_index(shared_test_vec.clone()));
+ check!(ffi::c_take_rust_vec_shared_push(shared_test_vec.clone()));
+ check!(ffi::c_take_rust_vec_shared_forward_iterator(
+ shared_test_vec,
+ ));
check!(ffi::c_take_ref_rust_vec(&test_vec));
+ check!(ffi::c_take_ref_rust_vec_index(&test_vec));
check!(ffi::c_take_ref_rust_vec_copy(&test_vec));
+ check!(ffi::c_take_ref_shared_string(&ffi::SharedString {
+ msg: "2020".to_owned()
+ }));
+ let ns_shared_test_vec = vec![ffi::AShared { z: 1010 }, ffi::AShared { z: 1011 }];
+ check!(ffi::c_take_rust_vec_ns_shared(ns_shared_test_vec));
+ let nested_ns_shared_test_vec = vec![ffi::ABShared { z: 1010 }, ffi::ABShared { z: 1011 }];
+ check!(ffi::c_take_rust_vec_nested_ns_shared(
+ nested_ns_shared_test_vec
+ ));
+
check!(ffi::c_take_enum(ffi::Enum::AVal));
+ check!(ffi::c_take_ns_enum(ffi::AEnum::AAVal));
+ check!(ffi::c_take_nested_ns_enum(ffi::ABEnum::ABAVal));
}
-/*
-// https://github.com/dtolnay/cxx/issues/232
#[test]
fn test_c_callback() {
fn callback(s: String) -> usize {
@@ -134,7 +166,6 @@
check!(ffi::c_take_callback(callback));
}
-*/
#[test]
fn test_c_call_r() {
@@ -163,6 +194,15 @@
assert_eq!(old_value, unique_ptr.get2());
assert_eq!(2022, unique_ptr.set_succeed(2022).unwrap());
assert!(unique_ptr.get_fail().is_err());
+ assert_eq!(2021, ffi::Shared { z: 0 }.c_method_on_shared());
+}
+
+#[test]
+fn test_c_ns_method_calls() {
+ let unique_ptr = ffi2::ns_c_return_unique_ptr_ns();
+
+ let old_value = unique_ptr.get();
+ assert_eq!(1000, old_value);
}
#[test]
@@ -181,3 +221,42 @@
unsafe extern "C" fn cxx_test_suite_r_is_correct(r: *const cxx_test_suite::R) -> bool {
*r == 2020
}
+
+#[test]
+fn test_rust_name_attribute() {
+ assert_eq!("2020", ffi::i32_overloaded_function(2020));
+ assert_eq!("2020", ffi::str_overloaded_function("2020"));
+ let unique_ptr = ffi::c_return_unique_ptr();
+ assert_eq!("2020", unique_ptr.i32_overloaded_method(2020));
+ assert_eq!("2020", unique_ptr.str_overloaded_method("2020"));
+}
+
+#[test]
+fn test_extern_trivial() {
+ let d = ffi2::c_return_trivial();
+ check!(ffi2::c_take_trivial_ref(&d));
+ check!(ffi2::c_take_trivial(d));
+ let d = ffi2::c_return_trivial_ptr();
+ check!(ffi2::c_take_trivial_ptr(d));
+ cxx::UniquePtr::new(ffi2::D { d: 42 });
+ let d = ffi2::ns_c_return_trivial();
+ check!(ffi2::ns_c_take_trivial(d));
+
+ let g = ffi2::c_return_trivial_ns();
+ check!(ffi2::c_take_trivial_ns_ref(&g));
+ check!(ffi2::c_take_trivial_ns(g));
+ let g = ffi2::c_return_trivial_ns_ptr();
+ check!(ffi2::c_take_trivial_ns_ptr(g));
+ cxx::UniquePtr::new(ffi2::G { g: 42 });
+}
+
+#[test]
+fn test_extern_opaque() {
+ let e = ffi2::c_return_opaque_ptr();
+ check!(ffi2::c_take_opaque_ref(e.as_ref().unwrap()));
+ check!(ffi2::c_take_opaque_ptr(e));
+
+ let f = ffi2::c_return_ns_opaque_ptr();
+ check!(ffi2::c_take_opaque_ns_ref(f.as_ref().unwrap()));
+ check!(ffi2::c_take_opaque_ns_ptr(f));
+}
diff --git a/tests/ui/bad_explicit_impl.rs b/tests/ui/bad_explicit_impl.rs
new file mode 100644
index 0000000..2106446
--- /dev/null
+++ b/tests/ui/bad_explicit_impl.rs
@@ -0,0 +1,10 @@
+#[cxx::bridge]
+mod ffi {
+ struct S {
+ x: u8,
+ }
+
+ impl fn() -> &S {}
+}
+
+fn main() {}
diff --git a/tests/ui/bad_explicit_impl.stderr b/tests/ui/bad_explicit_impl.stderr
new file mode 100644
index 0000000..cd0a317
--- /dev/null
+++ b/tests/ui/bad_explicit_impl.stderr
@@ -0,0 +1,5 @@
+error: unsupported Self type of explicit impl
+ --> $DIR/bad_explicit_impl.rs:7:5
+ |
+7 | impl fn() -> &S {}
+ | ^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/by_value_not_supported.stderr b/tests/ui/by_value_not_supported.stderr
index 1ff8dbf..0a56dd4 100644
--- a/tests/ui/by_value_not_supported.stderr
+++ b/tests/ui/by_value_not_supported.stderr
@@ -1,4 +1,4 @@
-error: using C++ type by value is not supported
+error: using opaque C++ type by value is not supported
--> $DIR/by_value_not_supported.rs:4:9
|
4 | c: C,
@@ -16,13 +16,19 @@
6 | s: CxxString,
| ^^^^^^^^^^^^
-error: passing C++ type by value is not supported
+error: needs a cxx::ExternType impl in order to be used as a field of `S`
+ --> $DIR/by_value_not_supported.rs:10:9
+ |
+10 | type C;
+ | ^^^^^^
+
+error: passing opaque C++ type by value is not supported
--> $DIR/by_value_not_supported.rs:16:14
|
16 | fn f(c: C) -> C;
| ^^^^
-error: returning C++ type by value is not supported
+error: returning opaque C++ type by value is not supported
--> $DIR/by_value_not_supported.rs:16:23
|
16 | fn f(c: C) -> C;
diff --git a/tests/ui/empty_enum.rs b/tests/ui/empty_enum.rs
index a9ad533..987004b 100644
--- a/tests/ui/empty_enum.rs
+++ b/tests/ui/empty_enum.rs
@@ -1,8 +1,6 @@
#[cxx::bridge]
mod ffi {
- enum A {
-
- }
+ enum A {}
}
fn main() {}
diff --git a/tests/ui/empty_enum.stderr b/tests/ui/empty_enum.stderr
index c73578a..7f35019 100644
--- a/tests/ui/empty_enum.stderr
+++ b/tests/ui/empty_enum.stderr
@@ -1,7 +1,5 @@
error: enums without any variants are not supported
--> $DIR/empty_enum.rs:3:5
|
-3 | / enum A {
-4 | |
-5 | | }
- | |_____^
+3 | enum A {}
+ | ^^^^^^^^^
diff --git a/tests/ui/impl_trait_for_type.rs b/tests/ui/impl_trait_for_type.rs
new file mode 100644
index 0000000..9284f73
--- /dev/null
+++ b/tests/ui/impl_trait_for_type.rs
@@ -0,0 +1,10 @@
+#[cxx::bridge]
+mod ffi {
+ struct S {
+ x: u8,
+ }
+
+ impl UniquePtrTarget for S {}
+}
+
+fn main() {}
diff --git a/tests/ui/impl_trait_for_type.stderr b/tests/ui/impl_trait_for_type.stderr
new file mode 100644
index 0000000..e05a461
--- /dev/null
+++ b/tests/ui/impl_trait_for_type.stderr
@@ -0,0 +1,5 @@
+error: unexpected impl, expected something like `impl UniquePtr<T> {}`
+ --> $DIR/impl_trait_for_type.rs:7:10
+ |
+7 | impl UniquePtrTarget for S {}
+ | ^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/missing_unsafe.rs b/tests/ui/missing_unsafe.rs
new file mode 100644
index 0000000..d8c0a23
--- /dev/null
+++ b/tests/ui/missing_unsafe.rs
@@ -0,0 +1,10 @@
+#[cxx::bridge]
+mod ffi {
+ extern "Rust" {
+ fn f(x: i32);
+ }
+}
+
+unsafe fn f(_x: i32) {}
+
+fn main() {}
diff --git a/tests/ui/missing_unsafe.stderr b/tests/ui/missing_unsafe.stderr
new file mode 100644
index 0000000..df1bce2
--- /dev/null
+++ b/tests/ui/missing_unsafe.stderr
@@ -0,0 +1,7 @@
+error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
+ --> $DIR/missing_unsafe.rs:4:12
+ |
+4 | fn f(x: i32);
+ | ^ call to unsafe function
+ |
+ = note: consult the function's documentation for information on how to avoid undefined behavior
diff --git a/tests/ui/multiple_parse_error.rs b/tests/ui/multiple_parse_error.rs
index 061eab6..138d6d6 100644
--- a/tests/ui/multiple_parse_error.rs
+++ b/tests/ui/multiple_parse_error.rs
@@ -2,8 +2,7 @@
mod ffi {
struct Monad<T>;
- extern "Haskell" {
- }
+ extern "Haskell" {}
}
fn main() {}
diff --git a/tests/ui/multiple_parse_error.stderr b/tests/ui/multiple_parse_error.stderr
index 854aa90..4189507 100644
--- a/tests/ui/multiple_parse_error.stderr
+++ b/tests/ui/multiple_parse_error.stderr
@@ -7,5 +7,5 @@
error: unrecognized ABI
--> $DIR/multiple_parse_error.rs:5:5
|
-5 | extern "Haskell" {
+5 | extern "Haskell" {}
| ^^^^^^^^^^^^^^^^
diff --git a/tests/ui/nonempty_impl_block.rs b/tests/ui/nonempty_impl_block.rs
new file mode 100644
index 0000000..239d1ec
--- /dev/null
+++ b/tests/ui/nonempty_impl_block.rs
@@ -0,0 +1,12 @@
+#[cxx::bridge]
+mod ffi {
+ struct S {
+ x: u8,
+ }
+
+ impl UniquePtr<S> {
+ fn new() -> Self;
+ }
+}
+
+fn main() {}
diff --git a/tests/ui/nonempty_impl_block.stderr b/tests/ui/nonempty_impl_block.stderr
new file mode 100644
index 0000000..e7881bb
--- /dev/null
+++ b/tests/ui/nonempty_impl_block.stderr
@@ -0,0 +1,8 @@
+error: expected an empty impl block
+ --> $DIR/nonempty_impl_block.rs:7:23
+ |
+7 | impl UniquePtr<S> {
+ | _______________________^
+8 | | fn new() -> Self;
+9 | | }
+ | |_____^
diff --git a/tests/ui/opaque_not_sized.stderr b/tests/ui/opaque_not_sized.stderr
index efd1144..9818e44 100644
--- a/tests/ui/opaque_not_sized.stderr
+++ b/tests/ui/opaque_not_sized.stderr
@@ -1,8 +1,11 @@
error[E0277]: the size for values of type `str` cannot be known at compilation time
- --> $DIR/opaque_not_sized.rs:4:14
- |
-4 | type TypeR;
- | ^^^^^ doesn't have a size known at compile-time
- |
- = help: within `TypeR`, the trait `std::marker::Sized` is not implemented for `str`
- = note: required because it appears within the type `TypeR`
+ --> $DIR/opaque_not_sized.rs:4:14
+ |
+4 | type TypeR;
+ | -----^^^^^-
+ | | |
+ | | doesn't have a size known at compile-time
+ | required by this bound in `__AssertSized`
+ |
+ = help: within `TypeR`, the trait `Sized` is not implemented for `str`
+ = note: required because it appears within the type `TypeR`
diff --git a/tests/ui/result_no_display.rs b/tests/ui/result_no_display.rs
new file mode 100644
index 0000000..b535677
--- /dev/null
+++ b/tests/ui/result_no_display.rs
@@ -0,0 +1,14 @@
+#[cxx::bridge]
+mod ffi {
+ extern "Rust" {
+ fn f() -> Result<()>;
+ }
+}
+
+pub struct NonError;
+
+fn f() -> Result<(), NonError> {
+ Ok(())
+}
+
+fn main() {}
diff --git a/tests/ui/result_no_display.stderr b/tests/ui/result_no_display.stderr
new file mode 100644
index 0000000..d0502f9
--- /dev/null
+++ b/tests/ui/result_no_display.stderr
@@ -0,0 +1,8 @@
+error[E0277]: `NonError` doesn't implement `std::fmt::Display`
+ --> $DIR/result_no_display.rs:4:19
+ |
+4 | fn f() -> Result<()>;
+ | ^^^^^^^^^^ `NonError` cannot be formatted with the default formatter
+ |
+ = help: the trait `std::fmt::Display` is not implemented for `NonError`
+ = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
diff --git a/tests/ui/struct_cycle.rs b/tests/ui/struct_cycle.rs
new file mode 100644
index 0000000..e74c2b1
--- /dev/null
+++ b/tests/ui/struct_cycle.rs
@@ -0,0 +1,34 @@
+#[cxx::bridge]
+mod ffi {
+ struct Node0 {
+ i: i32,
+ }
+
+ struct Node1 {
+ node2: Node2,
+ vec: Vec<Node3>,
+ }
+
+ struct Node2 {
+ node4: Node4,
+ }
+
+ struct Node3 {
+ node1: Node1,
+ }
+
+ struct Node4 {
+ node0: Node0,
+ node5: Node5,
+ }
+
+ struct Node5 {
+ node2: Node2,
+ }
+
+ struct Node6 {
+ node2: Node2,
+ }
+}
+
+fn main() {}
diff --git a/tests/ui/struct_cycle.stderr b/tests/ui/struct_cycle.stderr
new file mode 100644
index 0000000..49bcb5f
--- /dev/null
+++ b/tests/ui/struct_cycle.stderr
@@ -0,0 +1,23 @@
+error: unsupported cyclic data structure
+ --> $DIR/struct_cycle.rs:26:9
+ |
+26 | node2: Node2,
+ | ^^^^^^^^^^^^
+
+error: unsupported cyclic data structure
+ --> $DIR/struct_cycle.rs:22:9
+ |
+22 | node5: Node5,
+ | ^^^^^^^^^^^^
+
+error: unsupported cyclic data structure
+ --> $DIR/struct_cycle.rs:13:9
+ |
+13 | node4: Node4,
+ | ^^^^^^^^^^^^
+
+error: unsupported cyclic data structure
+ --> $DIR/struct_cycle.rs:8:9
+ |
+8 | node2: Node2,
+ | ^^^^^^^^^^^^
diff --git a/tests/ui/unique_ptr_to_opaque.rs b/tests/ui/unique_ptr_to_opaque.rs
new file mode 100644
index 0000000..720ca69
--- /dev/null
+++ b/tests/ui/unique_ptr_to_opaque.rs
@@ -0,0 +1,23 @@
+mod outside {
+ #[repr(C)]
+ pub struct C {
+ pub a: u8,
+ }
+ unsafe impl cxx::ExternType for C {
+ type Id = cxx::type_id!("C");
+ type Kind = cxx::kind::Opaque;
+ }
+}
+
+#[cxx::bridge]
+mod ffi {
+ extern "C" {
+ type C = crate::outside::C;
+ }
+
+ impl UniquePtr<C> {}
+}
+
+fn main() {
+ cxx::UniquePtr::new(outside::C { a: 4 });
+}
diff --git a/tests/ui/unique_ptr_to_opaque.stderr b/tests/ui/unique_ptr_to_opaque.stderr
new file mode 100644
index 0000000..19d76a3
--- /dev/null
+++ b/tests/ui/unique_ptr_to_opaque.stderr
@@ -0,0 +1,7 @@
+error[E0271]: type mismatch resolving `<outside::C as ExternType>::Kind == Trivial`
+ --> $DIR/unique_ptr_to_opaque.rs:22:5
+ |
+22 | cxx::UniquePtr::new(outside::C { a: 4 });
+ | ^^^^^^^^^^^^^^^^^^^ expected enum `Trivial`, found enum `cxx::kind::Opaque`
+ |
+ = note: required by `UniquePtr::<T>::new`
diff --git a/tests/ui/unique_ptr_twice.rs b/tests/ui/unique_ptr_twice.rs
new file mode 100644
index 0000000..b6cb4d4
--- /dev/null
+++ b/tests/ui/unique_ptr_twice.rs
@@ -0,0 +1,19 @@
+#[cxx::bridge]
+mod here {
+ extern "C" {
+ type C;
+ }
+
+ impl UniquePtr<C> {}
+}
+
+#[cxx::bridge]
+mod there {
+ extern "C" {
+ type C = crate::here::C;
+ }
+
+ impl UniquePtr<C> {}
+}
+
+fn main() {}
diff --git a/tests/ui/unique_ptr_twice.stderr b/tests/ui/unique_ptr_twice.stderr
new file mode 100644
index 0000000..5686cf1
--- /dev/null
+++ b/tests/ui/unique_ptr_twice.stderr
@@ -0,0 +1,8 @@
+error[E0119]: conflicting implementations of trait `cxx::private::UniquePtrTarget` for type `here::C`:
+ --> $DIR/unique_ptr_twice.rs:16:5
+ |
+7 | impl UniquePtr<C> {}
+ | ----------------- first implementation here
+...
+16 | impl UniquePtr<C> {}
+ | ^^^^^^^^^^^^^^^^^ conflicting implementation for `here::C`
diff --git a/tests/ui/unsupported_elided.rs b/tests/ui/unsupported_elided.rs
new file mode 100644
index 0000000..4033319
--- /dev/null
+++ b/tests/ui/unsupported_elided.rs
@@ -0,0 +1,20 @@
+use std::marker::PhantomData;
+
+#[cxx::bridge]
+mod ffi {
+ extern "Rust" {
+ type T;
+
+ fn f(t: &T) -> &str;
+ }
+}
+
+pub struct T<'a> {
+ _lifetime: PhantomData<&'a ()>,
+}
+
+fn f<'a>(_t: &T<'a>) -> &'a str {
+ ""
+}
+
+fn main() {}
diff --git a/tests/ui/unsupported_elided.stderr b/tests/ui/unsupported_elided.stderr
new file mode 100644
index 0000000..5136390
--- /dev/null
+++ b/tests/ui/unsupported_elided.stderr
@@ -0,0 +1,11 @@
+error[E0106]: missing lifetime specifier
+ --> $DIR/unsupported_elided.rs:8:24
+ |
+8 | fn f(t: &T) -> &str;
+ | -- ^ expected named lifetime parameter
+ |
+ = help: this function's return type contains a borrowed value, but the signature does not say which one of `t`'s 2 lifetimes it is borrowed from
+help: consider introducing a named lifetime parameter
+ |
+8 | fn f<'a>(t: &'a T) -> &'a str;
+ | ^^^^ ^^^^^ ^^^
diff --git a/tests/ui/wrong_type_id.rs b/tests/ui/wrong_type_id.rs
index 81a9b3f..e3d1380 100644
--- a/tests/ui/wrong_type_id.rs
+++ b/tests/ui/wrong_type_id.rs
@@ -1,11 +1,11 @@
-#[cxx::bridge(namespace = folly)]
+#[cxx::bridge(namespace = "folly")]
mod here {
extern "C" {
type StringPiece;
}
}
-#[cxx::bridge(namespace = folly)]
+#[cxx::bridge(namespace = "folly")]
mod there {
extern "C" {
type ByteRange = crate::here::StringPiece;
diff --git a/tests/ui/wrong_type_id.stderr b/tests/ui/wrong_type_id.stderr
index cb3ae15..2d8e50a 100644
--- a/tests/ui/wrong_type_id.stderr
+++ b/tests/ui/wrong_type_id.stderr
@@ -1,13 +1,13 @@
-error[E0271]: type mismatch resolving `<here::StringPiece as cxx::ExternType>::Id == (cxx::f, cxx::o, cxx::l, cxx::l, cxx::y, (), cxx::B, cxx::y, cxx::t, cxx::e, cxx::R, cxx::a, cxx::n, cxx::g, cxx::e)`
+error[E0271]: type mismatch resolving `<StringPiece as ExternType>::Id == (f, o, l, l, y, (), B, y, t, e, R, a, n, g, e)`
--> $DIR/wrong_type_id.rs:11:9
|
11 | type ByteRange = crate::here::StringPiece;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected a tuple with 15 elements, found one with 17 elements
|
- ::: $WORKSPACE/src/extern_type.rs:110:41
+ ::: $WORKSPACE/src/extern_type.rs
|
-110 | pub fn verify_extern_type<T: ExternType<Id = Id>, Id>() {}
- | ------- required by this bound in `cxx::private::verify_extern_type`
+ | pub fn verify_extern_type<T: ExternType<Id = Id>, Id>() {}
+ | ------- required by this bound in `verify_extern_type`
|
- = note: expected tuple `(cxx::f, cxx::o, cxx::l, cxx::l, cxx::y, (), cxx::B, cxx::y, cxx::t, cxx::e, cxx::R, cxx::a, cxx::n, cxx::g, cxx::e)`
- found tuple `(cxx::f, cxx::o, cxx::l, cxx::l, cxx::y, (), cxx::S, cxx::t, cxx::r, cxx::i, cxx::n, cxx::g, cxx::P, cxx::i, cxx::e, cxx::c, cxx::e)`
+ = note: expected tuple `(f, o, l, l, y, (), B, y, t, e, R, a, n, g, e)`
+ found tuple `(f, o, l, l, y, (), S, t, r, i, n, g, P, i, e, c, e)`
diff --git a/third-party/BUCK b/third-party/BUCK
index 9890a71..a9a3913 100644
--- a/third-party/BUCK
+++ b/third-party/BUCK
@@ -1,27 +1,21 @@
# To be generated by Facebook's `reindeer` tool once that is open source.
rust_library(
- name = "anyhow",
- srcs = glob(["vendor/anyhow-1.0.32/src/**"]),
- visibility = ["PUBLIC"],
- features = ["std"],
-)
-
-rust_library(
name = "bitflags",
srcs = glob(["vendor/bitflags-1.2.1/src/**"]),
)
rust_library(
name = "cc",
- srcs = glob(["vendor/cc-1.0.58/src/**"]),
+ srcs = glob(["vendor/cc-1.0.62/src/**"]),
visibility = ["PUBLIC"],
)
rust_library(
name = "clap",
- srcs = glob(["vendor/clap-2.33.1/src/**"]),
+ srcs = glob(["vendor/clap-2.33.3/src/**"]),
edition = "2015",
+ visibility = ["PUBLIC"],
deps = [
":bitflags",
":textwrap",
@@ -40,50 +34,14 @@
)
rust_library(
- name = "heck",
- srcs = glob(["vendor/heck-0.3.1/src/**"]),
- edition = "2015",
- deps = [":unicode-segmentation"],
-)
-
-rust_library(
name = "lazy_static",
srcs = glob(["vendor/lazy_static-1.4.0/src/**"]),
-)
-
-rust_library(
- name = "link-cplusplus",
- srcs = glob(["vendor/link-cplusplus-1.0.2/src/**"]),
visibility = ["PUBLIC"],
)
rust_library(
- name = "proc-macro-error",
- srcs = glob(["vendor/proc-macro-error-1.0.3/src/**"]),
- rustc_flags = ["--cfg=use_fallback"],
- deps = [
- ":proc-macro-error-attr",
- ":proc-macro2",
- ":quote",
- ":syn",
- ],
-)
-
-rust_library(
- name = "proc-macro-error-attr",
- srcs = glob(["vendor/proc-macro-error-attr-1.0.3/src/**"]),
- proc_macro = True,
- deps = [
- ":proc-macro2",
- ":quote",
- ":syn",
- ":syn-mid",
- ],
-)
-
-rust_library(
name = "proc-macro2",
- srcs = glob(["vendor/proc-macro2-1.0.19/src/**"]),
+ srcs = glob(["vendor/proc-macro2-1.0.24/src/**"]),
visibility = ["PUBLIC"],
features = [
"proc-macro",
@@ -106,32 +64,15 @@
)
rust_library(
- name = "structopt",
- srcs = glob(["vendor/structopt-0.3.15/src/**"]),
+ name = "scratch",
+ srcs = glob(["vendor/scratch-1.0.0/src/**"]),
+ env = {"OUT_DIR": ""},
visibility = ["PUBLIC"],
- deps = [
- ":clap",
- ":lazy_static",
- ":structopt-derive",
- ],
-)
-
-rust_library(
- name = "structopt-derive",
- srcs = glob(["vendor/structopt-derive-0.4.8/src/**"]),
- proc_macro = True,
- deps = [
- ":heck",
- ":proc-macro-error",
- ":proc-macro2",
- ":quote",
- ":syn",
- ],
)
rust_library(
name = "syn",
- srcs = glob(["vendor/syn-1.0.36/src/**"]),
+ srcs = glob(["vendor/syn-1.0.48/src/**"]),
visibility = ["PUBLIC"],
features = [
"clone-impls",
@@ -149,16 +90,6 @@
)
rust_library(
- name = "syn-mid",
- srcs = glob(["vendor/syn-mid-0.5.0/src/**"]),
- deps = [
- ":proc-macro2",
- ":quote",
- ":syn",
- ],
-)
-
-rust_library(
name = "termcolor",
srcs = glob(["vendor/termcolor-1.1.0/src/**"]),
)
@@ -170,12 +101,6 @@
)
rust_library(
- name = "unicode-segmentation",
- srcs = glob(["vendor/unicode-segmentation-1.6.0/src/**"]),
- edition = "2015",
-)
-
-rust_library(
name = "unicode-width",
srcs = glob(["vendor/unicode-width-0.1.8/src/**"]),
)
diff --git a/third-party/BUILD b/third-party/BUILD
index a01d4b7..d72b083 100644
--- a/third-party/BUILD
+++ b/third-party/BUILD
@@ -1,32 +1,27 @@
load(
"//tools/bazel:rust.bzl",
+ glob = "third_party_glob",
rust_binary = "third_party_rust_binary",
rust_library = "third_party_rust_library",
)
load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar")
rust_library(
- name = "anyhow",
- srcs = glob(["vendor/anyhow-1.0.32/src/**"]),
- crate_features = ["std"],
- visibility = ["//visibility:public"],
-)
-
-rust_library(
name = "bitflags",
srcs = glob(["vendor/bitflags-1.2.1/src/**"]),
)
rust_library(
name = "cc",
- srcs = glob(["vendor/cc-1.0.58/src/**"]),
+ srcs = glob(["vendor/cc-1.0.62/src/**"]),
visibility = ["//visibility:public"],
)
rust_library(
name = "clap",
- srcs = glob(["vendor/clap-2.33.1/src/**"]),
+ srcs = glob(["vendor/clap-2.33.3/src/**"]),
edition = "2015",
+ visibility = ["//visibility:public"],
deps = [
":bitflags",
":textwrap",
@@ -45,52 +40,14 @@
)
rust_library(
- name = "heck",
- srcs = glob(["vendor/heck-0.3.1/src/**"]),
- edition = "2015",
- deps = [":unicode-segmentation"],
-)
-
-rust_library(
name = "lazy_static",
srcs = glob(["vendor/lazy_static-1.4.0/src/**"]),
-)
-
-rust_library(
- name = "link-cplusplus",
- srcs = glob(["vendor/link-cplusplus-1.0.2/src/**"]),
visibility = ["//visibility:public"],
)
rust_library(
- name = "proc-macro-error",
- srcs = glob(["vendor/proc-macro-error-1.0.3/src/**"]),
- proc_macro_deps = [
- ":proc-macro-error-attr",
- ],
- rustc_flags = ["--cfg=use_fallback"],
- deps = [
- ":proc-macro2",
- ":quote",
- ":syn",
- ],
-)
-
-rust_library(
- name = "proc-macro-error-attr",
- srcs = glob(["vendor/proc-macro-error-attr-1.0.3/src/**"]),
- crate_type = "proc-macro",
- deps = [
- ":proc-macro2",
- ":quote",
- ":syn",
- ":syn-mid",
- ],
-)
-
-rust_library(
name = "proc-macro2",
- srcs = glob(["vendor/proc-macro2-1.0.19/src/**"]),
+ srcs = glob(["vendor/proc-macro2-1.0.24/src/**"]),
crate_features = [
"proc-macro",
"span-locations",
@@ -113,34 +70,15 @@
)
rust_library(
- name = "structopt",
- srcs = glob(["vendor/structopt-0.3.15/src/**"]),
- proc_macro_deps = [
- ":structopt-derive",
- ],
+ name = "scratch",
+ srcs = glob(["vendor/scratch-1.0.0/src/**"]),
+ rustc_env = {"OUT_DIR": ""},
visibility = ["//visibility:public"],
- deps = [
- ":clap",
- ":lazy_static",
- ],
-)
-
-rust_library(
- name = "structopt-derive",
- srcs = glob(["vendor/structopt-derive-0.4.8/src/**"]),
- crate_type = "proc-macro",
- deps = [
- ":heck",
- ":proc-macro-error",
- ":proc-macro2",
- ":quote",
- ":syn",
- ],
)
rust_library(
name = "syn",
- srcs = glob(["vendor/syn-1.0.36/src/**"]),
+ srcs = glob(["vendor/syn-1.0.48/src/**"]),
crate_features = [
"clone-impls",
"derive",
@@ -158,16 +96,6 @@
)
rust_library(
- name = "syn-mid",
- srcs = glob(["vendor/syn-mid-0.5.0/src/**"]),
- deps = [
- ":proc-macro2",
- ":quote",
- ":syn",
- ],
-)
-
-rust_library(
name = "termcolor",
srcs = glob(["vendor/termcolor-1.1.0/src/**"]),
)
@@ -179,12 +107,6 @@
)
rust_library(
- name = "unicode-segmentation",
- srcs = glob(["vendor/unicode-segmentation-1.6.0/src/**"]),
- edition = "2015",
-)
-
-rust_library(
name = "unicode-width",
srcs = glob(["vendor/unicode-width-0.1.8/src/**"]),
)
diff --git a/third-party/Cargo.lock b/third-party/Cargo.lock
index 8f40664..76b95f0 100644
--- a/third-party/Cargo.lock
+++ b/third-party/Cargo.lock
@@ -10,12 +10,6 @@
]
[[package]]
-name = "anyhow"
-version = "1.0.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
-
-[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -34,15 +28,15 @@
[[package]]
name = "cc"
-version = "1.0.58"
+version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
+checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40"
[[package]]
name = "clap"
-version = "2.33.1"
+version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
@@ -65,11 +59,13 @@
[[package]]
name = "cxx"
-version = "0.3.4"
+version = "0.5.9"
dependencies = [
"cc",
"cxx-build",
+ "cxx-gen",
"cxx-test-suite",
+ "cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
"rustversion",
@@ -78,9 +74,23 @@
[[package]]
name = "cxx-build"
-version = "0.3.4"
+version = "0.5.9"
dependencies = [
- "anyhow",
+ "cc",
+ "codespan-reporting",
+ "cxx-gen",
+ "lazy_static",
+ "pkg-config",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxx-gen"
+version = "0.6.6"
+dependencies = [
"cc",
"codespan-reporting",
"proc-macro2",
@@ -94,22 +104,36 @@
dependencies = [
"cxx",
"cxx-build",
+ "cxxbridge-flags",
]
[[package]]
name = "cxxbridge-cmd"
-version = "0.3.4"
+version = "0.5.9"
dependencies = [
- "anyhow",
+ "clap",
"codespan-reporting",
"proc-macro2",
"quote",
- "structopt",
"syn",
]
[[package]]
-name = "cxxbridge-demo"
+name = "cxxbridge-flags"
+version = "0.5.9"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "0.5.9"
+dependencies = [
+ "cxx",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "demo"
version = "0.0.0"
dependencies = [
"cxx",
@@ -117,16 +141,6 @@
]
[[package]]
-name = "cxxbridge-macro"
-version = "0.3.4"
-dependencies = [
- "cxx",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "dissimilar"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -139,19 +153,10 @@
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
-name = "heck"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
-dependencies = [
- "unicode-segmentation",
-]
-
-[[package]]
name = "hermit-abi"
-version = "0.1.15"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
dependencies = [
"libc",
]
@@ -170,50 +175,30 @@
[[package]]
name = "libc"
-version = "0.2.74"
+version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
[[package]]
name = "link-cplusplus"
-version = "1.0.2"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f563b3814ea63e830e3e321206e4e2b5177854586ebe3f595795ed7053b217f5"
+checksum = "f96aa785c87218ec773df6c510af203872b34e2df2cf47d6e908e5f36231e354"
dependencies = [
"cc",
]
[[package]]
-name = "proc-macro-error"
-version = "1.0.3"
+name = "pkg-config"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc175e9777c3116627248584e8f8b3e2987405cabe1c0adf7d1dd28f09dc7880"
-dependencies = [
- "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "syn",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cc9795ca17eb581285ec44936da7fc2335a3f34f2ddd13118b6f4d515435c50"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "syn-mid",
- "version_check",
-]
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "proc-macro2"
-version = "1.0.19"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
@@ -229,14 +214,9 @@
[[package]]
name = "rustversion"
-version = "1.0.3"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9bdc5e856e51e685846fb6c13a1f5e5432946c2c90501bdc76a1319f19e29da"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
+checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd"
[[package]]
name = "ryu"
@@ -245,19 +225,25 @@
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
-name = "serde"
-version = "1.0.114"
+name = "scratch"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
+checksum = "7e114536316b51a5aa7a0e59fc49661fd263c5507dd08bd28de052e57626ce69"
+
+[[package]]
+name = "serde"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.114"
+version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
+checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
dependencies = [
"proc-macro2",
"quote",
@@ -266,9 +252,9 @@
[[package]]
name = "serde_json"
-version = "1.0.57"
+version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
+checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
dependencies = [
"itoa",
"ryu",
@@ -282,34 +268,10 @@
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
-name = "structopt"
-version = "0.3.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de2f5e239ee807089b62adce73e48c625e0ed80df02c7ab3f068f5db5281065c"
-dependencies = [
- "clap",
- "lazy_static",
- "structopt-derive",
-]
-
-[[package]]
-name = "structopt-derive"
-version = "0.4.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "510413f9de616762a4fbeab62509bf15c729603b72d7cd71280fbca431b1c118"
-dependencies = [
- "heck",
- "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "syn"
-version = "1.0.36"
+version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250"
+checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
dependencies = [
"proc-macro2",
"quote",
@@ -317,17 +279,6 @@
]
[[package]]
-name = "syn-mid"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "termcolor"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -347,18 +298,18 @@
[[package]]
name = "toml"
-version = "0.5.6"
+version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
dependencies = [
"serde",
]
[[package]]
name = "trybuild"
-version = "1.0.31"
+version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a4d94e6adf00b96b1ab94fcfcd8c3cf916733b39adf90c8f72693629887b9b8"
+checksum = "b7d30fe369fd650072b352b1a9cb9587669de6b89be3b8225544012c1c45292d"
dependencies = [
"dissimilar",
"glob",
@@ -370,12 +321,6 @@
]
[[package]]
-name = "unicode-segmentation"
-version = "1.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
-
-[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -394,12 +339,6 @@
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
-name = "version_check"
-version = "0.9.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
-
-[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/tools/bazel/rust.bzl b/tools/bazel/rust.bzl
index 3ef4d91..b7b23e5 100644
--- a/tools/bazel/rust.bzl
+++ b/tools/bazel/rust.bzl
@@ -4,6 +4,10 @@
_rust_library = "rust_library",
_rust_test = "rust_test",
)
+load("@third-party//:vendor.bzl", "vendored")
+
+def third_party_glob(include):
+ return vendored and native.glob(include)
def rust_binary(edition = "2018", **kwargs):
_rust_binary(edition = edition, **kwargs)
diff --git a/tools/bazel/rust_cxx_bridge.bzl b/tools/bazel/rust_cxx_bridge.bzl
new file mode 100644
index 0000000..534f6b5
--- /dev/null
+++ b/tools/bazel/rust_cxx_bridge.bzl
@@ -0,0 +1,41 @@
+load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
+load("@rules_cc//cc:defs.bzl", "cc_library")
+
+def rust_cxx_bridge(name, src, deps = []):
+ native.alias(
+ name = "%s/header" % name,
+ actual = src + ".h",
+ )
+
+ native.alias(
+ name = "%s/source" % name,
+ actual = src + ".cc",
+ )
+
+ run_binary(
+ name = "%s/generated" % name,
+ srcs = [src],
+ outs = [
+ src + ".h",
+ src + ".cc",
+ ],
+ args = [
+ "$(location %s)" % src,
+ "-o",
+ "$(location %s.h)" % src,
+ "-o",
+ "$(location %s.cc)" % src,
+ ],
+ tool = "//:codegen",
+ )
+
+ cc_library(
+ name = name,
+ srcs = [src + ".cc"],
+ deps = deps + [":%s/include" % name],
+ )
+
+ cc_library(
+ name = "%s/include" % name,
+ hdrs = [src + ".h"],
+ )
diff --git a/tools/bazel/vendor.bzl b/tools/bazel/vendor.bzl
new file mode 100644
index 0000000..e9f10ac
--- /dev/null
+++ b/tools/bazel/vendor.bzl
@@ -0,0 +1,54 @@
+def _impl(repository_ctx):
+ # Link cxx repository into @third-party.
+ lockfile = repository_ctx.path(repository_ctx.attr.lockfile)
+ workspace = lockfile.dirname.dirname
+ repository_ctx.symlink(workspace, "workspace")
+
+ # Copy third-party/Cargo.lock since those are the crate versions that the
+ # BUILD file is written against.
+ vendor_lockfile = repository_ctx.path("workspace/third-party/Cargo.lock")
+ root_lockfile = repository_ctx.path("workspace/Cargo.lock")
+ _copy_file(repository_ctx, src = vendor_lockfile, dst = root_lockfile)
+
+ # Execute cargo vendor.
+ cmd = ["cargo", "vendor", "--versioned-dirs", "third-party/vendor"]
+ result = repository_ctx.execute(
+ cmd,
+ quiet = True,
+ working_directory = "workspace",
+ )
+ _log_cargo_vendor(repository_ctx, result)
+ if result.return_code != 0:
+ fail("failed to execute `{}`".format(" ".join(cmd)))
+
+ # Copy lockfile back to third-party/Cargo.lock to reflect any modification
+ # performed by Cargo.
+ _copy_file(repository_ctx, src = root_lockfile, dst = vendor_lockfile)
+
+ # Produce a token for third_party_glob to depend on so that the necessary
+ # sequencing is visible to Bazel.
+ repository_ctx.file("BUILD", executable = False)
+ repository_ctx.file("vendor.bzl", "vendored = True", executable = False)
+
+def _copy_file(repository_ctx, *, src, dst):
+ content = repository_ctx.read(src)
+ if not dst.exists or content != repository_ctx.read(dst):
+ repository_ctx.file(dst, content = content, executable = False)
+
+def _log_cargo_vendor(repository_ctx, result):
+ relevant = ""
+ for line in result.stderr.splitlines(True):
+ if line.strip() and not line.startswith("To use vendored sources,"):
+ relevant += line
+ if relevant:
+ # Render it as command output.
+ # If we just use print(), Bazel will cache and repeat the output even
+ # when not rerunning the command.
+ print = ["echo", relevant]
+ repository_ctx.execute(print, quiet = False)
+
+vendor = repository_rule(
+ attrs = {"lockfile": attr.label()},
+ local = True,
+ implementation = _impl,
+)
diff --git a/tools/buck/genrule.bzl b/tools/buck/genrule.bzl
new file mode 100644
index 0000000..b5364b7
--- /dev/null
+++ b/tools/buck/genrule.bzl
@@ -0,0 +1,8 @@
+def genrule(cmd, **kwargs):
+ # Resolve a distracting inconsistency between Buck and Bazel.
+ # Bazel creates the directory for your output file, while Buck expects the
+ # cmd to create it.
+ #
+ # TODO: send this as a PR to Buck, because Bazel's behavior here is better.
+ cmd = "mkdir -p `dirname ${OUT}`; " + cmd
+ native.genrule(cmd = cmd, **kwargs)
diff --git a/tools/buck/rust_cxx_bridge.bzl b/tools/buck/rust_cxx_bridge.bzl
new file mode 100644
index 0000000..4acc7c6
--- /dev/null
+++ b/tools/buck/rust_cxx_bridge.bzl
@@ -0,0 +1,34 @@
+load("//tools/buck:genrule.bzl", "genrule")
+
+def rust_cxx_bridge(name, src, deps = []):
+ genrule(
+ name = "%s/header" % name,
+ out = src + ".h",
+ cmd = "cp $(location :%s/generated)/generated.h ${OUT}" % name,
+ )
+
+ genrule(
+ name = "%s/source" % name,
+ out = src + ".cc",
+ cmd = "cp $(location :%s/generated)/generated.cc ${OUT}" % name,
+ )
+
+ genrule(
+ name = "%s/generated" % name,
+ srcs = [src],
+ out = ".",
+ cmd = "$(exe //:codegen) ${SRCS} -o ${OUT}/generated.h -o ${OUT}/generated.cc",
+ type = "cxxbridge",
+ )
+
+ cxx_library(
+ name = name,
+ srcs = [":%s/source" % name],
+ preferred_linkage = "static",
+ deps = deps + [":%s/include" % name],
+ )
+
+ cxx_library(
+ name = "%s/include" % name,
+ exported_headers = [":%s/header" % name],
+ )
diff --git a/tools/cargo/build.rs b/tools/cargo/build.rs
new file mode 100644
index 0000000..6c9d22d
--- /dev/null
+++ b/tools/cargo/build.rs
@@ -0,0 +1,73 @@
+use std::io::{self, Write};
+#[cfg(windows)]
+use std::os::windows::fs as windows;
+use std::path::Path;
+use std::process;
+#[cfg(windows)]
+use std::{env, fs};
+
+const MISSING: &str = "
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+When building `cxx` from a git clone, git's symlink support needs
+to be enabled on platforms that have it off by default (Windows).
+Either use:
+
+ $ git config --global core.symlinks true
+
+prior to cloning, or else use:
+
+ $ git clone -c core.symlinks=true https://github.com/dtolnay/cxx
+
+for the clone.
+
+Symlinks are only required when compiling locally from a clone of
+the git repository---they are NOT required when building `cxx` as
+a Cargo-managed (possibly transitive) build dependency downloaded
+through crates.io.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+";
+
+#[cfg(windows)]
+const DENIED: &str = "
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+When building `cxx` from a git clone on Windows we need Developer
+Mode enabled for symlink support.
+
+To enable Developer Mode: go under Settings to Update & Security,
+then 'For developers', and turn on the toggle for Developer Mode.
+
+For more explanation of symlinks in Windows, see these resources:
+> https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/
+> https://docs.microsoft.com/windows/uwp/get-started/enable-your-device-for-development
+
+Symlinks are only required when compiling locally from a clone of
+the git repository---they are NOT required when building `cxx` as
+a Cargo-managed (possibly transitive) build dependency downloaded
+through crates.io.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+";
+
+fn main() {
+ if Path::new("src/syntax/mod.rs").exists() {
+ return;
+ }
+
+ #[allow(unused_mut)]
+ let mut message = MISSING;
+
+ #[cfg(windows)]
+ if let Some(out_dir) = env::var_os("OUT_DIR") {
+ let parent_dir = Path::new(&out_dir).join("symlink");
+ let from_dir = parent_dir.join("from");
+ let to_dir = parent_dir.join("to");
+ if fs::create_dir_all(&from_dir).is_ok()
+ && fs::remove_dir(&to_dir).is_ok()
+ && windows::symlink_dir(&from_dir, &to_dir).is_err()
+ {
+ message = DENIED;
+ }
+ }
+
+ let _ = io::stderr().write_all(message.as_bytes());
+ process::exit(1);
+}