diff options
author | Matthew Maurer <mmaurer@google.com> | 2021-04-08 21:55:21 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-04-08 21:55:21 +0000 |
commit | 223f628bc09ed6fed06292e170aa1ccac440f384 (patch) | |
tree | 6750e4355c3c7e9e99ac5a198cbba6fb7daa23cc | |
parent | 206d55d5c3dc5cb7f79f50ea85968b1f0a8ac4f9 (diff) | |
parent | 104a118acedea4b6bf96ba7139eb6c502ef05b45 (diff) | |
download | cxx-223f628bc09ed6fed06292e170aa1ccac440f384.tar.gz |
Update CXX to 1.0.42 am: b4406114c9 am: f1e51d9d13 am: 104a118ace
Original change: https://android-review.googlesource.com/c/platform/external/rust/cxx/+/1668766
Change-Id: Iddfc0021ba8e2621691a0c3310f3f5215821ed70
248 files changed, 14193 insertions, 2409 deletions
diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..b0a6da98 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,18 @@ +Checks: + clang-analyzer-*, + clang-diagnostic-*, + cppcoreguidelines-*, + modernize-*, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-const-cast, + -cppcoreguidelines-pro-type-member-init, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-special-member-functions, + -modernize-use-default-member-init, + -modernize-use-equals-default, + -modernize-use-trailing-return-type, +HeaderFilterRegex: cxx\.h diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 00000000..11d46a73 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +msrv = "1.48.0" diff --git a/.devcontainer/build.Dockerfile b/.devcontainer/build.Dockerfile index 6085459d..f2763884 100644 --- a/.devcontainer/build.Dockerfile +++ b/.devcontainer/build.Dockerfile @@ -5,7 +5,7 @@ RUN apt-get update \ && 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/install-bazel https://github.com/bazelbuild/bazel/releases/download/4.0.0/bazel-4.0.0-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 \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26b2722a..3fafd9ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - rust: nightly - rust: beta - rust: stable - - rust: 1.43.0 + - rust: 1.48.0 - name: macOS rust: nightly os: macos @@ -26,6 +26,10 @@ jobs: - name: Windows (msvc) rust: nightly-x86_64-pc-windows-msvc os: windows + flags: /EHsc + env: + CXXFLAGS: ${{matrix.flags}} + RUSTFLAGS: --cfg deny_warnings steps: - name: Enable symlinks (windows) if: matrix.os == 'windows' @@ -42,7 +46,8 @@ jobs: # 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: echo "RUSTFLAGS=--cfg skip_ui_tests $RUSTFLAGS" >> $GITHUB_ENV + shell: bash - run: cargo run --manifest-path demo/Cargo.toml - run: cargo test --workspace --exclude cxx-test-suite @@ -63,7 +68,7 @@ jobs: chmod +x bin/buck echo bin >> $GITHUB_PATH - name: Install lld - run: sudo apt install lld + run: sudo apt-get install lld - name: Vendor dependencies run: | cp third-party/Cargo.lock . @@ -79,7 +84,7 @@ jobs: - uses: actions/checkout@v2 - name: Install Bazel run: | - wget -q -O install.sh https://github.com/bazelbuild/bazel/releases/download/2.1.1/bazel-2.1.1-installer-linux-x86_64.sh + wget -q -O install.sh https://github.com/bazelbuild/bazel/releases/download/4.0.0/bazel-4.0.0-installer-linux-x86_64.sh chmod +x install.sh ./install.sh --user echo $HOME/bin >> $GITHUB_PATH @@ -89,7 +94,19 @@ jobs: clippy: name: Clippy runs-on: ubuntu-latest + if: github.event_name != 'pull_request' steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@clippy - run: cargo clippy --workspace --tests -- -Dclippy::all + + clang-tidy: + name: Clang Tidy + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + steps: + - uses: actions/checkout@v2 + - name: Install clang-tidy + run: sudo apt-get install clang-tidy-11 + - name: Run clang-tidy + run: clang-tidy-11 src/cxx.cc --warnings-as-errors=* diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 73b8186e..d6b0ca6d 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -6,6 +6,7 @@ on: - master paths: - book/** + - .github/workflows/site.yml jobs: deploy: @@ -15,16 +16,15 @@ jobs: - uses: actions/checkout@v2 - name: Get mdBook - working-directory: book run: | - export MDBOOK_VERSION="v0.4.4" + export MDBOOK_VERSION="dtolnay" 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 + export MDBOOK_URL="https://github.com/dtolnay/mdBook/releases/download/cxx/${MDBOOK_TARBALL}" + curl -Lf "${MDBOOK_URL}" | tar -xzC book + book/mdbook --version - name: Build - working-directory: book - run: ./mdbook build + run: book/build.sh - name: Push to gh-pages working-directory: book/build diff --git a/.vscode/launch.json b/.vscode/launch.json index 244f5c4a..0218f47f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,6 +12,18 @@ "kind": "bin" } } + }, + { + "name": "Debug cargo tests", + "type": "lldb", + "request": "launch", + "cargo": { + "args": ["test", "--no-run"], + "filter": { + "name": "test", + "kind": "test" + } + } } ] } @@ -1,13 +1,14 @@ [package] name = "cxx" -version = "0.5.9" # remember to update html_root_url +version = "1.0.42" # remember to update html_root_url authors = ["David Tolnay <dtolnay@gmail.com>"] edition = "2018" -links = "cxxbridge05" +links = "cxxbridge1" license = "MIT OR Apache-2.0" description = "Safe interop between Rust and C++" repository = "https://github.com/dtolnay/cxx" documentation = "https://docs.rs/cxx" +homepage = "https://cxx.rs" readme = "README.md" exclude = ["/demo", "/gen", "/syntax", "/third-party"] keywords = ["ffi"] @@ -20,16 +21,16 @@ default = ["cxxbridge-flags/default"] # c++11 "c++20" = ["cxxbridge-flags/c++20"] [dependencies] -cxxbridge-macro = { version = "=0.5.9", path = "macro" } +cxxbridge-macro = { version = "=1.0.42", path = "macro" } link-cplusplus = "1.0" [build-dependencies] cc = "1.0.49" -cxxbridge-flags = { version = "=0.5.9", path = "flags", default-features = false } +cxxbridge-flags = { version = "=1.0.42", path = "flags", default-features = false } [dev-dependencies] -cxx-build = { version = "=0.5.9", path = "gen/build" } -cxx-gen = { version = "0.6", path = "gen/lib" } +cxx-build = { version = "=1.0.42", path = "gen/build" } +cxx-gen = { version = "0.7", path = "gen/lib" } cxx-test-suite = { version = "0", path = "tests/ffi" } rustversion = "1.0" trybuild = { version = "1.0.33", features = ["diff"] } @@ -39,3 +40,7 @@ members = ["demo", "flags", "gen/build", "gen/cmd", "gen/lib", "macro", "tests/f [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] + +[patch.crates-io] +cxx = { path = "." } +cxx-build = { path = "gen/build" } @@ -18,17 +18,24 @@ can be 100% safe. ```toml [dependencies] -cxx = "0.5" +cxx = "1.0" [build-dependencies] -cxx-build = "0.5" +cxx-build = "1.0" ``` -*Compiler support: requires rustc 1.43+ and c++11 or newer*<br> +*Compiler support: requires rustc 1.48+ and c++11 or newer*<br> *[Release notes](https://github.com/dtolnay/cxx/releases)* <br> +## Guide + +Please see **<https://cxx.rs>** for a tutorial, reference material, and example +code. + +<br> + ## Overview The idea is that we define the signatures of both sides of our FFI boundary @@ -90,7 +97,7 @@ mod ffi { fn next_chunk(buf: &mut MultiBuf) -> &[u8]; } - extern "C++" { + unsafe 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. @@ -151,19 +158,19 @@ items: - **Functions** — implemented in either language, callable from the other language. -Within the `extern "C"` part of the CXX bridge we list the types and functions -for which C++ is the source of truth, as well as the header(s) that declare -those APIs. In the future it's possible that this section could be generated -bindgen-style from the headers but for now we need the signatures written out; -static assertions will verify that they are accurate. +Within the `extern "Rust"` part of the CXX bridge we list the types and +functions for which Rust is the source of truth. These all implicitly refer to +the `super` module, the parent module of the CXX bridge. You can think of the +two items listed in the example above as being like `use super::MultiBuf` and +`use super::next_chunk` except re-exported to C++. The parent module will either +contain the definitions directly for simple things, or contain the relevant +`use` statements to bring them into scope from elsewhere. -Within the `extern "Rust"` part, we list types and functions for which Rust is -the source of truth. These all implicitly refer to the `super` module, the -parent module of the CXX bridge. You can think of the two items listed in the -example above as being like `use super::ThingR` and `use super::print_r` except -re-exported to C++. The parent module will either contain the definitions -directly for simple things, or contain the relevant `use` statements to bring -them into scope from elsewhere. +Within the `extern "C++"` part, we list types and functions for which C++ is the +source of truth, as well as the header(s) that declare those APIs. In the future +it's possible that this section could be generated bindgen-style from the +headers but for now we need the signatures written out; static assertions will +verify that they are accurate. Your function implementations themselves, whether in C++ or Rust, *do not* need to be defined as `extern "C"` ABI or no\_mangle. CXX will put in the right shims @@ -228,7 +235,7 @@ set up any additional source files and compiler flags as normal. # Cargo.toml [build-dependencies] -cxx-build = "0.5" +cxx-build = "1.0" ``` ```rust @@ -315,12 +322,16 @@ returns of functions. <tr><th>name in Rust</th><th>name in C++</th><th>restrictions</th></tr> <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.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>&[T]</td><td>rust::Slice<const T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr> +<tr><td>&mut [T]</td><td>rust::Slice<T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr> +<tr><td><a href="https://docs.rs/cxx/1.0/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.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><a href="https://docs.rs/cxx/1.0/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/1.0/cxx/struct.SharedPtr.html">SharedPtr<T></a></td><td>std::shared_ptr<T></td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr> +<tr><td>[T; N]</td><td>std::array<T, N></td><td><sup><i>cannot hold opaque C++ 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.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><a href="https://docs.rs/cxx/1.0/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>*mut T, *const T</td><td>T*, const T*</td><td><sup><i>fn with a raw pointer argument must be declared unsafe to call</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> @@ -341,7 +352,6 @@ matter of designing a nice API for each in its non-native language. <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> <br> @@ -1,39 +1,23 @@ +workspace(name = "cxx.rs") + load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("//tools/bazel:vendor.bzl", "vendor") http_archive( - name = "io_bazel_rules_rust", - sha256 = "5ed804fcd10a506a5b8e9e59bc6b3b7f43bc30c87ce4670e6f78df43604894fd", - strip_prefix = "rules_rust-fdf9655ba95616e0314b4e0ebab40bb0c5fe005c", - # Master branch as of 2020-07-30 - url = "https://github.com/bazelbuild/rules_rust/archive/fdf9655ba95616e0314b4e0ebab40bb0c5fe005c.tar.gz", -) - -http_archive( - name = "bazel_skylib", - sha256 = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44", + name = "rules_rust", + sha256 = "e6d835ee673f388aa5b62dc23d82db8fc76497e93fa47d8a4afe97abaf09b10d", + strip_prefix = "rules_rust-f37b9d6a552e9412285e627f30cb124e709f4f7a", urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz", + # Master branch as of 2021-01-27 + "https://github.com/bazelbuild/rules_rust/archive/f37b9d6a552e9412285e627f30cb124e709f4f7a.tar.gz", ], ) -load("@io_bazel_rules_rust//:workspace.bzl", "bazel_version") - -bazel_version(name = "bazel_version") - -load("@io_bazel_rules_rust//rust:repositories.bzl", "rust_repository_set") - -rust_repository_set( - name = "rust_1_47_linux", - exec_triple = "x86_64-unknown-linux-gnu", - version = "1.47.0", -) +load("@rules_rust//rust:repositories.bzl", "rust_repositories") -rust_repository_set( - name = "rust_1_47_darwin", - exec_triple = "x86_64-apple-darwin", - version = "1.47.0", +rust_repositories( + edition = "2018", + version = "1.50.0", ) vendor( diff --git a/book/.gitignore b/book/.gitignore index 690b5b80..72775071 100644 --- a/book/.gitignore +++ b/book/.gitignore @@ -1,2 +1,3 @@ /build /mdbook +/node_modules diff --git a/book/book.toml b/book/book.toml index d3187e0a..066f3a62 100644 --- a/book/book.toml +++ b/book/book.toml @@ -1,14 +1,22 @@ [book] -title = "CXX" +#title = "Rust ♡ C++" authors = ["David Tolnay"] -description = "Guide for the `cxx` crate, a safe approach to FFI between Rust and C++." +description = "CXX — safe interop between Rust and C++" [rust] edition = "2018" [build] build-dir = "build" +create-missing = false [output.html] +additional-css = ["css/cxx.css"] cname = "cxx.rs" git-repository-url = "https://github.com/dtolnay/cxx" +playground = { copyable = false } +print = { enable = false } + +[output.html.redirect] +"binding/index.html" = "../bindings.html" +"build/index.html" = "../building.html" diff --git a/book/build.js b/book/build.js new file mode 100755 index 00000000..2cda5860 --- /dev/null +++ b/book/build.js @@ -0,0 +1,104 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const cheerio = require('cheerio'); +const hljs = require('./build/highlight.js'); +const Entities = require('html-entities').AllHtmlEntities; +const entities = new Entities(); + +const githublink = `\ +<li class="part-title">\ +<a href="https://github.com/dtolnay/cxx">\ +<i class="fa fa-github"></i>\ +https://github.com/dtolnay/cxx\ +</a>\ +</li>`; + +const opengraph = `\ +<meta property="og:image" content="https://cxx.rs/cxx.png" />\ +<meta property="og:site_name" content="CXX" />\ +<meta property="og:title" content="CXX — safe interop between Rust and C++" />\ +<meta name="twitter:image:src" content="https://cxx.rs/cxx.png" />\ +<meta name="twitter:site" content="@davidtolnay" />\ +<meta name="twitter:card" content="summary" />\ +<meta name="twitter:title" content="CXX — safe interop between Rust and C++" />`; + +const htmljs = `\ +var html = document.querySelector('html'); +html.classList.remove('no-js'); +html.classList.add('js');`; + +const dirs = ['build']; +while (dirs.length) { + const dir = dirs.pop(); + fs.readdirSync(dir).forEach((entry) => { + path = dir + '/' + entry; + const stat = fs.statSync(path); + if (stat.isDirectory()) { + dirs.push(path); + return; + } + + if (!path.endsWith('.html')) { + return; + } + + const index = fs.readFileSync(path, 'utf8'); + const $ = cheerio.load(index, { decodeEntities: false }); + + $('head').append(opengraph); + $('script:nth-of-type(3)').text(htmljs); + $('nav#sidebar ol.chapter').append(githublink); + $('head link[href="tomorrow-night.css"]').attr('disabled', true); + $('head link[href="ayu-highlight.css"]').attr('disabled', true); + $('button#theme-toggle').attr('style', 'display:none'); + $('pre code').each(function () { + const node = $(this); + const langClass = node.attr('class').split(' ', 2)[0]; + if (!langClass.startsWith('language-')) { + return; + } + const lang = langClass.replace('language-', ''); + const lines = node.html().split('\n'); + const boring = lines.map((line) => + line.includes('<span class="boring">') + ); + const ellipsis = lines.map((line) => line.includes('// ...')); + const target = entities.decode(node.text()); + const highlighted = hljs.highlight(lang, target).value; + const result = highlighted + .split('\n') + .map(function (line, i) { + if (boring[i]) { + line = '<span class="boring">' + line; + } else if (ellipsis[i]) { + line = '<span class="ellipsis">' + line; + } + if (i > 0 && (boring[i - 1] || ellipsis[i - 1])) { + line = '</span>' + line; + } + return line; + }) + .join('\n'); + node.text(result); + node.removeClass(langClass); + if (!node.hasClass('focuscomment')) { + node.addClass('hidelines'); + node.addClass('hide-boring'); + } + }); + $('code').each(function () { + $(this).addClass('hljs'); + }); + + const out = $.html(); + fs.writeFileSync(path, out); + }); +} + +fs.copyFileSync('build/highlight.css', 'build/tomorrow-night.css'); +fs.copyFileSync('build/highlight.css', 'build/ayu-highlight.css'); + +var bookjs = fs.readFileSync('build/book.js', 'utf8'); +bookjs = bookjs.replace('set_theme(theme, false);', ''); +fs.writeFileSync('build/book.js', bookjs); diff --git a/book/build.sh b/book/build.sh new file mode 100755 index 00000000..783d304b --- /dev/null +++ b/book/build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +cd "$(dirname "$0")" + +if [ -f ./mdbook ]; then + ./mdbook build +else + mdbook build +fi + +if [ ! -d node_modules ]; then + npm install +fi + +./build.js diff --git a/book/css/cxx.css b/book/css/cxx.css new file mode 100644 index 00000000..647f4f71 --- /dev/null +++ b/book/css/cxx.css @@ -0,0 +1,44 @@ +:root { + --sidebar-width: 310px; +} + +.badges img { + margin: 0 7px 7px 0; +} + +.badges { + margin: 16px 0 120px; +} + +.boring { + opacity: 0.5; +} + +.no-js code:not(.focuscomment) .boring { + display: none; +} + +.js code:not(.hide-boring) .ellipsis { + display: none; +} + +.focuscomment .hljs-comment { + font-weight: bold; + color: black; +} + +.focuscomment .boring { + opacity: 0.5; +} + +nav.sidebar li.part-title i.fa-github { + font-size: 20px; + padding-right: 5px; + padding-top: 12px; + position: relative; + top: 1px; +} + +.sidebar .sidebar-scrollbox { + padding: 10px 0 10px 10px; +} diff --git a/book/diagram/.gitignore b/book/diagram/.gitignore new file mode 100644 index 00000000..81631c69 --- /dev/null +++ b/book/diagram/.gitignore @@ -0,0 +1,2 @@ +/bin +/build diff --git a/book/diagram/Makefile b/book/diagram/Makefile new file mode 100644 index 00000000..161da349 --- /dev/null +++ b/book/diagram/Makefile @@ -0,0 +1,12 @@ +all: overview.svg + +build/bin/svgbob: + cargo install --git https://github.com/ivanceras/svgbob --rev df01674c47350665158ececa476e63f51c58a9c7 --root build + +%.svg: %.ascii build/bin/svgbob + build/bin/svgbob $< > $@ + +clean: + rm -f *.svg + +.PHONY: all clean diff --git a/book/diagram/overview.ascii b/book/diagram/overview.ascii new file mode 100644 index 00000000..dd1c4b93 --- /dev/null +++ b/book/diagram/overview.ascii @@ -0,0 +1,13 @@ + .-----------------------------. + | #[cxx::bridge] mod | + | description of boundary | + '--------------+--------------' + | + | + "Macro expansion" | "Code generation" + +---------------+---------------+ + Safe | | + straightforward v v Straightforward + "Rust APIs".----------------. "Hidden C ABI".---------------. "C++ APIs" +Rust <----------->| "Rust bindings"|<~~~~~~~~~~~~~>| "C++ bindings"|<-----------> "C++" +code '----------------' '---------------' code diff --git a/book/diagram/overview.svg b/book/diagram/overview.svg new file mode 100644 index 00000000..e5154668 --- /dev/null +++ b/book/diagram/overview.svg @@ -0,0 +1,132 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="696" height="224"> + <style>line, path, circle,rect,polygon { + stroke: black; + stroke-width: 2; + stroke-opacity: 1; + fill-opacity: 1; + stroke-linecap: round; + stroke-linejoin: miter; + } + + text { + fill: black; + } + rect.backdrop{ + stroke: none; + fill: white; + } + .broken{ + stroke-dasharray: 8; + } + .filled{ + fill: black; + } + .bg_filled{ + fill: white; + } + .nofill{ + fill: white; + } + + text { + font-family: monospace; + font-size: 14px; + } + + .end_marked_arrow{ + marker-end: url(#arrow); + } + .start_marked_arrow{ + marker-start: url(#arrow); + } + + .end_marked_diamond{ + marker-end: url(#diamond); + } + .start_marked_diamond{ + marker-start: url(#diamond); + } + + .end_marked_circle{ + marker-end: url(#circle); + } + .start_marked_circle{ + marker-start: url(#circle); + } + + .end_marked_open_circle{ + marker-end: url(#open_circle); + } + .start_marked_open_circle{ + marker-start: url(#open_circle); + } + + .end_marked_big_open_circle{ + marker-end: url(#big_open_circle); + } + .start_marked_big_open_circle{ + marker-start: url(#big_open_circle); + } + + + </style> + <defs> + <marker id="arrow" viewBox="-2 -2 8 8" refX="4" refY="2" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <polygon points="0,0 0,4 4,2 0,0"></polygon> + </marker> + <marker id="diamond" viewBox="-2 -2 8 8" refX="4" refY="2" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <polygon points="0,2 2,0 4,2 2,4 0,2"></polygon> + </marker> + <marker id="circle" viewBox="0 0 8 8" refX="4" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <circle cx="4" cy="4" r="2" class="filled"></circle> + </marker> + <marker id="open_circle" viewBox="0 0 8 8" refX="4" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <circle cx="4" cy="4" r="2" class="bg_filled"></circle> + </marker> + <marker id="big_open_circle" viewBox="0 0 8 8" refX="4" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <circle cx="4" cy="4" r="3" class="bg_filled"></circle> + </marker> + </defs> + <rect class="backdrop" x="0" y="0" width="696" height="224"></rect> + <rect x="148" y="168" width="136" height="32" class="solid nofill" rx="4"></rect> + <text x="162" y="188" >Rust bindings</text> + <rect x="412" y="168" width="128" height="32" class="solid nofill" rx="4"></rect> + <text x="426" y="188" >C++ bindings</text> + <text x="266" y="28" >#[cxx::bridge]</text> + <text x="394" y="28" >mod</text> + <text x="250" y="44" >description</text> + <text x="346" y="44" >of</text> + <text x="370" y="44" >boundary</text> + <line x1="220" y1="120" x2="220" y2="160" class="solid end_marked_arrow"></line> + <line x1="476" y1="120" x2="476" y2="160" class="solid end_marked_arrow"></line> + <text x="74" y="140" >Safe</text> + <text x="34" y="156" >straightforward</text> + <text x="530" y="156" >Straightforward</text> + <polygon points="48,180 40,184 48,188" class="filled"></polygon> + <line x1="48" y1="184" x2="144" y2="184" class="solid end_marked_arrow"></line> + <polygon points="296,180 288,184 296,188" class="filled"></polygon> + <line x1="296" y1="184" x2="408" y2="184" class="broken end_marked_arrow"></line> + <polygon points="552,180 544,184 552,188" class="filled"></polygon> + <line x1="552" y1="184" x2="648" y2="184" class="solid end_marked_arrow"></line> + <text x="2" y="188" >Rust</text> + <text x="2" y="204" >code</text> + <text x="658" y="204" >code</text> + <text x="202" y="108" >Macro expansion</text> + <text x="370" y="108" >Code generation</text> + <text x="58" y="172" >Rust APIs</text> + <text x="298" y="172" >Hidden C ABI</text> + <text x="562" y="172" >C++ APIs</text> + <text x="658" y="188" >C++</text> + <g> + <path d="M 228,8 A 8,8 0,0,0 220,16" class="nofill"></path> + <line x1="228" y1="8" x2="468" y2="8" class="solid"></line> + <path d="M 468,8 A 8,8 0,0,1 476,16" class="nofill"></path> + <line x1="220" y1="16" x2="220" y2="48" class="solid"></line> + <line x1="476" y1="16" x2="476" y2="48" class="solid"></line> + <path d="M 220,48 A 8,8 0,0,0 228,56" class="nofill"></path> + <line x1="228" y1="56" x2="468" y2="56" class="solid"></line> + <line x1="348" y1="56" x2="348" y2="120" class="solid"></line> + <path d="M 476,48 A 8,8 0,0,1 468,56" class="nofill"></path> + <line x1="220" y1="120" x2="476" y2="120" class="solid"></line> + </g> +</svg> diff --git a/book/package-lock.json b/book/package-lock.json new file mode 100644 index 00000000..dec26ad1 --- /dev/null +++ b/book/package-lock.json @@ -0,0 +1,207 @@ +{ + "name": "cxx-book-build", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "html-entities": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", + "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==" + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + }, + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + } + } +} diff --git a/book/package.json b/book/package.json new file mode 100644 index 00000000..092cea21 --- /dev/null +++ b/book/package.json @@ -0,0 +1,12 @@ +{ + "name": "cxx-book-build", + "version": "0.0.0", + "main": "build.js", + "dependencies": { + "cheerio": "^0.22.0", + "html-entities": "^1.3.1" + }, + "prettier": { + "singleQuote": true + } +} diff --git a/book/src/404.md b/book/src/404.md new file mode 100644 index 00000000..c5b71f28 --- /dev/null +++ b/book/src/404.md @@ -0,0 +1,5 @@ +### Whoops, this page doesn’t exist :-( + +<br> + +<img src="https://www.rust-lang.org/static/images/ferris-error.png" alt="ferris" width="325"> diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index f3fa924f..a8f89bfc 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -1,3 +1,37 @@ # Summary -[Rust ❤️ C++](about.md) +- [Rust ❤️ C++](index.md) + +- [Core concepts](concepts.md) + +- [Tutorial](tutorial.md) + +- [Other Rust–C++ interop tools](context.md) + +- [Multi-language build system options](building.md) + - [Cargo](build/cargo.md) + - [Bazel](build/bazel.md) + - [CMake](build/cmake.md) + - [More...](build/other.md) + +- [Reference: the bridge module](reference.md) + - [extern "Rust"](extern-rust.md) + - [extern "C++"](extern-c++.md) + - [Shared types](shared.md) + - [Attributes](attributes.md) + - [Async functions](async.md) + - [Error handling](binding/result.md) + +- [Reference: built-in bindings](bindings.md) + - [String — rust::String](binding/string.md) + - [&str — rust::Str](binding/str.md) + - [&[T], &mut [T] — rust::Slice\<T\>](binding/slice.md) + - [CxxString — std::string](binding/cxxstring.md) + - [Box\<T\> — rust::Box\<T\>](binding/box.md) + - [UniquePtr\<T\> — std::unique\_ptr\<T\>](binding/uniqueptr.md) + - [SharedPtr\<T\> — std::shared\_ptr\<T\>](binding/sharedptr.md) + - [Vec\<T\> — rust::Vec\<T\>](binding/vec.md) + - [CxxVector\<T\> — std::vector\<T\>](binding/cxxvector.md) + - [*mut T, *const T raw pointers](binding/rawptr.md) + - [Function pointers](binding/fn.md) + - [Result\<T\>](binding/result.md) diff --git a/book/src/about.md b/book/src/about.md deleted file mode 100644 index 6e63bf5e..00000000 --- a/book/src/about.md +++ /dev/null @@ -1 +0,0 @@ -### Coming soon diff --git a/book/src/async.md b/book/src/async.md new file mode 100644 index 00000000..b4c696a3 --- /dev/null +++ b/book/src/async.md @@ -0,0 +1,86 @@ +{{#title Async functions — Rust ♡ C++}} +# Async functions + +Direct FFI of async functions is absolutely in scope for CXX (on C++20 and up) +but is not implemented yet in the current release. We are aiming for an +implementation that is as easy as: + +```rust,noplayground +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + async fn doThing(arg: Arg) -> Ret; + } +} +``` + +```cpp,hidelines +rust::Future<Ret> doThing(Arg arg) { + auto v1 = co_await f(); + auto v2 = co_await g(arg); + co_return v1 + v2; +} +``` + +## Workaround + +For now the recommended approach is to handle the return codepath over a oneshot +channel (such as [`futures::channel::oneshot`]) represented in an opaque Rust +type on the FFI. + +[`futures::channel::oneshot`]: https://docs.rs/futures/0.3.8/futures/channel/oneshot/index.html + +```rust,noplayground +// bridge.rs + +use futures::channel::oneshot; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + type DoThingContext; + } + + unsafe extern "C++" { + include!("path/to/bridge_shim.h"); + + fn shim_doThing( + arg: Arg, + done: fn(Box<DoThingContext>, ret: Ret), + ctx: Box<DoThingContext>, + ); + } +} + +struct DoThingContext(oneshot::Sender<Ret>); + +pub async fn do_thing(arg: Arg) -> Ret { + let (tx, rx) = oneshot::channel(); + let context = Box::new(DoThingContext(tx)); + + ffi::shim_doThing( + arg, + |context, ret| { let _ = context.0.send(ret); }, + context, + ); + + rx.await.unwrap() +} +``` + +```cpp +// bridge_shim.cc + +#include "path/to/bridge.rs.h" +#include "rust/cxx.h" + +void shim_doThing( + Arg arg, + rust::Fn<void(rust::Box<DoThingContext> ctx, Ret ret)> done, + rust::Box<DoThingContext> ctx) noexcept { + doThing(arg) + .then([done, ctx(std::move(ctx))](auto &&res) mutable { + (*done)(std::move(ctx), std::move(res)); + }); +} +``` diff --git a/book/src/attributes.md b/book/src/attributes.md new file mode 100644 index 00000000..9c33b777 --- /dev/null +++ b/book/src/attributes.md @@ -0,0 +1,75 @@ +{{#title Attributes — Rust ♡ C++}} +# Attributes + +## namespace + +The top-level cxx::bridge attribute macro takes an optional `namespace` argument +to control the C++ namespace into which to emit extern Rust items and the +namespace in which to expect to find the extern C++ items. + +```rust,noplayground +#[cxx::bridge(namespace = "path::of::my::company")] +mod ffi { + extern "Rust" { + type MyType; // emitted to path::of::my::company::MyType + } + + extern "C++" { + type TheirType; // refers to path::of::my::company::TheirType + } +} +``` + +Additionally, a `#[namespace = "..."]` attribute may be used inside the bridge +module on any extern block or individual item. An item will inherit the +namespace specified on its surrounding extern block if any, otherwise the +namespace specified with the top level cxx::bridge attribute if any, otherwise +the global namespace. + +```rust,noplayground +#[cxx::bridge(namespace = "third_priority")] +mod ffi { + #[namespace = "second_priority"] + extern "Rust" { + fn f(); + + #[namespace = "first_priority"] + fn g(); + } + + extern "Rust" { + fn h(); + } +} +``` + +The above would result in functions `::second_priority::f`, +`::first_priority::g`, `::third_priority::h`. + +## rust\_name, cxx\_name + +Sometimes you want the Rust name of a function or type to differ from its C++ +name. Importantly, this enables binding multiple overloads of the same C++ +function name using distinct Rust names. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + #[rust_name = "i32_overloaded_function"] + fn cOverloadedFunction(x: i32) -> String; + #[rust_name = "str_overloaded_function"] + fn cOverloadedFunction(x: &str) -> String; + } +} +``` + +The `#[rust_name = "..."]` attribute replaces the name that Rust should use for +this function, and an analogous `#[cxx_name = "..."]` attribute replaces the +name that C++ should use. + +Either of the two attributes may be used on extern "Rust" as well as extern +"C++" functions, according to which one you find clearer in context. + +The same attribute works for renaming functions, opaque types, shared +structs and enums, and enum variants. diff --git a/book/src/binding/box.md b/book/src/binding/box.md new file mode 100644 index 00000000..7df19597 --- /dev/null +++ b/book/src/binding/box.md @@ -0,0 +1,120 @@ +{{#title rust::Box<T> — Rust ♡ C++}} +# rust::Box\<T\> + +### Public API: + +```cpp,hidelines +// rust/cxx.h +# +# #include <type_traits> +# +# namespace rust { + +template <typename T> +class Box final { +public: + using element_type = T; + using const_pointer = + typename std::add_pointer<typename std::add_const<T>::type>::type; + using pointer = typename std::add_pointer<T>::type; + + Box(Box &&) noexcept; + ~Box() noexcept; + + explicit Box(const T &); + explicit Box(T &&); + + Box &operator=(Box &&) noexcept; + + const T *operator->() const noexcept; + const T &operator*() const noexcept; + T *operator->() noexcept; + T &operator*() noexcept; + + template <typename... Fields> + static Box in_place(Fields &&...); + + void swap(Box &) noexcept; + + // Important: requires that `raw` came from an into_raw call. Do not + // pass a pointer from `new` or any other source. + static Box from_raw(T *) noexcept; + + T *into_raw() noexcept; +}; +# +# } // namespace rust +``` + +### Restrictions: + +Box\<T\> does not support T being an opaque C++ type. You should use +[UniquePtr\<T\>](uniqueptr.md) or [SharedPtr\<T\>](sharedptr.md) instead for +transferring ownership of opaque C++ types on the language boundary. + +If T is an opaque Rust type, the Rust type is required to be [Sized] i.e. size +known at compile time. In the future we may introduce support for dynamically +sized opaque Rust types. + +[Sized]: https://doc.rust-lang.org/std/marker/trait.Sized.html + +## Example + +This program uses a Box to pass ownership of some opaque piece of Rust state +over to C++ and then back to a Rust callback, which is a useful pattern for +implementing [async functions over FFI](../async.md). + +```rust,noplayground +// src/main.rs + +use std::io::Write; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + type File; + } + + unsafe extern "C++" { + include!("example/include/example.h"); + + fn f( + callback: fn(Box<File>, fst: &str, snd: &str), + out: Box<File>, + ); + } +} + +pub struct File(std::fs::File); + +fn main() { + let out = std::fs::File::create("example.log").unwrap(); + + ffi::f( + |mut out, fst, snd| { let _ = write!(out.0, "{}{}\n", fst, snd); }, + Box::new(File(out)), + ); +} +``` + +```cpp +// include/example.h + +#pragma once +#include "example/src/main.rs.h" +#include "rust/cxx.h" + +void f(rust::Fn<void(rust::Box<File>, rust::Str, rust::Str)> callback, + rust::Box<File> out); +``` + +```cpp +// include/example.cc + +#include "example/include/example.h" + +void f(rust::Fn<void(rust::Box<File>, rust::Str, rust::Str)> callback, + rust::Box<File> out) { + callback(std::move(out), "fearless", "concurrency"); +} +``` diff --git a/book/src/binding/cxxstring.md b/book/src/binding/cxxstring.md new file mode 100644 index 00000000..0b1d7bbd --- /dev/null +++ b/book/src/binding/cxxstring.md @@ -0,0 +1,139 @@ +{{#title std::string — Rust ♡ C++}} +# std::string + +The Rust binding of std::string is called **[`CxxString`]**. See the link for +documentation of the Rust API. + +[`CxxString`]: https://docs.rs/cxx/*/cxx/struct.CxxString.html + +### Restrictions: + +Rust code can never obtain a CxxString by value. C++'s string requires a move +constructor and may hold internal pointers, which is not compatible with Rust's +move behavior. Instead in Rust code we will only ever look at a CxxString +through a reference or smart pointer, as in &CxxString or Pin\<&mut CxxString\> +or UniquePtr\<CxxString\>. + +In order to construct a CxxString on the stack from Rust, you must use the +[`let_cxx_string!`] macro which will pin the string properly. The code below +uses this in one place, and the link covers the syntax. + +[`let_cxx_string!`]: https://docs.rs/cxx/*/cxx/macro.let_cxx_string.html + +## Example + +This example uses C++17's std::variant to build a toy JSON type. JSON can hold +various types including strings, and JSON's object type is a map with string +keys. The example demonstrates Rust indexing into one of those maps. + +```rust,noplayground +// src/main.rs + +use cxx::let_cxx_string; + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("example/include/json.h"); + + #[cxx_name = "json"] + type Json; + #[cxx_name = "object"] + type Object; + + fn isNull(self: &Json) -> bool; + fn isNumber(self: &Json) -> bool; + fn isString(self: &Json) -> bool; + fn isArray(self: &Json) -> bool; + fn isObject(self: &Json) -> bool; + + fn getNumber(self: &Json) -> f64; + fn getString(self: &Json) -> &CxxString; + fn getArray(self: &Json) -> &CxxVector<Json>; + fn getObject(self: &Json) -> &Object; + + #[cxx_name = "at"] + fn get<'a>(self: &'a Object, key: &CxxString) -> &'a Json; + + fn load_config() -> UniquePtr<Json>; + } +} + +fn main() { + let config = ffi::load_config(); + + let_cxx_string!(key = "name"); + println!("{}", config.getObject().get(&key).getString()); +} +``` + +```cpp +// include/json.h + +#pragma once +#include <map> +#include <memory> +#include <variant> +#include <vector> + +class json final { +public: + static const json null; + using number = double; + using string = std::string; + using array = std::vector<json>; + using object = std::map<string, json>; + + json() noexcept = default; + json(const json &) = default; + json(json &&) = default; + template <typename... T> + json(T &&...value) : value(std::forward<T>(value)...) {} + + bool isNull() const; + bool isNumber() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + number getNumber() const; + const string &getString() const; + const array &getArray() const; + const object &getObject() const; + +private: + std::variant<std::monostate, number, string, array, object> value; +}; + +using object = json::object; + +std::unique_ptr<json> load_config(); +``` + +```cpp +// include/json.cc + +#include "example/include/json.h" +#include <initializer_list> +#include <utility> + +const json json::null{}; +bool json::isNull() const { return std::holds_alternative<std::monostate>(value); } +bool json::isNumber() const { return std::holds_alternative<number>(value); } +bool json::isString() const { return std::holds_alternative<string>(value); } +bool json::isArray() const { return std::holds_alternative<array>(value); } +bool json::isObject() const { return std::holds_alternative<object>(value); } +json::number json::getNumber() const { return std::get<number>(value); } +const json::string &json::getString() const { return std::get<string>(value); } +const json::array &json::getArray() const { return std::get<array>(value); } +const json::object &json::getObject() const { return std::get<object>(value); } + +std::unique_ptr<json> load_config() { + return std::make_unique<json>( + std::in_place_type<json::object>, + std::initializer_list<std::pair<const std::string, json>>{ + {"name", "cxx-example"}, + {"edition", 2018.}, + {"repository", json::null}}); +} +``` diff --git a/book/src/binding/cxxvector.md b/book/src/binding/cxxvector.md new file mode 100644 index 00000000..fd95a2db --- /dev/null +++ b/book/src/binding/cxxvector.md @@ -0,0 +1,62 @@ +{{#title std::vector<T> — Rust ♡ C++}} +# std::vector\<T\> + +The Rust binding of std::vector\<T\> is called **[`CxxVector<T>`]**. See the +link for documentation of the Rust API. + +[`CxxVector<T>`]: https://docs.rs/cxx/*/cxx/struct.CxxVector.html + +### Restrictions: + +Rust code can never obtain a CxxVector by value. Instead in Rust code we will +only ever look at a vector behind a reference or smart pointer, as in +&CxxVector\<T\> or UniquePtr\<CxxVector\<T\>\>. + +CxxVector\<T\> does not support T being an opaque Rust type. You should use a +Vec\<T\> (C++ rust::Vec\<T\>) instead for collections of opaque Rust types on +the language boundary. + +## Example + +This program involves Rust code converting a `CxxVector<CxxString>` (i.e. +`std::vector<std::string>`) into a Rust `Vec<String>`. + +```rust,noplayground +// src/main.rs + +#![no_main] // main defined in C++ by main.cc + +use cxx::{CxxString, CxxVector}; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + fn f(vec: &CxxVector<CxxString>); + } +} + +fn f(vec: &CxxVector<CxxString>) { + let vec: Vec<String> = vec + .iter() + .map(|s| s.to_string_lossy().into_owned()) + .collect(); + g(&vec); +} + +fn g(vec: &[String]) { + println!("{:?}", vec); +} +``` + +```cpp +// src/main.cc + +#include "example/src/main.rs.h" +#include <string> +#include <vector> + +int main() { + std::vector<std::string> vec{"fearless", "concurrency"}; + f(vec); +} +``` diff --git a/book/src/binding/fn.md b/book/src/binding/fn.md new file mode 100644 index 00000000..2934b069 --- /dev/null +++ b/book/src/binding/fn.md @@ -0,0 +1,34 @@ +{{#title Function pointers — Rust ♡ C++}} +# Function pointers + +### Public API: + +```cpp,hidelines +// rust/cxx.h +# +# namespace rust { + +template <typename Signature> +class Fn; + +template <typename Ret, typename... Args> +class Fn<Ret(Args...)> final { +public: + Ret operator()(Args... args) const noexcept; + Fn operator*() const noexcept; +}; +# +# } // namespace rust +``` + +### Restrictions: + +Function pointers with a Result return type are not implemented yet. + +Passing a function pointer from C++ to Rust is not implemented yet, only from +Rust to an `extern "C++"` function is implemented. + +## Example + +Function pointers are commonly useful for implementing [async functions over +FFI](../async.md). See the example code on that page. diff --git a/book/src/binding/rawptr.md b/book/src/binding/rawptr.md new file mode 100644 index 00000000..17942112 --- /dev/null +++ b/book/src/binding/rawptr.md @@ -0,0 +1,100 @@ +{{#title *mut T, *const T — Rust ♡ C++}} +# *mut T, *const T + +Generally you should use references (`&mut T`, `&T`) or [std::unique_ptr\<T\>] +where possible over raw pointers, but raw pointers are available too as an +unsafe fallback option. + +[std::unique_ptr\<T\>]: uniqueptr.md + +### Restrictions: + +Extern functions and function pointers taking a raw pointer as an argument must +be declared `unsafe fn` i.e. unsafe to call. The same does not apply to +functions which only *return* a raw pointer, though presumably doing anything +useful with the returned pointer is going to involve unsafe code elsewhere +anyway. + +## Example + +This example illustrates making a Rust call to a canonical C-style `main` +signature involving `char *argv[]`. + +```cpp +// include/args.h + +#pragma once + +void parseArgs(int argc, char *argv[]); +``` + +```cpp +// src/args.cc + +#include "example/include/args.h" +#include <iostream> + +void parseArgs(int argc, char *argv[]) { + std::cout << argc << std::endl; + for (int i = 0; i < argc; i++) { + std::cout << '"' << argv[i] << '"' << std::endl; + } +} +``` + +```rust,noplayground +// src/main.rs + +use std::env; +use std::ffi::CString; +use std::os::raw::c_char; +use std::os::unix::ffi::OsStrExt; +use std::ptr; + +#[cxx::bridge] +mod ffi { + extern "C++" { + include!("example/include/args.h"); + + unsafe fn parseArgs(argc: i32, argv: *mut *mut c_char); + } +} + +fn main() { + // Convert from OsString to nul-terminated CString, truncating each argument + // at the first inner nul byte if present. + let args: Vec<CString> = env::args_os() + .map(|os_str| { + let bytes = os_str.as_bytes(); + CString::new(bytes).unwrap_or_else(|nul_error| { + let nul_position = nul_error.nul_position(); + let mut bytes = nul_error.into_vec(); + bytes.truncate(nul_position); + CString::new(bytes).unwrap() + }) + }) + .collect(); + + // Convert from Vec<CString> of owned strings to Vec<*mut c_char> of + // borrowed string pointers. + // + // Once extern type stabilizes (https://github.com/rust-lang/rust/issues/43467) + // and https://internals.rust-lang.org/t/pre-rfc-make-cstr-a-thin-pointer/6258 + // is implemented, and CStr pointers become thin, we can sidestep this step + // by accumulating the args as Vec<Box<CStr>> up front, then simply casting + // from *mut [Box<CStr>] to *mut [*mut CStr] to *mut *mut c_char. + let argc = args.len(); + let mut argv: Vec<*mut c_char> = Vec::with_capacity(argc + 1); + for arg in &args { + argv.push(arg.as_ptr() as *mut c_char); + } + argv.push(ptr::null_mut()); // Nul terminator. + + unsafe { + ffi::parseArgs(argc as i32, argv.as_mut_ptr()); + } + + // The CStrings go out of scope here. C function must not have held on to + // the pointers beyond this point. +} +``` diff --git a/book/src/binding/result.md b/book/src/binding/result.md new file mode 100644 index 00000000..e49dcf4d --- /dev/null +++ b/book/src/binding/result.md @@ -0,0 +1,148 @@ +{{#title Result<T> — Rust ♡ C++}} +# Result\<T\> + +Result\<T\> is allowed as the return type of an extern function in either +direction. Its behavior is to translate to/from C++ exceptions. If your codebase +does not use C++ exceptions, or prefers to represent fallibility using something +like outcome\<T\>, leaf::result\<T\>, StatusOr\<T\>, etc then you'll need to +handle the translation of those to Rust Result\<T\> using your own shims for +now. Better support for this is planned. + +If an exception is thrown from an `extern "C++"` function that is *not* declared +by the CXX bridge to return Result, the program calls C++'s `std::terminate`. +The behavior is equivalent to the same exception being thrown through a +`noexcept` C++ function. + +If a panic occurs in *any* `extern "Rust"` function, regardless of whether it is +declared by the CXX bridge to return Result, a message is logged and the program +calls Rust's `std::process::abort`. + +## Returning Result from Rust to C++ + +An `extern "Rust"` function returning a Result turns into a `throw` in C++ if +the Rust side produces an error. + +Note that the return type written inside of cxx::bridge must be written without +a second type parameter. Only the Ok type is specified for the purpose of the +FFI. The Rust *implementation* (outside of the bridge module) may pick any error +type as long as it has a std::fmt::Display impl. + +```rust,noplayground +# use std::io; +# +#[cxx::bridge] +mod ffi { + extern "Rust" { + fn fallible1(depth: usize) -> Result<String>; + fn fallible2() -> Result<()>; + } +} + +fn fallible1(depth: usize) -> anyhow::Result<String> { + if depth == 0 { + return Err(anyhow::Error::msg("fallible1 requires depth > 0")); + } + ... +} + +fn fallible2() -> Result<(), io::Error> { + ... + Ok(()) +} +``` + +The exception that gets thrown by CXX on the C++ side is always of type +`rust::Error` and has the following C++ public API. The `what()` member function +gives the error message according to the Rust error's std::fmt::Display impl. + +```cpp,hidelines +// rust/cxx.h +# +# namespace rust { + +class Error final : public std::exception { +public: + Error(const Error &); + Error(Error &&) noexcept; + ~Error() noexcept; + + Error &operator=(const Error &); + Error &operator=(Error &&) noexcept; + + const char *what() const noexcept override; +}; +# +# } // namespace rust +``` + +## Returning Result from C++ to Rust + +An `extern "C++"` function returning a Result turns into a `catch` in C++ that +converts the exception into an Err for Rust. + +Note that the return type written inside of cxx::bridge must be written without +a second type parameter. Only the Ok type is specified for the purpose of the +FFI. The resulting error type created by CXX when an `extern "C++"` function +throws will always be of type **[`cxx::Exception`]**. + +[`cxx::Exception`]: https://docs.rs/cxx/*/cxx/struct.Exception.html + +```rust,noplayground +# use std::process; +# +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("example/include/example.h"); + fn fallible1(depth: usize) -> Result<String>; + fn fallible2() -> Result<()>; + } +} + +fn main() { + if let Err(err) = ffi::fallible1(99) { + eprintln!("Error: {}", err); + process::exit(1); + } +} +``` + +The specific set of caught exceptions and the conversion to error message are +both customizable. The way you do this is by defining a template function +`rust::behavior::trycatch` with a suitable signature inside any one of the +headers `include!`'d by your cxx::bridge. + +The template signature is required to be: + +```cpp,hidelines +namespace rust { +namespace behavior { + +template <typename Try, typename Fail> +static void trycatch(Try &&func, Fail &&fail) noexcept; + +} // namespace behavior +} // namespace rust +``` + +The default `trycatch` used by CXX if you have not provided your own is the +following. You must follow the same pattern: invoke `func` with no arguments, +catch whatever exception(s) you want, and invoke `fail` with the error message +you'd like for the Rust error to have. + +```cpp,hidelines +# #include <exception> +# +# namespace rust { +# namespace behavior { +# +template <typename Try, typename Fail> +static void trycatch(Try &&func, Fail &&fail) noexcept try { + func(); +} catch (const std::exception &e) { + fail(e.what()); +} +# +# } // namespace behavior +# } // namespace rust +``` diff --git a/book/src/binding/sharedptr.md b/book/src/binding/sharedptr.md new file mode 100644 index 00000000..a3b70700 --- /dev/null +++ b/book/src/binding/sharedptr.md @@ -0,0 +1,80 @@ +{{#title std::shared_ptr<T> — Rust ♡ C++}} +# std::shared\_ptr\<T\> + +The Rust binding of std::shared\_ptr\<T\> is called **[`SharedPtr<T>`]**. See +the link for documentation of the Rust API. + +[`SharedPtr<T>`]: https://docs.rs/cxx/*/cxx/struct.SharedPtr.html + +### Restrictions: + +SharedPtr\<T\> does not support T being an opaque Rust type. You should use a +Box\<T\> (C++ [rust::Box\<T\>](box.md)) instead for transferring ownership of +opaque Rust types on the language boundary. + +## Example + +```rust,noplayground +// src/main.rs + +use std::ops::Deref; +use std::ptr; + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("example/include/example.h"); + + type Object; + + fn create_shared_ptr() -> SharedPtr<Object>; + } +} + +fn main() { + let ptr1 = ffi::create_shared_ptr(); + + { + // Create a second shared_ptr holding shared ownership of the same + // object. There is still only one Object but two SharedPtr<Object>. + // Both pointers point to the same object on the heap. + let ptr2 = ptr1.clone(); + assert!(ptr::eq(ptr1.deref(), ptr2.deref())); + + // ptr2 goes out of scope, but Object is not destroyed yet. + } + + println!("say goodbye to Object"); + + // ptr1 goes out of scope and Object is destroyed. +} +``` + +```cpp +// include/example.h + +#pragma once +#include <memory> + +class Object { +public: + Object(); + ~Object(); +}; + +std::shared_ptr<Object> create_shared_ptr(); +``` + +```cpp +// src/example.cc + +#include "example/include/example.h" +#include <iostream> + +Object::Object() { std::cout << "construct Object" << std::endl; } +Object::~Object() { std::cout << "~Object" << std::endl; } + +std::shared_ptr<Object> create_shared_ptr() { + return std::make_shared<Object>(); +} +``` diff --git a/book/src/binding/slice.md b/book/src/binding/slice.md new file mode 100644 index 00000000..803277ba --- /dev/null +++ b/book/src/binding/slice.md @@ -0,0 +1,171 @@ +{{#title rust::Slice<T> — Rust ♡ C++}} +# rust::Slice\<const T\>, rust::Slice\<T\> + +- Rust `&[T]` is written `rust::Slice<const T>` in C++ +- Rust `&mut [T]` is written `rust::Slice<T>` in C++ + +### Public API: + +```cpp,hidelines +// rust/cxx.h +# +# #include <iterator> +# #include <type_traits> +# +# namespace rust { + +template <typename T> +class Slice final { +public: + using value_type = T; + + Slice() noexcept; + Slice(const Slice<T> &) noexcept; + Slice(T *, size_t count) noexcept; + + Slice &operator=(Slice<T> &&) noexcept; + Slice &operator=(const Slice<T> &) noexcept + requires std::is_const_v<T>; + + T *data() const noexcept; + size_t size() const noexcept; + size_t length() const noexcept; + bool empty() const noexcept; + + T &operator[](size_t n) const noexcept; + T &at(size_t n) const; + T &front() const noexcept; + T &back() const noexcept; + + class iterator; + iterator begin() const noexcept; + iterator end() const noexcept; + + void swap(Slice &) noexcept; +}; +# +# template <typename T> +# class Slice<T>::iterator final { +# public: +# using iterator_category = std::random_access_iterator_tag; +# using value_type = T; +# using pointer = T *; +# using reference = T &; +# +# T &operator*() const noexcept; +# T *operator->() const noexcept; +# T &operator[](ptrdiff_t) const noexcept; +# +# iterator &operator++() noexcept; +# iterator operator++(int) noexcept; +# iterator &operator--() noexcept; +# iterator operator--(int) noexcept; +# +# iterator &operator+=(ptrdiff_t) noexcept; +# iterator &operator-=(ptrdiff_t) noexcept; +# iterator operator+(ptrdiff_t) const noexcept; +# iterator operator-(ptrdiff_t) const noexcept; +# ptrdiff_t operator-(const iterator &) const noexcept; +# +# bool operator==(const iterator &) const noexcept; +# bool operator!=(const iterator &) const noexcept; +# bool operator<(const iterator &) const noexcept; +# bool operator>(const iterator &) const noexcept; +# bool operator<=(const iterator &) const noexcept; +# bool operator>=(const iterator &) const noexcept; +# }; +# +# } // namespace rust +``` + +### Restrictions: + +T must not be an opaque Rust type or opaque C++ type. Support for opaque Rust +types in slices is coming. + +Allowed as function argument or return value. Not supported in shared structs. + +Only rust::Slice\<const T\> is copy-assignable, not rust::Slice\<T\>. (Both are +move-assignable.) You'll need to write std::move occasionally as a reminder that +accidentally exposing overlapping &mut \[T\] to Rust is UB. + +## Example + +This example is a C++ program that constructs a slice containing JSON data (by +reading from stdin, but it could be from anywhere), then calls into Rust to +pretty-print that JSON data into a std::string via the [serde_json] and +[serde_transcode] crates. + +[serde_json]: https://github.com/serde-rs/json +[serde_transcode]: https://github.com/sfackler/serde-transcode + +```rust,noplayground +// src/main.rs + +#![no_main] // main defined in C++ by main.cc + +use cxx::CxxString; +use std::io::{self, Write}; +use std::pin::Pin; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + fn prettify_json(input: &[u8], output: Pin<&mut CxxString>) -> Result<()>; + } +} + +struct WriteToCxxString<'a>(Pin<&'a mut CxxString>); + +impl<'a> Write for WriteToCxxString<'a> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + self.0.as_mut().push_bytes(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +fn prettify_json(input: &[u8], output: Pin<&mut CxxString>) -> serde_json::Result<()> { + let writer = WriteToCxxString(output); + let mut deserializer = serde_json::Deserializer::from_slice(input); + let mut serializer = serde_json::Serializer::pretty(writer); + serde_transcode::transcode(&mut deserializer, &mut serializer) +} +``` + +```cpp +// src/main.cc + +#include "example/src/main.rs.h" +#include <iostream> +#include <iterator> +#include <string> +#include <vector> + +int main() { + // Read json from stdin. + std::istreambuf_iterator<char> begin{std::cin}, end; + std::vector<unsigned char> input{begin, end}; + rust::Slice<const uint8_t> slice{input.data(), input.size()}; + + // Prettify using serde_json and serde_transcode. + std::string output; + prettify_json(slice, output); + + // Write to stdout. + std::cout << output << std::endl; +} +``` + +Testing the example: + +```console +$ echo '{"fearless":"concurrency"}' | cargo run + Finished dev [unoptimized + debuginfo] target(s) in 0.02s + Running `target/debug/example` +{ + "fearless": "concurrency" +} +``` diff --git a/book/src/binding/str.md b/book/src/binding/str.md new file mode 100644 index 00000000..a4820aaf --- /dev/null +++ b/book/src/binding/str.md @@ -0,0 +1,117 @@ +{{#title rust::Str — Rust ♡ C++}} +# rust::Str + +### Public API: + +```cpp,hidelines +// rust/cxx.h +# +# #include <iosfwd> +# #include <string> +# +# namespace rust { + +class Str final { +public: + Str() noexcept; + Str(const Str &) noexcept; + Str(const String &) noexcept; + + // Throws std::invalid_argument if not utf-8. + Str(const std::string &); + Str(const char *); + Str(const char *, size_t); + + Str &operator=(const Str &) noexcept; + + explicit operator std::string() const; + + // Note: no null terminator. + const char *data() const noexcept; + size_t size() const noexcept; + size_t length() const noexcept; + + using iterator = const char *; + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const Str &) const noexcept; + bool operator!=(const Str &) const noexcept; + bool operator<(const Str &) const noexcept; + bool operator<=(const Str &) const noexcept; + bool operator>(const Str &) const noexcept; + bool operator>=(const Str &) const noexcept; + + void swap(Str &) noexcept; +}; + +std::ostream &operator<<(std::ostream &, const Str &); +# +# } // namespace rust +``` + +### Notes: + +**Be aware that rust::Str behaves like &str i.e. it is a borrow!** C++ +needs to be mindful of the lifetimes at play. + +Just to reiterate: &str is rust::Str. Do not try to write &str as `const +rust::Str &`. A language-level C++ reference is not able to capture the fat +pointer nature of &str. + +### Restrictions: + +Allowed as function argument or return value. Not supported in shared structs +yet. `&mut str` is not supported yet, but is also extremely obscure so this is +fine. + +## Example + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + extern "Rust" { + fn r(greeting: &str); + } + + unsafe extern "C++" { + include!("example/include/greeting.h"); + fn c(greeting: &str); + } +} + +fn r(greeting: &str) { + println!("{}", greeting); +} + +fn main() { + ffi::c("hello from Rust"); +} +``` + +```cpp +// include/greeting.h + +#pragma once +#include "example/src/main.rs.h" +#include "rust/cxx.h" + +void c(rust::Str greeting); +``` + +```cpp +// src/greeting.cc + +#include "example/include/greeting.h" +#include <iostream> + +void c(rust::Str greeting) { + std::cout << greeting << std::endl; + r("hello from C++"); +} +``` diff --git a/book/src/binding/string.md b/book/src/binding/string.md new file mode 100644 index 00000000..a7d0790b --- /dev/null +++ b/book/src/binding/string.md @@ -0,0 +1,115 @@ +{{#title rust::String — Rust ♡ C++}} +# rust::String + +### Public API: + +```cpp,hidelines +// rust/cxx.h +# +# #include <iosfwd> +# #include <string> +# +# namespace rust { + +class String final { +public: + String() noexcept; + String(const String &) noexcept; + String(String &&) noexcept; + ~String() noexcept; + + // Throws std::invalid_argument if not utf-8. + String(const std::string &); + String(const char *); + String(const char *, size_t); + + String &operator=(const String &) noexcept; + String &operator=(String &&) noexcept; + + explicit operator std::string() const; + + // Note: no null terminator. + const char *data() const noexcept; + size_t size() const noexcept; + size_t length() const noexcept; + + const char *c_str() noexcept; + + using iterator = char *; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const String &) const noexcept; + bool operator!=(const String &) const noexcept; + bool operator<(const String &) const noexcept; + bool operator<=(const String &) const noexcept; + bool operator>(const String &) const noexcept; + bool operator>=(const String &) const noexcept; + + void swap(String &) noexcept; +}; + +std::ostream &operator<<(std::ostream &, const String &); +# +# } // namespace rust +``` + +### Restrictions: + +None. Strings may be used as function arguments and function return values, by +value or by reference, as well as fields of shared structs. + +## Example + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + struct ConcatRequest { + fst: String, + snd: String, + } + + unsafe extern "C++" { + include!("example/include/concat.h"); + fn concat(r: ConcatRequest) -> String; + } +} + +fn main() { + let concatenated = ffi::concat(ffi::ConcatRequest { + fst: "fearless".to_owned(), + snd: "concurrency".to_owned(), + }); + println!("concatenated: {:?}", concatenated); +} +``` + +```cpp +// include/concat.h + +#pragma once +#include "example/src/main.rs.h" +#include "rust/cxx.h" + +rust::String concat(ConcatRequest r); +``` + +```cpp +// src/concat.cc + +#include "example/include/concat.h" + +rust::String concat(ConcatRequest r) { + // The full suite of operator overloads hasn't been added + // yet on rust::String, but we can get it done like this: + return std::string(r.fst) + std::string(r.snd); +} +``` diff --git a/book/src/binding/uniqueptr.md b/book/src/binding/uniqueptr.md new file mode 100644 index 00000000..eefbc34a --- /dev/null +++ b/book/src/binding/uniqueptr.md @@ -0,0 +1,63 @@ +{{#title std::unique_ptr<T> — Rust ♡ C++}} +# std::unique\_ptr\<T\> + +The Rust binding of std::unique\_ptr\<T\> is called **[`UniquePtr<T>`]**. See +the link for documentation of the Rust API. + +[`UniquePtr<T>`]: https://docs.rs/cxx/*/cxx/struct.UniquePtr.html + +### Restrictions: + +Only `std::unique_ptr<T, std::default_delete<T>>` is currently supported. Custom +deleters may be supported in the future. + +UniquePtr\<T\> does not support T being an opaque Rust type. You should use a +Box\<T\> (C++ [rust::Box\<T\>](box.md)) instead for transferring ownership of +opaque Rust types on the language boundary. + +## Example + +UniquePtr is commonly useful for returning opaque C++ objects to Rust. This use +case was featured in the [*blobstore tutorial*](../tutorial.md). + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("example/include/blobstore.h"); + + type BlobstoreClient; + + fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; + // ... + } +} + +fn main() { + let client = ffi::new_blobstore_client(); + // ... +} +``` + +```cpp +// include/blobstore.h + +#pragma once +#include <memory> + +class BlobstoreClient; + +std::unique_ptr<BlobstoreClient> new_blobstore_client(); +``` + +```cpp +// src/blobstore.cc + +#include "example/include/blobstore.h" + +std::unique_ptr<BlobstoreClient> new_blobstore_client() { + return std::make_unique<BlobstoreClient>(); +} +``` diff --git a/book/src/binding/vec.md b/book/src/binding/vec.md new file mode 100644 index 00000000..948fa671 --- /dev/null +++ b/book/src/binding/vec.md @@ -0,0 +1,190 @@ +{{#title rust::Vec<T> — Rust ♡ C++}} +# rust::Vec\<T\> + +### Public API: + +```cpp,hidelines +// rust/cxx.h +# +# #include <initializer_list> +# #include <iterator> +# #include <type_traits> +# +# namespace rust { + +template <typename T> +class Vec final { +public: + using value_type = T; + + Vec() noexcept; + Vec(std::initializer_list<T>); + Vec(const Vec &); + Vec(Vec &&) noexcept; + ~Vec() noexcept; + + Vec &operator=(Vec &&) noexcept; + Vec &operator=(const Vec &); + + size_t size() const noexcept; + bool empty() const noexcept; + const T *data() const noexcept; + T *data() noexcept; + size_t capacity() const noexcept; + + const T &operator[](size_t n) const noexcept; + const T &at(size_t n) const; + const T &front() const; + const T &back() const; + + T &operator[](size_t n) noexcept; + T &at(size_t n); + T &front(); + T &back(); + + void reserve(size_t new_cap); + void push_back(const T &value); + void push_back(T &&value); + template <typename... Args> + void emplace_back(Args &&...args); + + class iterator; + iterator begin() noexcept; + iterator end() noexcept; + + class const_iterator; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + void swap(Vec &) noexcept; +}; +# +# template <typename T> +# class Vec<T>::iterator final { +# public: +# using iterator_category = std::random_access_iterator_tag; +# using value_type = T; +# using pointer = T *; +# using reference = T &; +# +# T &operator*() const noexcept; +# T *operator->() const noexcept; +# T &operator[](ptrdiff_t) const noexcept; +# +# iterator &operator++() noexcept; +# iterator operator++(int) noexcept; +# iterator &operator--() noexcept; +# iterator operator--(int) noexcept; +# +# iterator &operator+=(ptrdiff_t) noexcept; +# iterator &operator-=(ptrdiff_t) noexcept; +# iterator operator+(ptrdiff_t) const noexcept; +# iterator operator-(ptrdiff_t) const noexcept; +# ptrdiff_t operator-(const iterator &) const noexcept; +# +# bool operator==(const iterator &) const noexcept; +# bool operator!=(const iterator &) const noexcept; +# bool operator<(const iterator &) const noexcept; +# bool operator<=(const iterator &) const noexcept; +# bool operator>(const iterator &) const noexcept; +# bool operator>=(const iterator &) const noexcept; +# }; +# +# template <typename T> +# class Vec<T>::const_iterator final { +# public: +# using iterator_category = std::random_access_iterator_tag; +# using value_type = const T; +# using pointer = const T *; +# using reference = const T &; +# +# const T &operator*() const noexcept; +# const T *operator->() const noexcept; +# const T &operator[](ptrdiff_t) const noexcept; +# +# const_iterator &operator++() noexcept; +# const_iterator operator++(int) noexcept; +# const_iterator &operator--() noexcept; +# const_iterator operator--(int) noexcept; +# +# const_iterator &operator+=(ptrdiff_t) noexcept; +# const_iterator &operator-=(ptrdiff_t) noexcept; +# const_iterator operator+(ptrdiff_t) const noexcept; +# const_iterator operator-(ptrdiff_t) const noexcept; +# ptrdiff_t operator-(const const_iterator &) const noexcept; +# +# bool operator==(const const_iterator &) const noexcept; +# bool operator!=(const const_iterator &) const noexcept; +# bool operator<(const const_iterator &) const noexcept; +# bool operator<=(const const_iterator &) const noexcept; +# bool operator>(const const_iterator &) const noexcept; +# bool operator>=(const const_iterator &) const noexcept; +# }; +# +# } // namespace rust +``` + +### Restrictions: + +Vec\<T\> does not support T being an opaque C++ type. You should use +CxxVector\<T\> (C++ std::vector\<T\>) instead for collections of opaque C++ +types on the language boundary. + +## Example + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + struct Shared { + v: u32, + } + + unsafe extern "C++" { + include!("example/include/example.h"); + + fn f(elements: Vec<Shared>); + } +} + +fn main() { + let shared = |v| ffi::Shared { v }; + let elements = vec![shared(3), shared(2), shared(1)]; + ffi::f(elements); +} +``` + +```cpp +// include/example.h + +#pragma once +#include "example/src/main.rs.h" +#include "rust/cxx.h" + +void f(rust::Vec<Shared> elements); +``` + +```cpp +// src/example.cc + +#include "example/include/example.h" +#include <algorithm> +#include <cassert> +#include <iostream> +#include <iterator> +#include <vector> + +void f(rust::Vec<Shared> v) { + for (auto shared : v) { + std::cout << shared.v << std::endl; + } + + // Copy the elements to a C++ std::vector using STL algorithm. + std::vector<Shared> stdv; + std::copy(v.begin(), v.end(), std::back_inserter(stdv)); + assert(v.size() == stdv.size()); +} +``` diff --git a/book/src/bindings.md b/book/src/bindings.md new file mode 100644 index 00000000..bcb51d8e --- /dev/null +++ b/book/src/bindings.md @@ -0,0 +1,56 @@ +{{#title Built-in bindings — Rust ♡ C++}} +# Built-in bindings reference + +In addition to all the primitive types (i32 <=> int32_t), the following +common types may be used in the fields of shared structs and the arguments and +returns of extern functions. + +<br> + +<table> +<tr><th>name in Rust</th><th>name in C++</th><th>restrictions</th></tr> +<tr><td style="padding:3px 6px">String</td><td style="padding:3px 6px"><b><a href="binding/string.md">rust::String</a></b></td><td style="padding:3px 6px"></td></tr> +<tr><td style="padding:3px 6px">&str</td><td style="padding:3px 6px"><b><a href="binding/str.md">rust::Str</a></b></td><td style="padding:3px 6px"></td></tr> +<tr><td style="padding:3px 6px">&[T]</td><td style="padding:3px 6px"><b><a href="binding/slice.md">rust::Slice<const T></a></b></td><td style="padding:3px 6px"><sup><i>cannot hold opaque C++ type</i></sup></td></tr> +<tr><td style="padding:3px 6px">&mut [T]</td><td style="padding:3px 6px"><b><a href="binding/slice.md">rust::Slice<T></a></b></td><td style="padding:3px 6px"><sup><i>cannot hold opaque C++ type</i></sup></td></tr> +<tr><td style="padding:3px 6px"><b><a href="binding/cxxstring.md">CxxString</a></b></td><td style="padding:3px 6px">std::string</td><td style="padding:3px 6px"><sup><i>cannot be passed by value</i></sup></td></tr> +<tr><td style="padding:3px 6px">Box<T></td><td style="padding:3px 6px"><b><a href="binding/box.md">rust::Box<T></a></b></td><td style="padding:3px 6px"><sup><i>cannot hold opaque C++ type</i></sup></td></tr> +<tr><td style="padding:3px 6px"><b><a href="binding/uniqueptr.md">UniquePtr<T></a></b></td><td style="padding:3px 6px">std::unique_ptr<T></td><td style="padding:3px 6px"><sup><i>cannot hold opaque Rust type</i></sup></td></tr> +<tr><td style="padding:3px 6px"><b><a href="binding/sharedptr.md">SharedPtr<T></a></b></td><td style="padding:3px 6px">std::shared_ptr<T></td><td style="padding:3px 6px"><sup><i>cannot hold opaque Rust type</i></sup></td></tr> +<tr><td style="padding:3px 6px">[T; N]</td><td style="padding:3px 6px">std::array<T, N></td><td style="padding:3px 6px"><sup><i>cannot hold opaque C++ type</i></sup></td></tr> +<tr><td style="padding:3px 6px">Vec<T></td><td style="padding:3px 6px"><b><a href="binding/vec.md">rust::Vec<T></a></b></td><td style="padding:3px 6px"><sup><i>cannot hold opaque C++ type</i></sup></td></tr> +<tr><td style="padding:3px 6px"><b><a href="binding/cxxvector.md">CxxVector<T></a></b></td><td style="padding:3px 6px">std::vector<T></td><td style="padding:3px 6px"><sup><i>cannot be passed by value, cannot hold opaque Rust type</i></sup></td></tr> +<tr><td style="padding:3px 6px"><b><a href="binding/rawptr.md">*mut T, *const T</a></b></td><td style="padding:3px 6px">T*, const T*</td><td style="padding:3px 6px"><sup><i>fn with a raw pointer argument must be declared unsafe to call</i></sup></td></tr> +<tr><td style="padding:3px 6px">fn(T, U) -> V</td><td style="padding:3px 6px"><b><a href="binding/fn.md">rust::Fn<V(T, U)></a></b></td><td style="padding:3px 6px"><sup><i>only passing from Rust to C++ is implemented so far</i></sup></td></tr> +<tr><td style="padding:3px 6px"><b><a href="binding/result.md">Result<T></a></b></td><td style="padding:3px 6px">throw/catch</td><td style="padding:3px 6px"><sup><i>allowed as return type only</i></sup></td></tr> +</table> + +<br> + +The C++ API of the `rust` namespace is defined by the *include/cxx.h* file in +the CXX GitHub repo. You will need to include this header in your C++ code when +working with those types. **When using Cargo and the cxx-build crate, the header +is made available to you at `#include "rust/cxx.h"`.** + +The `rust` namespace additionally provides lowercase type aliases of all the +types mentioned in the table, for use in codebases preferring that style. For +example `rust::String`, `rust::Vec` may alternatively be written `rust::string`, +`rust::vec` etc. + +## Pending bindings + +The following types are intended to be supported "soon" but are just not +implemented yet. I don't expect any of these to be hard to make work but it's a +matter of designing a nice API for each in its non-native language. + +<br> + +<table> +<tr><th>name in Rust</th><th>name in C++</th></tr> +<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> +</table> diff --git a/book/src/build/bazel.md b/book/src/build/bazel.md new file mode 100644 index 00000000..08edb19f --- /dev/null +++ b/book/src/build/bazel.md @@ -0,0 +1,106 @@ +{{#title Bazel, Buck — Rust ♡ C++}} +## Bazel, Buck, potentially other similar environments + +Starlark-based build systems with the ability to compile a code generator and +invoke it as a `genrule` will run CXX's C++ code generator via its `cxxbridge` +command line interface. + +The tool is packaged as the `cxxbridge-cmd` crate on crates.io or can be built +from the *gen/cmd/* directory of the CXX GitHub repo. + +```console +$ cargo install cxxbridge-cmd + +$ cxxbridge src/bridge.rs --header > path/to/bridge.rs.h +$ cxxbridge src/bridge.rs > path/to/bridge.rs.cc +``` + +The CXX repo maintains working Bazel `BUILD` and Buck `BUCK` targets for the +complete blobstore tutorial (chapter 3) for your reference, tested in CI. These +aren't meant to be directly what you use in your codebase, but serve as an +illustration of one possible working pattern. + +```python +# tools/bazel/rust_cxx_bridge.bzl + +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"], + ) +``` + +```python +# demo/BUILD + +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"], + deps = [ + ":blobstore-include", + ":bridge/include", + ], +) + +cc_library( + name = "blobstore-include", + hdrs = ["include/blobstore.h"], + deps = ["//:core"], +) +``` diff --git a/book/src/build/cargo.md b/book/src/build/cargo.md new file mode 100644 index 00000000..82ccfb50 --- /dev/null +++ b/book/src/build/cargo.md @@ -0,0 +1,306 @@ +{{#title Cargo-based setup — Rust ♡ C++}} +# Cargo-based builds + +As one aspect of delivering a good Rust–C++ interop experience, CXX turns +Cargo into a quite usable build system for C++ projects published as a +collection of crates.io packages, including a consistent and frictionless +experience `#include`-ing C++ headers across dependencies. + +## Canonical setup + +CXX's integration with Cargo is handled through the [cxx-build] crate. + +[cxx-build]: https://docs.rs/cxx-build + +```toml,hidelines +## Cargo.toml +# [package] +# name = "..." +# version = "..." +# edition = "2018" + +[dependencies] +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" +``` + +The canonical build script is as follows. The indicated line returns a +[`cc::Build`] instance (from the usual widely used `cc` crate) on which you can +set up any additional source files and compiler flags as normal. + +[`cc::Build`]: https://docs.rs/cc/1.0/cc/struct.Build.html + +```rust,noplayground +// build.rs + +fn main() { + cxx_build::bridge("src/main.rs") // returns a cc::Build + .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=src/demo.cc"); + println!("cargo:rerun-if-changed=include/demo.h"); +} +``` + +The `rerun-if-changed` lines are optional but make it so that Cargo does not +spend time recompiling your C++ code when only non-C++ code has changed since +the previous Cargo build. By default without any `rerun-if-changed`, Cargo will +re-execute the build script after *any* file changed in the project. + +If stuck, try comparing what you have against the *demo/* directory of the CXX +GitHub repo, which maintains a working Cargo-based setup for the blobstore +tutorial (chapter 3). + +## Header include paths + +With cxx-build, by default your include paths always start with the crate name. +This applies to both `#include` within your C++ code, and `include!` in the +`extern "C++"` section of your Rust cxx::bridge. + +Your crate name is determined by the `name` entry in Cargo.toml. + +For example if your crate is named `yourcratename` and contains a C++ header +file `path/to/header.h` relative to Cargo.toml, that file will be includable as: + +```cpp +#include "yourcratename/path/to/header.h" +``` + +A crate can choose a prefix for its headers that is different from the crate +name by modifying **[`CFG.include_prefix`][CFG]** from build.rs: + +[CFG]: https://docs.rs/cxx-build/*/cxx_build/static.CFG.html + +```rust,noplayground +// build.rs + +use cxx_build::CFG; + +fn main() { + CFG.include_prefix = "my/project"; + + cxx_build::bridge(...)... +} +``` + +Subsequently the header located at `path/to/header.h` would now be includable +as: + +```cpp +#include "my/project/path/to/header.h" +``` + +The empty string `""` is a valid include prefix and will make it possible to +have `#include "path/to/header.h"`. However, if your crate is a library, be +considerate of possible name collisions that may occur in downstream crates. If +using an empty include prefix, you'll want to make sure your headers' local path +within the crate is sufficiently namespaced or unique. + +## Including generated code + +If your `#[cxx::bridge]` module contains an `extern "Rust"` block i.e. types or +functions exposed from Rust to C++, or any shared data structures, the +CXX-generated C++ header declaring those things is available using a `.rs.h` +extension on the Rust source file's name. + +```cpp +// the header generated from path/to/lib.rs +#include "yourcratename/path/to/lib.rs.h" +``` + +For giggles, it's also available using just a plain `.rs` extension as if you +were including the Rust file directly. Use whichever you find more palatable. + +```cpp +#include "yourcratename/path/to/lib.rs" +``` + +## Including headers from dependencies + +You get to include headers from your dependencies, both handwritten ones +contained as `.h` files in their Cargo package, as well as CXX-generated ones. + +It works the same as an include of a local header: use the crate name (or their +include\_prefix if their crate changed it) followed by the relative path of the +header within the crate. + +```cpp +#include "dependencycratename/path/to/their/header.h` +``` + +Note that cross-crate imports are only made available between **direct +dependencies**. You must directly depend on the other 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. See *[the `links` +manifest key][links]* in the Cargo reference. + +[links]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key + +<br><br><br> + +# Advanced features + +The following CFG settings are only relevant to you if you are writing a library +that needs to support downstream crates `#include`-ing its C++ public headers. + +## Publicly exporting header directories + +**[`CFG.exported_header_dirs`][CFG]** (vector of absolute paths) defines a set +of additional directories from which the current crate, directly dependent +crates, and further crates to which this crate's headers are exported (more +below) will be able to `#include` headers. + +Adding a directory to `exported_header_dirs` is similar to adding it to the +current build via the `cc` crate's [`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. + +[`Build::include`]: https://docs.rs/cc/1/cc/struct.Build.html#method.include + +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 otherwise +execute before yours decides what to put into `exported_header_dirs`. + +### Example + +One of your crate's headers wants to include a system library, such as `#include +"Python.h"`. + +```rust,noplayground +// 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"`. + +```rust,noplayground +// 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"); +} +``` + +## Publicly exporting dependencies + +**[`CFG.exported_header_prefixes`][CFG]** (vector of strings) 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: + +```rust,noplayground +// 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"); +} +``` + +<br> + +For more fine grained control, there is **[`CFG.exported_header_links`][CFG]** +(vector of strings) which 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 C++ dependency as part of your crate's public API, except with +finer 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 + +```rust,noplayground +// build.rs + +use cxx_build::CFG; + +fn main() { + CFG.exported_header_links.push("git2"); + + cxx_build::bridge("src/bridge.rs").compile("demo"); +} +``` diff --git a/book/src/build/cmake.md b/book/src/build/cmake.md new file mode 100644 index 00000000..ed3cb61d --- /dev/null +++ b/book/src/build/cmake.md @@ -0,0 +1,24 @@ +{{#title CMake — Rust ♡ C++}} +# CMake + +There is not an officially endorsed CMake setup for CXX, but a few developers +have shared one that they got working. You can try one of these as a starting +point. If you feel that you have arrived at a CMake setup that is superior to +what is available in these links, feel free to make a PR adding it to this list. + +<br> + +--- + +- **<https://github.com/XiangpengHao/cxx-cmake-example>** + + - Supports cross-language link time optimization (LTO) + +--- + +- **<https://github.com/david-cattermole/cxx-demo-example>** + + - Includes a cbindgen component + - Tested on Windows 10 with MSVC, and on Linux + +--- diff --git a/book/src/build/other.md b/book/src/build/other.md new file mode 100644 index 00000000..8933c41b --- /dev/null +++ b/book/src/build/other.md @@ -0,0 +1,75 @@ +{{#title Other build systems — Rust ♡ C++}} +# Some other build system + +You will need to achieve at least these three things: + +- Produce the CXX-generated C++ bindings code. +- Compile the generated C++ code. +- Link the resulting objects together with your other C++ and Rust objects. + +### Producing the generated code + +CXX's Rust code generation automatically happens when the `#[cxx::bridge]` +procedural macro is expanded during the normal Rust compilation process, so no +special build steps are required there. + +But the C++ side of the bindings needs to be generated. Your options are: + +- Use the `cxxbridge` command, which is a standalone command line interface to + the CXX C++ code generator. Wire up your build system to compile and invoke + this tool. + + ```console + $ cxxbridge src/bridge.rs --header > path/to/bridge.rs.h + $ cxxbridge src/bridge.rs > path/to/bridge.rs.cc + ``` + + It's packaged as the `cxxbridge-cmd` crate on crates.io or can be built from + the *gen/cmd/* directory of the CXX GitHub repo. + +- Or, build your own code generator frontend on top of the [cxx-gen] crate. This + is currently unofficial and unsupported. + +[cxx-gen]: https://docs.rs/cxx-gen + +### Compiling C++ + +However you like. We can provide no guidance. + +### Linking the C++ and Rust together + +When linking a binary which contains mixed Rust and C++ code, you will have to +choose between using the Rust toolchain (`rustc`) or the C++ toolchain which you +may already have extensively tuned. + +Rust does not generate simple standalone `.o` files, so you can't just throw the +Rust-generated code into your existing C++ toolchain linker. Instead you need to +choose one of these options: + +* Use `rustc` as the final linker. Pass any non-Rust libraries using `-L + <directory>` and `-l<library>` rustc arguments, and/or `#[link]` directives in + your Rust code. If you need to link against C/C++ `.o` files you can use + `-Clink-arg=file.o`. + +* Use your C++ linker. In this case, you first need to use `rustc` and/or + `cargo` to generate a _single_ Rust `staticlib` target and pass that into your + foreign linker invocation. + + * If you need to link multiple Rust subsystems, you will need to generate a + _single_ `staticlib` perhaps using lots of `extern crate` statements to + include multiple Rust `rlib`s. Multiple Rust `staticlib` files are likely + to conflict. + +Passing Rust `rlib`s directly into your non-Rust linker is not supported (but +apparently sometimes works). + +See the [Rust reference's *Linkage*][linkage] page for some general information +here. + +[linkage]: https://doc.rust-lang.org/reference/linkage.html + +The following open rust-lang issues might hold more recent guidance or +inspiration: [rust-lang/rust#73632], [rust-lang/rust#73295]. + +[rust-lang/rust#73632]: https://github.com/rust-lang/rust/issues/73632 +[rust-lang/rust#73295]: https://github.com/rust-lang/rust/issues/73295 diff --git a/book/src/building.md b/book/src/building.md new file mode 100644 index 00000000..c75939e0 --- /dev/null +++ b/book/src/building.md @@ -0,0 +1,20 @@ +{{#title Multi-language build system options — Rust ♡ C++}} +# Multi-language build system options + +CXX is designed to be convenient to integrate into a variety of build systems. + +If you are working in a project that does not already have a preferred build +system for its C++ code *or* which will be relying heavily on open source +libraries from the Rust package registry, you're likely to have the easiest +experience with Cargo which is the build system commonly used by open source +Rust projects. Refer to the ***[Cargo](build/cargo.md)*** chapter about CXX's +Cargo support. + +Among build systems designed for first class multi-language support, Bazel is a +solid choice. Refer to the ***[Bazel](build/bazel.md)*** chapter. + +If your codebase is already invested in CMake, refer to the +***[CMake](build/cmake.md)*** chapter. + +If you have some other build system that you'd like to try to make work with +CXX, see [this page](build/other.md) for notes. diff --git a/book/src/concepts.md b/book/src/concepts.md new file mode 100644 index 00000000..75daedde --- /dev/null +++ b/book/src/concepts.md @@ -0,0 +1,85 @@ +{{#title Core concepts — Rust ♡ C++}} +# Core concepts + +This page is a brief overview of the major concepts of CXX, enough so that you +recognize the shape of things as you read the tutorial and following chapters. + +In CXX, the language of the FFI boundary involves 3 kinds of items: + +- **Shared structs** — data structures whose fields are made visible to + both languages. The definition written within cxx::bridge in Rust is usually + the single source of truth, though there are ways to do sharing based on a + bindgen-generated definition with C++ as source of truth. + +- **Opaque types** — their fields are secret from the other language. + These cannot be passed across the FFI by value but only behind an indirection, + such as a reference `&`, a Rust `Box`, or a C++ `unique_ptr`. Can be a type + alias for an arbitrarily complicated generic language-specific type depending + on your use case. + +- **Functions** — implemented in either language, callable from the other + language. + +```rust,noplayground,focuscomment +# #[cxx::bridge] +# mod ffi { + // Any shared structs, whose fields will be visible to both languages. +# 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 MultiBuf; +# + // Functions implemented in Rust. +# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; +# } +# +# unsafe extern "C++" { + // One or more headers with the matching C++ declarations for the + // enclosing extern "C++" block. 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; +# } +# } +``` + +Within the `extern "Rust"` part of the CXX bridge we list the types and +functions for which Rust is the source of truth. These all implicitly refer to +the `super` module, the parent module of the CXX bridge. You can think of the +two items listed in the example above as being like `use super::MultiBuf` and +`use super::next_chunk` except re-exported to C++. The parent module will either +contain the definitions directly for simple things, or contain the relevant +`use` statements to bring them into scope from elsewhere. + +Within the `extern "C++"` part, we list types and functions for which C++ is the +source of truth, as well as the header(s) that declare those APIs. In the future +it's possible that this section could be generated bindgen-style from the +headers but for now we need the signatures written out; static assertions verify +that they are accurate. + +<br><br> + +Be aware that the design of this library is intentionally restrictive and +opinionated! It isn't a goal to be flexible enough to handle an arbitrary +signature in either language. Instead this project is about carving out a highly +expressive set of functionality about which we can make powerful safety +guarantees today and extend over time. You may find that it takes some practice +to use CXX bridge effectively as it won't work in all the ways that you may be +used to. + +<br> diff --git a/book/src/context.md b/book/src/context.md new file mode 100644 index 00000000..516ee91f --- /dev/null +++ b/book/src/context.md @@ -0,0 +1,118 @@ +{{#title Other Rust–C++ interop tools — Rust ♡ C++}} +# Context: other Rust–C++ interop tools + +When it comes to interacting with an idiomatic Rust API or idiomatic C++ API +from the other language, the generally applicable approaches outside of the CXX +crate are: + +- Build a C-compatible wrapper around the code (expressed using `extern "C"` + signatures, primitives, C-compatible structs, raw pointers). Translate that + manually to equivalent `extern "C"` declarations in the other language and + keep them in sync. Preferably, build a safe/idiomatic wrapper around the + translated `extern "C"` signatures for callers to use. + +- Build a C wrapper around the C++ code and use **[bindgen]** to translate that + programmatically to `extern "C"` Rust signatures. Preferably, build a + safe/idiomatic Rust wrapper on top. + +- Build a C-compatible Rust wrapper around the Rust code and use **[cbindgen]** + to translate that programmatically to an `extern "C"` C++ header. Preferably, + build an idiomatic C++ wrapper. + +**If the code you are binding is already *"effectively C"*, the above has you +covered.** You should use bindgen or cbindgen, or manually translated C +signatures if there aren't too many and they seldom change. + +[bindgen]: https://github.com/rust-lang/rust-bindgen +[cbindgen]: https://github.com/eqrion/cbindgen + +## C++ vs C + +Bindgen has some basic support for C++. It can reason about classes, member +functions, and the layout of templated types. However, everything it does +related to C++ is best-effort only. Bindgen starts from a point of wanting to +generate declarations for everything, so any C++ detail that it hasn't +implemented will cause a crash if you are lucky ([bindgen#388]) or more likely +silently emit an incompatible signature ([bindgen#380], [bindgen#607], +[bindgen#652], [bindgen#778], [bindgen#1194]) which will do arbitrary +memory-unsafe things at runtime whenever called. + +[bindgen#388]: https://github.com/rust-lang/rust-bindgen/issues/388 +[bindgen#380]: https://github.com/rust-lang/rust-bindgen/issues/380 +[bindgen#607]: https://github.com/rust-lang/rust-bindgen/issues/607 +[bindgen#652]: https://github.com/rust-lang/rust-bindgen/issues/652 +[bindgen#778]: https://github.com/rust-lang/rust-bindgen/issues/778 +[bindgen#1194]: https://github.com/rust-lang/rust-bindgen/issues/1194 + +Thus using bindgen correctly requires not just juggling all your pointers +correctly at the language boundary, but also understanding ABI details and their +workarounds and reliably applying them. For example, the programmer will +discover that their program sometimes segfaults if they call a function that +returns std::unique\_ptr\<T\> through bindgen. Why? Because unique\_ptr, despite +being "just a pointer", has a different ABI than a pointer or a C struct +containing a pointer ([bindgen#778]) and is not directly expressible in Rust. +Bindgen emitted something that *looks* reasonable and you will have a hell of a +time in gdb working out what went wrong. Eventually people learn to avoid +anything involving a non-trivial copy constructor, destructor, or inheritance, +and instead stick to raw pointers and primitives and trivial structs only +— in other words C. + +## Geometric intuition for why there is so much opportunity for improvement + +The CXX project attempts a different approach to C++ FFI. + +Imagine Rust and C and C++ as three vertices of a scalene triangle, with length +of the edges being related to similarity of the languages when it comes to +library design. + +The most similar pair (the shortest edge) is Rust–C++. These languages +have largely compatible concepts of things like ownership, vectors, strings, +fallibility, etc that translate clearly from signatures in either language to +signatures in the other language. + +When we make a binding for an idiomatic C++ API using bindgen, and we fall down +to raw pointers and primitives and trivial structs as described above, what we +are really doing is coding the two longest edges of the triangle: getting from +C++ down to C, and C back up to Rust. The Rust–C edge always involves a +great deal of `unsafe` code, and the C++–C edge similarly requires care +just for basic memory safety. Something as basic as "how do I pass ownership of +a string to the other language?" becomes a strap-yourself-in moment, +particularly for someone not already an expert in one or both sides. + +You should think of the `cxx` crate as being the midpoint of the Rust–C++ +edge. Rather than coding the two long edges, you will code half the short edge +in Rust and half the short edge in C++, in both cases with the library playing +to the strengths of the Rust type system *and* the C++ type system to help +assure correctness. + +If you've already been through the tutorial in the previous chapter, take a +moment to appreciate that the C++ side *really* looks like we are just writing +C++ and the Rust side *really* looks like we are just writing Rust. Anything you +could do wrong in Rust, and almost anything you could reasonably do wrong in +C++, will be caught by the compiler. This highlights that we are on the "short +edge of the triangle". + +But it all still boils down to the same things: it's still FFI from one piece of +native code to another, nothing is getting serialized or allocated or +runtime-checked in between. + +## Role of CXX + +The role of CXX is to capture the language boundary with more fidelity than what +`extern "C"` is able to represent. You can think of CXX as being a replacement +for `extern "C"` in a sense. + +From this perspective, CXX is a lower level tool than the bindgens. Just as +bindgen and cbindgen are built on top of `extern "C"`, it makes sense to think +about higher level tools built on top of CXX. Such a tool might consume a C++ +header and/or Rust module (and/or IDL like Thrift) and emit the corresponding +safe cxx::bridge language boundary, leveraging CXX's static analysis and +underlying implementation of that boundary. We are beginning to see this space +explored by the [autocxx] tool, though nothing yet ready for broad use in the +way that CXX on its own is. + +[autocxx]: https://github.com/google/autocxx + +But note in other ways CXX is higher level than the bindgens, with rich support +for common standard library types. CXX's types serve as an intuitive vocabulary +for designing a good boundary between components in different languages. diff --git a/book/src/cxx.png b/book/src/cxx.png Binary files differnew file mode 100644 index 00000000..aeb7ca9b --- /dev/null +++ b/book/src/cxx.png diff --git a/book/src/extern-c++.md b/book/src/extern-c++.md new file mode 100644 index 00000000..11ed7b54 --- /dev/null +++ b/book/src/extern-c++.md @@ -0,0 +1,352 @@ +{{#title extern "C++" — Rust ♡ C++}} +# extern "C++" + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "C++" { + include!("path/to/header.h"); + include!("path/to/another.h"); + + ... + } +} +``` + +The `extern "C++"` section of a CXX bridge declares C++ types and signatures to +be made available to Rust, and gives the paths of the header(s) which contain +the corresponding C++ declarations. + +A bridge module may contain zero or more extern "C++" blocks. + +## Opaque C++ types + +Type defined in C++ that are made available to Rust, but only behind an +indirection. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { + extern "C++" { + # include!("path/to/header.h"); + # + type MyType; + type MyOtherType; + } +# } +``` + +For example in the ***[Tutorial](tutorial.md)*** we saw `BlobstoreClient` +implemented as an opaque C++ type. The blobstore client was created in C++ and +returned to Rust by way of a UniquePtr. + +**Mutability:** Unlike extern Rust types and shared types, an extern C++ type is +not permitted to be passed by plain mutable reference `&mut MyType` across the +FFI bridge. For mutation support, the bridge is required to use `Pin<&mut +MyType>`. This is to safeguard against things like mem::swap-ing the contents of +two mutable references, given that Rust doesn't have information about the size +of the underlying object and couldn't invoke an appropriate C++ move constructor +anyway. + +**Thread safety:** Be aware that CXX does not assume anything about the thread +safety of your extern C++ types. In other words the `MyType` etc bindings which +CXX produces for you in Rust *do not* come with `Send` and `Sync` impls. If you +are sure that your C++ type satisfies the requirements of `Send` and/or `Sync` +and need to leverage that fact from Rust, you must provide your own unsafe +marker trait impls. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { +# extern "C++" { +# include!("path/to/header.h"); +# +# type MyType; +# } +# } +# +/// The C++ implementation of MyType is thread safe. +unsafe impl Send for ffi::MyType {} +unsafe impl Sync for ffi::MyType {} +``` + +Take care in doing this because thread safety in C++ can be extremely tricky to +assess if you are coming from a Rust background. For example the +`BlobstoreClient` type in the tutorial is *not thread safe* despite doing only +completely innocuous things in its implementation. Concurrent calls to the `tag` +member function trigger a data race on the `blobs` map. + +## Functions and member functions + +This largely follows the same principles as ***[extern +"Rust"](extern-rust.md)*** functions and methods. In particular, any signature +with a `self` parameter is interpreted as a C++ non-static member function and +exposed to Rust as a method. + +The programmer **does not** need to promise that the signatures they have typed +in are accurate; that would be unreasonable. CXX performs static assertions that +the signatures exactly correspond with what is declared in C++. Rather, the +programmer is only on the hook for things that C++'s static information is not +precise enough to capture, i.e. things that would only be represented at most by +comments in the C++ code unintelligible to a static assertion: namely whether +the C++ function is safe or unsafe to be called from Rust. + +**Safety:** the extern "C++" block is responsible for deciding whether to expose +each signature inside as safe-to-call or unsafe-to-call. If an extern block +contains at least one safe-to-call signature, it must be written as an `unsafe +extern` block, which serves as an item level unsafe block to indicate that an +unchecked safety claim is being made about the contents of the block. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + # include!("path/to/header.h"); + # + fn f(); // safe to call + } + + extern "C++" { + unsafe fn g(); // unsafe to call + } +} +``` + +## Lifetimes + +C++ types holding borrowed data may be described naturally in Rust by an extern +type with a generic lifetime parameter. For example in the case of the following +pair of types: + +```cpp +// header.h + +class Resource; + +class TypeContainingBorrow { + TypeContainingBorrow(const Resource &res) : res(res) {} + const Resource &res; +}; + +std::shared_ptr<TypeContainingBorrow> create(const Resource &res); +``` + +we'd want to expose this to Rust as: + +```rust,noplayground +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + # include!("path/to/header.h"); + # + type Resource; + type TypeContainingBorrow<'a>; + + fn create<'a>(res: &'a Resource) -> SharedPtr<TypeContainingBorrow<'a>>; + + // or with lifetime elision: + fn create(res: &Resource) -> SharedPtr<TypeContainingBorrow>; + } +} +``` + +## Reusing existing binding types + +Extern C++ types support a syntax for declaring that a Rust binding of the +correct C++ type already exists outside of the current bridge module. This +avoids generating a fresh new binding which Rust's type system would consider +non-interchangeable with the first. + +```rust,noplayground +#[cxx::bridge(namespace = "path::to")] +mod ffi { + extern "C++" { + type MyType = crate::existing::MyType; + } + + extern "Rust" { + fn f(x: &MyType) -> usize; + } +} +``` + +In this case rather than producing a unique new Rust type `ffi::MyType` for the +Rust binding of C++'s `::path::to::MyType`, CXX will reuse the already existing +binding at `crate::existing::MyType` in expressing the signature of `f` and any +other uses of `MyType` within the bridge module. + +CXX safely validates that `crate::existing::MyType` is in fact a binding for the +right C++ type `::path::to::MyType` by generating a static assertion based on +`crate::existing::MyType`'s implementation of [`ExternType`], which is a trait +automatically implemented by CXX for bindings that it generates but can also be +manually implemented as described below. + +[`ExternType`]: https://docs.rs/cxx/*/cxx/trait.ExternType.html + +`ExternType` serves the following two related use cases. + +#### Safely unifying occurrences of an extern type across bridges + +In the following snippet, two #\[cxx::bridge\] invocations in different files +(possibly different crates) both contain function signatures involving the same +C++ type `example::Demo`. If both were written just containing `type Demo;`, +then both macro expansions would produce their own separate Rust type called +`Demo` and thus the compiler wouldn't allow us to take the `Demo` returned by +`file1::ffi::create_demo` and pass it as the `Demo` argument accepted by +`file2::ffi::take_ref_demo`. Instead, one of the two `Demo`s has been defined as +an extern type alias of the other, making them the same type in Rust. + +```rust,noplayground +// file1.rs +#[cxx::bridge(namespace = "example")] +pub mod ffi { + unsafe extern "C++" { + type Demo; + + fn create_demo() -> UniquePtr<Demo>; + } +} +``` + +```rust,noplayground +// file2.rs +#[cxx::bridge(namespace = "example")] +pub mod ffi { + unsafe extern "C++" { + type Demo = crate::file1::ffi::Demo; + + fn take_ref_demo(demo: &Demo); + } +} +``` + +#### Integrating with bindgen-generated or handwritten unsafe bindings + +Handwritten `ExternType` impls make it possible to plug in a data structure +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 +equivalent to Rust type that is the `Self` type of the impl. + +```rust,noplayground +mod folly_sys; // the bindgen-generated bindings + +use cxx::{type_id, ExternType}; + +unsafe impl ExternType for folly_sys::StringPiece { + type Id = type_id!("folly::StringPiece"); + type Kind = cxx::kind::Opaque; +} + +#[cxx::bridge(namespace = "folly")] +pub mod ffi { + unsafe extern "C++" { + include!("rust_cxx_bindings.h"); + + type StringPiece = crate::folly_sys::StringPiece; + + fn print_string_piece(s: &StringPiece); + } +} + +// Now if we construct a StringPiece or obtain one through one +// of the bindgen-generated signatures, we are able to pass it +// along to ffi::print_string_piece. +``` + +The `ExternType::Id` associated type encodes a type-level representation of the +type's C++ namespace and type name. It will always be defined using the +`type_id!` macro exposed in the cxx crate. + +The `ExternType::Kind` associated type will always be either +[`cxx::kind::Opaque`] or [`cxx::kind::Trivial`] identifying whether a C++ type +is soundly relocatable by Rust's move semantics. 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. + +[`cxx::kind::Opaque`]: https://docs.rs/cxx/*/cxx/kind/enum.Opaque.html +[`cxx::kind::Trivial`]: https://docs.rs/cxx/*/cxx/kind/enum.Trivial.html +[move constructor is trivial]: https://en.cppreference.com/w/cpp/types/is_move_constructible + +If you believe your C++ type reflected by the ExternType impl is indeed fine to +hold by value and move in Rust, you can specify: + +```rust,noplayground +# 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. + +## Explicit shim trait impls + +This is a somewhat niche feature, but important when you need it. + +CXX's support for C++'s std::unique\_ptr and std::vector is built on a set of +internal trait impls connecting the Rust API of UniquePtr and CxxVector to +underlying template instantiations performed by the C++ compiler. + +When reusing a binding type across multiple bridge modules as described in the +previous section, you may find that your code needs some trait impls which CXX +hasn't decided to generate. + +```rust,noplayground +#[cxx::bridge] +mod ffi1 { + extern "C++" { + include!("path/to/header.h"); + + type A; + type B; + + // Okay: CXX sees UniquePtr<B> using a type B defined within the same + // bridge, and automatically emits the right template instantiations + // corresponding to std::unique_ptr<B>. + fn get_b() -> UniquePtr<B>; + } +} + +#[cxx::bridge] +mod ffi2 { + extern "C++" { + type A = crate::ffi1::A; + + // Rust trait error: CXX processing this module has no visibility into + // whether template instantiations corresponding to std::unique_ptr<A> + // have already been emitted by the upstream library, so it does not + // emit them here. If the upstream library does not have any signatures + // involving UniquePtr<A>, an explicit instantiation of the template + // needs to be requested in one module or the other. + fn get_a() -> UniquePtr<A>; + } +} +``` + +You can request a specific template instantiation at a particular location in +the Rust crate hierarchy by writing `impl UniquePtr<A> {}` inside of the bridge +module which defines `A` but does not otherwise contain any use of +`UniquePtr<A>`. + +```rust,noplayground +#[cxx::bridge] +mod ffi1 { + extern "C++" { + include!("path/to/header.h"); + + type A; + type B; + + fn get_b() -> UniquePtr<B>; + } + + impl UniquePtr<A> {} // explicit instantiation +} +``` diff --git a/book/src/extern-rust.md b/book/src/extern-rust.md new file mode 100644 index 00000000..ab9dcb64 --- /dev/null +++ b/book/src/extern-rust.md @@ -0,0 +1,165 @@ +{{#title extern "Rust" — Rust ♡ C++}} +# extern "Rust" + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "Rust" { + + } +} +``` + +The `extern "Rust"` section of a CXX bridge declares Rust types and signatures +to be made available to C++. + +The CXX code generator uses your extern "Rust" section(s) to produce a C++ +header file containing the corresponding C++ declarations. The generated header +has the same path as the Rust source file containing the bridge, except with a +`.rs.h` file extension. + +A bridge module may contain zero or more extern "Rust" blocks. + +## Opaque Rust types + +Types defined in Rust that are made available to C++, but only behind an +indirection. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { + extern "Rust" { + type MyType; + type MyOtherType; + type OneMoreType<'a>; + } +# } +``` + +For example in the ***[Tutorial](tutorial.md)*** we saw `MultiBuf` used in this +way. Rust code created the `MultiBuf`, passed a `&mut MultiBuf` to C++, and C++ +later passed a `&mut MultiBuf` back across the bridge to Rust. + +Another example is the one on the ***[Box\<T\>](binding/box.md)*** page, which +exposes the Rust standard library's `std::fs::File` to C++ as an opaque type in +a similar way but with Box as the indirection rather than &mut. + +The types named as opaque types (`MyType` etc) refer to types in the `super` +module, the parent module of the CXX bridge. You can think of an opaque type `T` +as being like a re-export `use super::T` made available to C++ via the generated +header. + +Opaque types are currently required to be [`Sized`] and [`Unpin`]. In +particular, a trait object `dyn MyTrait` or slice `[T]` may not be used for an +opaque Rust type. These restrictions may be lifted in the future. + +[`Sized`]: https://doc.rust-lang.org/std/marker/trait.Sized.html +[`Unpin`]: https://doc.rust-lang.org/std/marker/trait.Unpin.html + +For now, types used as extern Rust types are required to be defined by the same +crate that contains the bridge using them. This restriction may be lifted in the +future. + +The bridge's parent module will contain the appropriate imports or definitions +for these types. + +```rust,noplayground +use path::to::MyType; + +pub struct MyOtherType { + ... +} +# +# #[cxx::bridge] +# mod ffi { +# extern "Rust" { +# type MyType; +# type MyOtherType; +# } +# } +``` + +## Functions + +Rust functions made callable to C++. + +Just like for opaque types, these functions refer implicitly to something in +scope in the `super` module, whether defined there or imported by some `use` +statement. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "Rust" { + struct MyType; + fn f() -> Box<MyType>; + } +} + +struct MyType(i32); + +fn f() -> Box<MyType> { + return Box::new(MyType(1)); +} +``` + +Extern Rust function signature may consist of types defined in the bridge, +primitives, and [any of these additional bindings](bindings.md). + +## Methods + +Any signature with a `self` parameter is interpreted as a Rust method and +exposed to C++ as a non-static member function. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { + extern "Rust" { + type MyType; + fn f(&self) -> usize; + } +# } +``` + +The `self` parameter may be a shared reference `&self`, an exclusive reference +`&mut self`, or a pinned reference `self: Pin<&mut Self>`. A by-value `self` is +not currently supported. + +If the surrounding `extern "Rust"` block contains exactly one extern type, that +type is implicitly the receiver for a `&self` or `&mut self` method. If the +surrounding block contains *more than one* extern type, a receiver type must be +provided explicitly for the self parameter, or you can consider splitting into +multiple extern blocks. + +```rust,noplayground +# #[cxx::bridge] +# mod ffi { + extern "Rust" { + type First; + type Second; + fn bar(self: &First); + fn foo(self: &mut Second); + } +# } +``` + +## Functions with explicit lifetimes + +An extern Rust function signature is allowed to contain explicit lifetimes but +in this case the function must be declared unsafe-to-call. This is pretty +meaningless given we're talking about calls from C++, but at least it draws some +extra attention from the caller that they may be responsible for upholding some +atypical lifetime relationship. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "Rust" { + type MyType; + unsafe fn f<'a>(&'a self, s: &str) -> &'a str; + } +} +``` + +Bounds on a lifetime (like `<'a, 'b: 'a>`) are not currently supported. Nor are +type parameters or where-clauses. diff --git a/book/src/index.md b/book/src/index.md new file mode 100644 index 00000000..7b03e7e1 --- /dev/null +++ b/book/src/index.md @@ -0,0 +1,83 @@ +<div class="badges"> +<a href="https://github.com/dtolnay/cxx"><img src="https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github" alt="github" height="28" class="badge"></a><a href="https://crates.io/crates/cxx"><img src="https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust" alt="crates-io" height="28" class="badge"></a><a href="https://docs.rs/cxx"><img src="https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" alt="docs-rs" height="28" class="badge"></a> +</div> + +# CXX — safe interop between Rust and C++ + +This library provides a safe mechanism for calling C++ code from Rust and Rust +code from C++. It carves out a regime of commonality where Rust and C++ are +semantically very similar and guides the programmer to express their language +boundary effectively within this regime. CXX fills in the low level stuff so +that you get a safe binding, preventing the pitfalls of doing a foreign function +interface over unsafe C-style signatures. + +<div style="height:226px;padding:34px 0 24px"> +<object type="image/svg+xml" data="overview.svg"></object> +</div> + +From a high level description of the language boundary, CXX uses static analysis +of the types and function signatures to protect both Rust's and C++'s +invariants. Then it uses a pair of code generators to implement the boundary +efficiently on both sides together with any necessary static assertions for +later in the build process to verify correctness. + +The resulting FFI bridge operates at zero or negligible overhead, i.e. no +copying, no serialization, no memory allocation, no runtime checks needed. + +The FFI signatures are able to use native data structures from whichever side +they please. In addition, CXX provides builtin bindings for key standard library +types like strings, vectors, Box, unique\_ptr, etc to expose an idiomatic API on +those types to the other language. + +## Example + +In this example we are writing a Rust application that calls a C++ client of 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 pieces, or fragments of a file +spread across memory for some other reason (like a rope data structure). + +```rust,noplayground +#[cxx::bridge] +mod ffi { + extern "Rust" { + type MultiBuf; + + fn next_chunk(buf: &mut MultiBuf) -> &[u8]; + } + + unsafe extern "C++" { + include!("example/include/blobstore.h"); + + type BlobstoreClient; + + fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; + fn put(self: &BlobstoreClient, buf: &mut MultiBuf) -> Result<u64>; + } +} +``` + +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. + +The [***Tutorial***](tutorial.md) chapter walks through a fleshed out version of +this blobstore example in full detail, including all of the Rust code and all of +the C++ code. The code is also provided in runnable form in the *demo* directory +of <https://github.com/dtolnay/cxx>. To try it out, run `cargo run` from that +directory. + +- [demo/src/main.rs](https://github.com/dtolnay/cxx/blob/master/demo/src/main.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) + +The key takeaway, which is enabled by the CXX library, is that the Rust code in +main.rs is 100% ordinary safe Rust code working idiomatically with Rust types +while the C++ code in blobstore.cc is 100% ordinary C++ code working +idiomatically with C++ types. The Rust code feels like Rust and the C++ code +feels like C++, not like C-style "FFI glue". + +<br> + +***Chapter outline:** See the hamburger menu in the top left if you are on a +small screen and it didn't open with a sidebar by default.* diff --git a/book/src/overview.svg b/book/src/overview.svg new file mode 100644 index 00000000..65905471 --- /dev/null +++ b/book/src/overview.svg @@ -0,0 +1,99 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="696" height="224"> + <style>line, path, circle,rect,polygon { + stroke: black; + stroke-width: 2; + stroke-opacity: 1; + fill-opacity: 1; + stroke-linecap: round; + stroke-linejoin: miter; + } + + text { + fill: black; + } + rect.backdrop{ + stroke: none; + fill: white; + fill-opacity: 0; + } + .broken{ + stroke-dasharray: 8; + } + .filled{ + fill: black; + } + .bg_filled{ + fill: white; + } + .nofill{ + fill: white; + fill-opacity: 0; + } + + text { + font-family: monospace; + font-size: 14px; + } + </style> + <defs> + <marker id="arrow" viewBox="-2 -2 8 8" refX="4" refY="2" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <polygon points="0,0 0,4 4,2 0,0"></polygon> + </marker> + <marker id="diamond" viewBox="-2 -2 8 8" refX="4" refY="2" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <polygon points="0,2 2,0 4,2 2,4 0,2"></polygon> + </marker> + <marker id="circle" viewBox="0 0 8 8" refX="4" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <circle cx="4" cy="4" r="2" class="filled"></circle> + </marker> + <marker id="open_circle" viewBox="0 0 8 8" refX="4" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <circle cx="4" cy="4" r="2" class="bg_filled"></circle> + </marker> + <marker id="big_open_circle" viewBox="0 0 8 8" refX="4" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse"> + <circle cx="4" cy="4" r="3" class="bg_filled"></circle> + </marker> + </defs> + <rect class="backdrop" x="0" y="0" width="696" height="224"></rect> + <rect x="148" y="168" width="136" height="32" class="solid nofill" rx="4"></rect> + <text x="162" y="188" >Rust bindings</text> + <rect x="412" y="168" width="128" height="32" class="solid nofill" rx="4"></rect> + <text x="426" y="188" >C++ bindings</text> + <text x="272" y="28" >#[cxx::bridge] mod</text> + <text x="250" y="44" >description of boundary</text> + <line x1="220" y1="120" x2="220" y2="160" class="solid"></line> + <polygon points="216,156 224,156 220,164" class="filled"></polygon> + <line x1="476" y1="120" x2="476" y2="160" class="solid"></line> + <polygon points="472,156 480,156 476,164" class="filled"></polygon> + <text x="74" y="140" >Safe</text> + <text x="34" y="156" >straightforward</text> + <text x="530" y="156" >Straightforward</text> + <polygon points="48,180 40,184 48,188" class="filled"></polygon> + <line x1="48" y1="184" x2="144" y2="184" class="solid"></line> + <polygon points="136,180 144,184 136,188" class="filled"></polygon> + <polygon points="296,180 288,184 296,188" class="filled"></polygon> + <line x1="296" y1="184" x2="408" y2="184" class="broken"></line> + <polygon points="400,180 408,184 400,188" class="filled"></polygon> + <polygon points="552,180 544,184 552,188" class="filled"></polygon> + <line x1="552" y1="184" x2="648" y2="184" class="solid"></line> + <polygon points="644,180 652,184 644,188" class="filled"></polygon> + <text x="2" y="188" >Rust</text> + <text x="2" y="204" >code</text> + <text x="658" y="204" >code</text> + <text x="202" y="108" >Macro expansion</text> + <text x="370" y="108" >Code generation</text> + <text x="58" y="172" >Rust APIs</text> + <text x="298" y="172" >Hidden C ABI</text> + <text x="562" y="172" >C++ APIs</text> + <text x="658" y="188" >C++</text> + <g> + <path d="M 228,8 A 8,8 0,0,0 220,16" class="nofill"></path> + <line x1="228" y1="8" x2="468" y2="8" class="solid"></line> + <path d="M 468,8 A 8,8 0,0,1 476,16" class="nofill"></path> + <line x1="220" y1="16" x2="220" y2="48" class="solid"></line> + <line x1="476" y1="16" x2="476" y2="48" class="solid"></line> + <path d="M 220,48 A 8,8 0,0,0 228,56" class="nofill"></path> + <line x1="228" y1="56" x2="468" y2="56" class="solid"></line> + <line x1="348" y1="56" x2="348" y2="120" class="solid"></line> + <path d="M 476,48 A 8,8 0,0,1 468,56" class="nofill"></path> + <line x1="220" y1="120" x2="476" y2="120" class="solid"></line> + </g> +</svg> diff --git a/book/src/reference.md b/book/src/reference.md new file mode 100644 index 00000000..62ca35c5 --- /dev/null +++ b/book/src/reference.md @@ -0,0 +1,29 @@ +{{#title The bridge module — Rust ♡ C++}} +# The bridge module reference + +The ***[Core concepts](concepts.md)*** in chapter 2 covered the high level model +that CXX uses to represent a language boundary. This chapter builds on that one +to document an exhaustive reference on the syntax and functionality of +\#\[cxx::bridge\]. + +- ***[extern "Rust"](extern-rust.md)*** — exposing opaque Rust types, Rust + functions, Rust methods to C++; functions with lifetimes. + +- ***[extern "C++"](extern-c++.md)*** — binding opaque C++ types, C++ + functions, C++ member functions; sharing an opaque type definition across + multiple bridge modules or different crates; using bindgen-generated data + structures across a CXX bridge; Rust orphan-rule-compatible way to request + that particular glue code be emitted in a specific bridge module. + +- ***[Shared types](shared.md)*** — shared structs; shared enums; using + Rust as source of truth vs C++ as source of truth. + +- ***[Attributes](attributes.md)*** — working with namespaces; giving + functions a different name in their non-native language. + +- ***[Async functions](async.md)*** — integrating async C++ with async + Rust. + +- ***[Error handling](binding/result.md)*** — representing fallibility on + the language boundary; accessing a Rust error message from C++; customizing + the set of caught exceptions and their conversion to a Rust error message. diff --git a/book/src/shared.md b/book/src/shared.md new file mode 100644 index 00000000..4043db12 --- /dev/null +++ b/book/src/shared.md @@ -0,0 +1,246 @@ +{{#title Shared types — Rust ♡ C++}} +# Shared types + +Shared types enable *both* languages to have visibility into the internals of a +type. This is in contrast to opaque Rust types and opaque C++ types, for which +only one side gets to manipulate the internals. + +Unlike opaque types, the FFI bridge is allowed to pass and return shared types +by value. + +The order in which shared types are written is not important. C++ is order +sensitive but CXX will topologically sort and forward-declare your types as +necessary. + +## Shared structs and enums + +For enums, only C-like a.k.a. unit variants are currently supported. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + struct PlayingCard { + suit: Suit, + value: u8, // A=1, J=11, Q=12, K=13 + } + + enum Suit { + Clubs, + Diamonds, + Hearts, + Spades, + } + + unsafe extern "C++" { + fn deck() -> Vec<PlayingCard>; + fn sort(cards: &mut Vec<PlayingCard>); + } +} +``` + +## The generated data structures + +Shared structs compile to an aggregate-initialization compatible C++ struct. + +Shared enums compile to a C++ `enum class` with a sufficiently sized integral +base type decided by CXX. + +```cpp +// generated header + +struct PlayingCard final { + Suit suit; + uint8_t value; +}; + +enum class Suit : uint8_t { + Clubs = 0, + Diamonds = 1, + Hearts = 2, + Spades = 3, +}; +``` + +Because it is not UB in C++ for an `enum class` to hold a value different from +all of the listed variants, we use a Rust representation for shared enums that +is compatible with this. The API you'll get is something like: + +```rust,noplayground +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct Suit { + pub repr: u8, +} +#[allow(non_upper_case_globals)] +impl Suit { + pub const Clubs: Self = Suit { repr: 0 }; + pub const Diamonds: Self = Suit { repr: 1 }; + pub const Hearts: Self = Suit { repr: 2 }; + pub const Spades: Self = Suit { repr: 3 }; +} +``` + +Notice you're free to treat the enum as an integer in Rust code via the public +`repr` field. + +Pattern matching with `match` still works but will require you to write wildcard +arms to handle the situation of an enum value that is not one of the listed +variants. + +```rust,noplayground +fn main() { + let suit: Suit = /*...*/; + match suit { + Suit::Clubs => ..., + Suit::Diamonds => ..., + Suit::Hearts => ..., + Suit::Spades => ..., + _ => ..., // fallback arm + } +} +``` + +If a shared struct has generic lifetime parameters, the lifetimes are simply not +represented on the C++ side. C++ code will need care when working with borrowed +data (as usual in C++). + +```rust,noplayground +#[cxx::bridge] +mod ffi { + struct Borrowed<'a> { + flags: &'a [&'a str], + } +} +``` + +```cpp +// generated header + +struct Borrowed final { + rust::Slice<const rust::Str> flags; +}; +``` + +## Enum discriminants + +You may provide explicit discriminants for some or all of the enum variants, in +which case those numbers will be propagated into the generated C++ `enum class`. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + enum SmallPrime { + Two = 2, + Three = 3, + Five = 5, + Seven = 7, + } +} +``` + +Variants without an explicit discriminant are assigned the previous discriminant +plus 1. If the first variant has not been given an explicit discriminant, it is +assigned discriminant 0. + +By default CXX represents your enum using the smallest integer type capable of +fitting all the discriminants (whether explicit or implicit). If you need a +different representation for reasons, provide a `repr` attribute. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + #[repr(i32)] + enum Enum { + Zero, + One, + Five = 5, + Six, + } +} +``` + +```cpp +// generated header + +enum class Enum : int32_t { + Zero = 0, + One = 1, + Five = 5, + Six = 6, +}; +``` + +## Extern enums + +If you need to interoperate with an already existing enum for which an existing +C++ definition is the source of truth, make sure that definition is provided by +some header in the bridge and then declare your enum *additionally* as an extern +C++ type. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + enum Enum { + Yes, + No, + } + + extern "C++" { + include!("path/to/the/header.h"); + type Enum; + } +} +``` + +CXX will recognize this pattern and, instead of generating a C++ definition of +the enum, will instead generate C++ static assertions asserting that the +variants and discriminant values and integer representation written in Rust all +correctly match the existing C++ enum definition. + +Extern enums support all the same features as ordinary shared enums (explicit +discriminants, repr). Again, CXX will static assert that all of those things you +wrote are correct. + +## Derives + +The following standard traits are supported in `derive(...)` within the CXX +bridge module. + +- `Clone` +- `Copy` +- `Debug` +- `Default` +- `Eq` +- `Hash` +- `Ord` +- `PartialEq` +- `PartialOrd` + +Note that shared enums automatically always come with impls of `Copy`, `Clone`, +`Eq`, and `PartialEq`, so you're free to omit those derives on an enum. + +```rust,noplayground +#[cxx::bridge] +mod ffi { + #[derive(Clone, Debug, Hash)] + struct ExampleStruct { + x: u32, + s: String, + } + + #[derive(Hash, Ord, PartialOrd)] + enum ExampleEnum { + Yes, + No, + } +} +``` + +The derives naturally apply to *both* the Rust data type *and* the corresponding +C++ data type: + +- `Hash` gives you a specialization of [`template <> struct std::hash<T>`][hash] in C++ +- `PartialEq` produces `operator==` and `operator!=` +- `PartialOrd` produces `operator<`, `operator<=`, `operator>`, `operator>=` + +[hash]: https://en.cppreference.com/w/cpp/utility/hash diff --git a/book/src/tutorial.md b/book/src/tutorial.md new file mode 100644 index 00000000..c9fa00cb --- /dev/null +++ b/book/src/tutorial.md @@ -0,0 +1,687 @@ +{{#title Tutorial — Rust ♡ C++}} +# Tutorial: CXX blobstore client + +This example walks through a Rust application that calls into a C++ client of a +blobstore service. In fact we'll see calls going in both directions: Rust to C++ +as well as C++ to Rust. For your own use case it may be that you need just one +of these directions. + +All of the code involved in the example is shown on this page, but it's also +provided in runnable form in the *demo* directory of +<https://github.com/dtolnay/cxx>. To try it out directly, run `cargo run` from +that directory. + +This tutorial assumes you've read briefly about **shared structs**, **opaque +types**, and **functions** in the [*Core concepts*](concepts.md) page. + +## Creating the project + +We'll use Cargo, which is the build system commonly used by open source Rust +projects. (CXX works with other build systems too; refer to chapter 5.) + +Create a blank Cargo project: `mkdir cxx-demo`; `cd cxx-demo`; `cargo init`. + +Edit the Cargo.toml to add a dependency on the `cxx` crate: + +```toml,hidelines +## Cargo.toml +# [package] +# name = "cxx-demo" +# version = "0.1.0" +# edition = "2018" + +[dependencies] +cxx = "1.0" +``` + +We'll revisit this Cargo.toml later when we get to compiling some C++ code. + +## Defining the language boundary + +CXX relies on a description of the function signatures that will be exposed from +each language to the other. You provide this description using `extern` blocks +in a Rust module annotated with the `#[cxx::bridge]` attribute macro. + +We'll open with just the following at the top of src/main.rs and walk through +each item in detail. + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + +} +# +# fn main() {} +``` + +The contents of this module will be everything that needs to be agreed upon by +both sides of the FFI boundary. + +## Calling a C++ function from Rust + +Let's obtain an instance of the C++ blobstore client, a class `BlobstoreClient` +defined in C++. + +We'll treat `BlobstoreClient` as an *opaque type* in CXX's classification so +that Rust does not need to assume anything about its implementation, not even +its size or alignment. In general, a C++ type might have a move-constructor +which is incompatible with Rust's move semantics, or may hold internal +references which cannot be modeled by Rust's borrowing system. Though there are +alternatives, the easiest way to not care about any such thing on an FFI +boundary is to require no knowledge about a type by treating it as opaque. + +Opaque types may only be manipulated behind an indirection such as a reference +`&`, a Rust `Box`, or a `UniquePtr` (Rust binding of `std::unique_ptr`). We'll +add a function through which C++ can return a `std::unique_ptr<BlobstoreClient>` +to Rust. + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + include!("cxx-demo/include/blobstore.h"); + + type BlobstoreClient; + + fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; + } +} + +fn main() { + let client = ffi::new_blobstore_client(); +} +``` + +The nature of `unsafe` extern blocks is clarified in more detail in the +[*extern "C++"*](extern-c++.md) chapter. In brief: the programmer is **not** +promising that the signatures they have typed in are accurate; that would be +unreasonable. CXX performs static assertions that the signatures exactly match +what is declared in C++. Rather, the programmer is only on the hook for things +that C++'s semantics are not precise enough to capture, i.e. things that would +only be represented at most by comments in the C++ code. In this case, it's +whether `new_blobstore_client` is safe or unsafe to call. If that function said +something like "must be called at most once or we'll stomp yer memery", Rust +would instead want to expose it as `unsafe fn new_blobstore_client`, this time +inside a safe `extern "C++"` block because the programmer is no longer on the +hook for any safety claim about the signature. + +If you build this file right now with `cargo build`, it won't build because we +haven't written a C++ implementation of `new_blobstore_client` nor instructed +Cargo about how to link it into the resulting binary. You'll see an error from +the linker like this: + +```console +error: linking with `cc` failed: exit code: 1 + | + = /bin/ld: target/debug/deps/cxx-demo-7cb7fddf3d67d880.rcgu.o: in function `cxx_demo::ffi::new_blobstore_client': + src/main.rs:1: undefined reference to `cxxbridge1$new_blobstore_client' + collect2: error: ld returned 1 exit status +``` + +## Adding in the C++ code + +In CXX's integration with Cargo, all #include paths begin with a crate name by +default (when not explicitly selected otherwise by a crate; see +`CFG.include_prefix` in chapter 5). That's why we see +`include!("cxx-demo/include/blobstore.h")` above — we'll be putting the +C++ header at relative path `include/blobstore.h` within the Rust crate. If your +crate is named something other than `cxx-demo` according to the `name` field in +Cargo.toml, you will need to use that name everywhere in place of `cxx-demo` +throughout this tutorial. + +```cpp +// include/blobstore.h + +#pragma once +#include <memory> + +class BlobstoreClient { +public: + BlobstoreClient(); +}; + +std::unique_ptr<BlobstoreClient> new_blobstore_client(); +``` + +```cpp +// src/blobstore.cc + +#include "cxx-demo/include/blobstore.h" + +BlobstoreClient::BlobstoreClient() {} + +std::unique_ptr<BlobstoreClient> new_blobstore_client() { + return std::unique_ptr<BlobstoreClient>(new BlobstoreClient()); +} +``` + +Using `std::make_unique` would work too, as long as you pass `-std=c++14` to the +C++ compiler as described later on. + +The placement in *include/* and *src/* is not significant; you can place C++ +code anywhere else in the crate as long as you use the right paths throughout +the tutorial. + +Be aware that *CXX does not look at any of these files.* You're free to put +arbitrary C++ code in here, #include your own libraries, etc. All we do is emit +static assertions against what you provide in the headers. + +## Compiling the C++ code with Cargo + +Cargo has a [build scripts] feature suitable for compiling non-Rust code. + +We need to introduce a new build-time dependency on CXX's C++ code generator in +Cargo.toml: + +```toml,hidelines +## Cargo.toml +# [package] +# name = "cxx-demo" +# version = "0.1.0" +# edition = "2018" + +[dependencies] +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" +``` + +Then add a build.rs build script adjacent to Cargo.toml to run the cxx-build +code generator and C++ compiler. The relevant arguments are the path to the Rust +source file containing the cxx::bridge language boundary definition, and the +paths to any additional C++ source files to be compiled during the Rust crate's +build. + +```rust,noplayground +// build.rs + +fn main() { + cxx_build::bridge("src/main.rs") + .file("src/blobstore.cc") + .compile("cxx-demo"); +} +``` + +This build.rs would also be where you set up C++ compiler flags, for example if +you'd like to have access to `std::make_unique` from C++14. See the page on +***[Cargo-based builds](build/cargo.md)*** for more details about CXX's Cargo +integration. + +```rust,noplayground +# // build.rs +# +# fn main() { + cxx_build::bridge("src/main.rs") + .file("src/blobstore.cc") + .flag_if_supported("-std=c++14") + .compile("cxx-demo"); +# } +``` + +[build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html + +The project should now build and run successfully, though not do anything useful +yet. + +```console +cxx-demo$ cargo run + Compiling cxx-demo v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.34s + Running `target/debug/cxx-demo` + +cxx-demo$ +``` + +## Calling a Rust function from C++ + +Our C++ 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 pieces, or fragments of a file spread across memory for +some other reason (like a rope data structure). + +We'll express this by handing off an iterator over contiguous borrowed chunks. +This loosely resembles the API of the widely used `bytes` crate's `Buf` trait. +During a `put`, we'll make C++ call back into Rust to obtain contiguous chunks +of the upload (all with no copying or allocation on the language boundary). In +reality the C++ client might contain some sophisticated batching of chunks +and/or parallel uploading that all of this ties into. + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + extern "Rust" { + type MultiBuf; + + fn next_chunk(buf: &mut MultiBuf) -> &[u8]; + } + + unsafe extern "C++" { + include!("cxx-demo/include/blobstore.h"); + + type BlobstoreClient; + + fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; + fn put(&self, parts: &mut MultiBuf) -> u64; + } +} +# +# fn main() { +# let client = ffi::new_blobstore_client(); +# } +``` + +Any signature having a `self` parameter (the Rust name for C++'s `this`) is +considered a method / non-static member function. If there is only one `type` in +the surrounding extern block, it'll be a method of that type. If there is more +than one `type`, you can disambiguate which one a method belongs to by writing +`self: &BlobstoreClient` in the argument list. + +As usual, now we need to provide Rust definitions of everything declared by the +`extern "Rust"` block and a C++ definition of the new signature declared by the +`extern "C++"` block. + +```rust,noplayground +// src/main.rs +# +# #[cxx::bridge] +# mod ffi { +# extern "Rust" { +# type MultiBuf; +# +# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; +# } +# +# unsafe extern "C++" { +# include!("cxx-demo/include/blobstore.h"); +# +# type BlobstoreClient; +# +# fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; +# fn put(&self, parts: &mut MultiBuf) -> u64; +# } +# } + +// 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_or(&[], Vec::as_slice) +} +# +# fn main() { +# let client = ffi::new_blobstore_client(); +# } +``` + +```cpp,hidelines +// include/blobstore.h + +# #pragma once +# #include <memory> +# +struct MultiBuf; + +class BlobstoreClient { +public: + BlobstoreClient(); + uint64_t put(MultiBuf &buf) const; +}; +# +#std::unique_ptr<BlobstoreClient> new_blobstore_client(); +``` + +In blobstore.cc we're able to call the Rust `next_chunk` function, exposed to +C++ by a header `main.rs.h` generated by the CXX code generator. In CXX's Cargo +integration this generated header has a path containing the crate name, the +relative path of the Rust source file within the crate, and a `.rs.h` extension. + +```cpp,hidelines +// src/blobstore.cc + +##include "cxx-demo/include/blobstore.h" +##include "cxx-demo/src/main.rs.h" +##include <functional> +# +# BlobstoreClient::BlobstoreClient() {} +# +# std::unique_ptr<BlobstoreClient> new_blobstore_client() { +# return std::make_unique<BlobstoreClient>(); +# } + +// Upload a new blob and return a blobid that serves as a handle to the blob. +uint64_t BlobstoreClient::put(MultiBuf &buf) const { + // Traverse the caller's chunk iterator. + std::string contents; + while (true) { + auto chunk = next_chunk(buf); + if (chunk.size() == 0) { + break; + } + contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size()); + } + + // Pretend we did something useful to persist the data. + auto blobid = std::hash<std::string>{}(contents); + return blobid; +} +``` + +This is now ready to use. :) + +```rust,noplayground +// src/main.rs +# +# #[cxx::bridge] +# mod ffi { +# extern "Rust" { +# type MultiBuf; +# +# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; +# } +# +# unsafe extern "C++" { +# include!("cxx-demo/include/blobstore.h"); +# +# type BlobstoreClient; +# +# fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; +# fn put(&self, parts: &mut MultiBuf) -> u64; +# } +# } +# +# 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_or(&[], Vec::as_slice) +# } + +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); +} +``` + +```console +cxx-demo$ cargo run + Compiling cxx-demo v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.41s + Running `target/debug/cxx-demo` + +blobid = 9851996977040795552 +``` + +## Interlude: What gets generated? + +For the curious, it's easy to look behind the scenes at what CXX has done to +make these function calls work. You shouldn't need to do this during normal +usage of CXX, but for the purpose of this tutorial it can be educative. + +CXX comprises *two* code generators: a Rust one (which is the cxx::bridge +attribute procedural macro) and a C++ one. + +### Rust generated code + +It's easiest to view the output of the procedural macro by installing +[cargo-expand]. Then run `cargo expand ::ffi` to macro-expand the `mod ffi` +module. + +[cargo-expand]: https://github.com/dtolnay/cargo-expand + +```console +cxx-demo$ cargo install cargo-expand +cxx-demo$ cargo expand ::ffi +``` + +You'll see some deeply unpleasant code involving `#[repr(C)]`, `#[link_name]`, +and `#[export_name]`. + +### C++ generated code + +For debugging convenience, `cxx_build` links all generated C++ code into Cargo's +target directory under *target/cxxbridge/*. + +```console +cxx-demo$ exa -T target/cxxbridge/ +target/cxxbridge +├── cxx-demo +│ └── src +│ ├── main.rs.cc -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/sources/cxx-demo/src/main.rs.cc +│ └── main.rs.h -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/include/cxx-demo/src/main.rs.h +└── rust + └── cxx.h -> ~/.cargo/registry/src/github.com-1ecc6299db9ec823/cxx-1.0.0/include/cxx.h +``` + +In those files you'll see declarations or templates of any CXX Rust types +present in your language boundary (like `rust::Slice<T>` for `&[T]`) and `extern +"C"` signatures corresponding to your extern functions. + +If it fits your workflow better, the CXX C++ code generator is also available as +a standalone executable which outputs generated code to stdout. + +```console +cxx-demo$ cargo install cxxbridge-cmd +cxx-demo$ cxxbridge src/main.rs +``` + +## Shared data structures + +So far the calls in both directions above only used **opaque types**, not +**shared structs**. + +Shared structs are data structures whose complete definition is visible to both +languages, making it possible to pass them by value across the language +boundary. Shared structs translate to a C++ aggregate-initialization compatible +struct exactly matching the layout of the Rust one. + +As the last step of this demo, we'll use a shared struct `BlobMetadata` to pass +metadata about blobs between our Rust application and C++ blobstore client. + +```rust,noplayground +// src/main.rs + +#[cxx::bridge] +mod ffi { + struct BlobMetadata { + size: usize, + tags: Vec<String>, + } + + extern "Rust" { + // ... +# type MultiBuf; +# +# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; + } + + unsafe extern "C++" { + // ... +# include!("cxx-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; + } +} +# +# 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_or(&[], Vec::as_slice) +# } + +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); +} +``` + +```cpp,hidelines +// include/blobstore.h +# +# #pragma once +# #include "rust/cxx.h" +# #include <memory> + +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(); +``` + +```cpp,hidelines +// src/blobstore.cc + +##include "cxx-demo/include/blobstore.h" +##include "cxx-demo/src/main.rs.h" +##include <algorithm> +##include <functional> +##include <set> +##include <string> +##include <unordered_map> + +// 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 class 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 { +# // Traverse the caller's chunk iterator. +# std::string contents; +# 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.cbegin(), blob->second.tags.cend(), + [&](auto &t) { metadata.tags.emplace_back(t); }); + } + return metadata; +} +# +# std::unique_ptr<BlobstoreClient> new_blobstore_client() { +# return std::make_unique<BlobstoreClient>(); +# } +``` + +```console +cxx-demo$ cargo run + Running `target/debug/cxx-demo` + +blobid = 9851996977040795552 +tags = ["rust"] +``` + +*You've now seen all the code involved in the tutorial. It's available all +together in runnable form in the* demo *directory of +<https://github.com/dtolnay/cxx>. You can run it directly without stepping +through the steps above by running `cargo run` from that directory.* + +<br> + +# Takeaways + +The key contribution of CXX is it gives you Rust–C++ interop in which +*all* of the Rust side of the code you write *really* looks like you are just +writing normal Rust, and the C++ side *really* looks like you are just writing +normal C++. + +You've seen in this tutorial that none of the code involved feels like C or like +the usual perilous "FFI glue" prone to leaks or memory safety flaws. + +An expressive system of opaque types, shared types, and key standard library +type bindings enables API design on the language boundary that captures the +proper ownership and borrowing contracts of the interface. + +CXX plays to the strengths of the Rust type system *and* C++ type system *and* +the programmer's intuitions. An individual working on the C++ side without a +Rust background, or the Rust side without a C++ background, will be able to +apply all their usual intuitions and best practices about development in their +language to maintain a correct FFI. + +<br><br> @@ -1,5 +1,6 @@ use std::env; use std::path::Path; +use std::process::Command; fn main() { cc::Build::new() @@ -7,12 +8,42 @@ fn main() { .cpp(true) .cpp_link_stdlib(None) // linked via link-cplusplus crate .flag_if_supported(cxxbridge_flags::STD) - .compile("cxxbridge05"); + .warnings_into_errors(cfg!(deny_warnings)) + .compile("cxxbridge1"); + 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()); } + + if let Some(rustc) = rustc_version() { + if rustc.minor < 48 { + println!("cargo:warning=The cxx crate requires a rustc version 1.48.0 or newer."); + println!( + "cargo:warning=You appear to be building with: {}", + rustc.version, + ); + } + } +} + +struct RustVersion { + version: String, + minor: u32, +} + +fn rustc_version() -> Option<RustVersion> { + let rustc = env::var_os("RUSTC")?; + let output = Command::new(rustc).arg("--version").output().ok()?; + let version = String::from_utf8(output.stdout).ok()?; + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + let minor = pieces.next()?.parse().ok()?; + Some(RustVersion { version, minor }) } diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 00000000..c24e3b5e --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1 @@ +-std=c++11 diff --git a/demo/Cargo.toml b/demo/Cargo.toml index dc948610..8115b1e4 100644 --- a/demo/Cargo.toml +++ b/demo/Cargo.toml @@ -2,11 +2,14 @@ name = "demo" version = "0.0.0" authors = ["David Tolnay <dtolnay@gmail.com>"] +license = "MIT OR Apache-2.0" +description = "Toy project from https://github.com/dtolnay/cxx" +repository = "https://github.com/dtolnay/cxx" edition = "2018" publish = false [dependencies] -cxx = { path = ".." } +cxx = "1.0" [build-dependencies] -cxx-build = { path = "../gen/build" } +cxx-build = "1.0" diff --git a/demo/include/blobstore.h b/demo/include/blobstore.h index 68a7fc2b..d89583aa 100644 --- a/demo/include/blobstore.h +++ b/demo/include/blobstore.h @@ -16,8 +16,8 @@ public: BlobMetadata metadata(uint64_t blobid) const; private: - class Impl; - std::shared_ptr<Impl> impl; + class impl; + std::shared_ptr<impl> impl; }; std::unique_ptr<BlobstoreClient> new_blobstore_client(); diff --git a/demo/src/blobstore.cc b/demo/src/blobstore.cc index 0036a327..7cf40dfe 100644 --- a/demo/src/blobstore.cc +++ b/demo/src/blobstore.cc @@ -13,7 +13,7 @@ namespace blobstore { // // In reality the implementation of BlobstoreClient could be a large complex C++ // library. -class BlobstoreClient::Impl { +class BlobstoreClient::impl { friend BlobstoreClient; using Blob = struct { std::string data; @@ -22,7 +22,7 @@ class BlobstoreClient::Impl { std::unordered_map<uint64_t, Blob> blobs; }; -BlobstoreClient::BlobstoreClient() : impl(new BlobstoreClient::Impl) {} +BlobstoreClient::BlobstoreClient() : impl(new class 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 { @@ -57,7 +57,7 @@ BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const { 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(), + std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(), [&](auto &t) { metadata.tags.emplace_back(t); }); } return metadata; diff --git a/demo/src/main.rs b/demo/src/main.rs index 10f57e5b..458f1f21 100644 --- a/demo/src/main.rs +++ b/demo/src/main.rs @@ -14,7 +14,7 @@ mod ffi { } // C++ types and signatures exposed to Rust. - extern "C++" { + unsafe extern "C++" { include!("demo/include/blobstore.h"); type BlobstoreClient; @@ -38,7 +38,7 @@ pub struct MultiBuf { 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(&[]) + next.map_or(&[], Vec::as_slice) } fn main() { diff --git a/flags/Cargo.toml b/flags/Cargo.toml index cc5f2302..87c796cb 100644 --- a/flags/Cargo.toml +++ b/flags/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cxxbridge-flags" -version = "0.5.9" +version = "1.0.42" authors = ["David Tolnay <dtolnay@gmail.com>"] edition = "2018" license = "MIT OR Apache-2.0" diff --git a/flags/LICENSE-APACHE b/flags/LICENSE-APACHE new file mode 120000 index 00000000..965b606f --- /dev/null +++ b/flags/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE
\ No newline at end of file diff --git a/flags/LICENSE-MIT b/flags/LICENSE-MIT new file mode 120000 index 00000000..76219eb7 --- /dev/null +++ b/flags/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT
\ No newline at end of file diff --git a/gen/build/Cargo.toml b/gen/build/Cargo.toml index 3f7b0eac..33f17dff 100644 --- a/gen/build/Cargo.toml +++ b/gen/build/Cargo.toml @@ -1,26 +1,30 @@ [package] name = "cxx-build" -version = "0.5.9" +version = "1.0.42" 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" +homepage = "https://cxx.rs" exclude = ["build.rs"] keywords = ["ffi"] categories = ["development-tools::ffi"] +[features] +parallel = ["cc/parallel"] + [dependencies] cc = "1.0.49" -codespan-reporting = "0.9" +codespan-reporting = "0.11" lazy_static = "1.4" -proc-macro2 = { version = "1.0.17", default-features = false, features = ["span-locations"] } +proc-macro2 = { version = "1.0.26", 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"] } +syn = { version = "1.0.68", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] } [dev-dependencies] -cxx-gen = { version = "0.6", path = "../lib" } +cxx-gen = { version = "0.7", path = "../lib" } pkg-config = "0.3" [package.metadata.docs.rs] diff --git a/gen/build/src/cfg.rs b/gen/build/src/cfg.rs index c15a1737..6818bdaa 100644 --- a/gen/build/src/cfg.rs +++ b/gen/build/src/cfg.rs @@ -266,10 +266,10 @@ pub use self::r#impl::Cfg::CFG; #[cfg(not(doc))] mod r#impl { use crate::intern::{intern, InternedString}; + use crate::syntax::map::UnorderedMap as Map; 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}; @@ -316,7 +316,7 @@ mod r#impl { // cfg: AtomicPtr<super::Cfg>, // } // - static CONST_DEREFS: RefCell<HashMap<Handle, Box<super::Cfg<'static>>>> = RefCell::default(); + static CONST_DEREFS: RefCell<Map<Handle, Box<super::Cfg<'static>>>> = RefCell::default(); } #[derive(Eq, PartialEq, Hash)] diff --git a/gen/build/src/intern.rs b/gen/build/src/intern.rs index 51bc07cd..25b87061 100644 --- a/gen/build/src/intern.rs +++ b/gen/build/src/intern.rs @@ -1,5 +1,5 @@ +use crate::syntax::set::UnorderedSet as Set; use lazy_static::lazy_static; -use std::collections::HashSet; use std::sync::{Mutex, PoisonError}; #[derive(Copy, Clone, Default)] @@ -13,7 +13,7 @@ impl InternedString { pub fn intern(s: &str) -> InternedString { lazy_static! { - static ref INTERN: Mutex<HashSet<&'static str>> = Mutex::new(HashSet::new()); + static ref INTERN: Mutex<Set<&'static str>> = Mutex::new(Set::new()); } let mut set = INTERN.lock().unwrap_or_else(PoisonError::into_inner); diff --git a/gen/build/src/lib.rs b/gen/build/src/lib.rs index db15335a..63b2cf63 100644 --- a/gen/build/src/lib.rs +++ b/gen/build/src/lib.rs @@ -46,12 +46,35 @@ //! ``` #![allow( + clippy::cast_sign_loss, + clippy::default_trait_access, + clippy::doc_markdown, clippy::drop_copy, + clippy::enum_glob_use, clippy::inherent_to_string, + clippy::items_after_statements, + clippy::let_underscore_drop, + clippy::match_bool, + clippy::match_on_vec_items, + clippy::match_same_arms, + clippy::module_name_repetitions, clippy::needless_doctest_main, + clippy::needless_pass_by_value, clippy::new_without_default, + clippy::nonminimal_bool, + clippy::option_if_let_else, clippy::or_fun_call, - clippy::toplevel_ref_arg + clippy::redundant_else, + clippy::shadow_unrelated, + clippy::similar_names, + clippy::single_match_else, + clippy::struct_excessive_bools, + clippy::too_many_arguments, + clippy::too_many_lines, + clippy::toplevel_ref_arg, + clippy::upper_case_acronyms, + // clippy bug: https://github.com/rust-lang/rust-clippy/issues/6983 + clippy::wrong_self_convention )] mod cfg; @@ -70,10 +93,10 @@ use crate::error::{Error, Result}; use crate::gen::error::report; use crate::gen::Opt; use crate::paths::PathExt; +use crate::syntax::map::{Entry, UnorderedMap}; use crate::target::TargetDir; use cc::Build; -use std::collections::btree_map::Entry; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::env; use std::ffi::{OsStr, OsString}; use std::io::{self, Write}; @@ -186,7 +209,6 @@ fn build(rust_source_files: &mut dyn Iterator<Item = impl AsRef<Path>>) -> Resul 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); @@ -196,6 +218,7 @@ fn build(rust_source_files: &mut dyn Iterator<Item = impl AsRef<Path>>) -> Resul generate_bridge(prj, &mut build, path.as_ref())?; } + this_crate.print_to_cargo(); eprintln!("\nCXX include path:"); for header_dir in this_crate.header_dirs { build.include(&header_dir.path); @@ -256,12 +279,10 @@ fn make_this_crate(prj: &Project) -> Result<Crate> { path: include_dir, }); - if let Some(crate_dir) = crate_dir { - this_crate.header_dirs.push(HeaderDir { - exported: true, - path: 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 { @@ -270,7 +291,7 @@ fn make_this_crate(prj: &Project) -> Result<Crate> { }); } - let mut header_dirs_index = BTreeMap::new(); + let mut header_dirs_index = UnorderedMap::new(); let mut used_header_links = BTreeSet::new(); let mut used_header_prefixes = BTreeSet::new(); for krate in deps::direct_dependencies() { @@ -335,24 +356,30 @@ fn make_this_crate(prj: &Project) -> Result<Crate> { Ok(this_crate) } -fn make_crate_dir(prj: &Project) -> Option<PathBuf> { +fn make_crate_dir(prj: &Project) -> PathBuf { if prj.include_prefix.as_os_str().is_empty() { - return Some(prj.manifest_dir.clone()); + return 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 + let ref link = crate_dir.join(&prj.include_prefix); + let ref manifest_dir = prj.manifest_dir; + if out::symlink_dir(manifest_dir, link).is_err() && cfg!(not(unix)) { + let cachedir_tag = "\ + Signature: 8a477f597d28d172789f06886806bc55\n\ + # This file is a cache directory tag created by cxx.\n\ + # For information about cache directory tags see https://bford.info/cachedir/\n"; + let _ = out::write(crate_dir.join("CACHEDIR.TAG"), cachedir_tag.as_bytes()); + let max_depth = 6; + best_effort_copy_headers(manifest_dir, link, max_depth); } + crate_dir } 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") { + if let Some(ref original) = env::var_os("DEP_CXXBRIDGE1_HEADER") { out::symlink_file(original, cxx_h)?; out::symlink_file(original, shared_cxx_h)?; } else { @@ -393,6 +420,49 @@ fn generate_bridge(prj: &Project, build: &mut Build, rust_source_file: &Path) -> Ok(()) } +fn best_effort_copy_headers(src: &Path, dst: &Path, max_depth: usize) { + // Not using crate::gen::fs because we aren't reporting the errors. + use std::fs; + + let mut dst_created = false; + let mut entries = match fs::read_dir(src) { + Ok(entries) => entries, + Err(_) => return, + }; + + while let Some(Ok(entry)) = entries.next() { + let file_name = entry.file_name(); + if file_name.to_string_lossy().starts_with('.') { + continue; + } + match entry.file_type() { + Ok(file_type) if file_type.is_dir() && max_depth > 0 => { + let src = entry.path(); + if src.join("Cargo.toml").exists() || src.join("CACHEDIR.TAG").exists() { + continue; + } + let dst = dst.join(file_name); + best_effort_copy_headers(&src, &dst, max_depth - 1); + } + Ok(file_type) if file_type.is_file() => { + let src = entry.path(); + match src.extension().and_then(OsStr::to_str) { + Some("h") | Some("hh") | Some("hpp") => {} + _ => continue, + } + if !dst_created && fs::create_dir_all(dst).is_err() { + return; + } + dst_created = true; + let dst = dst.join(file_name); + let _ = fs::remove_file(&dst); + let _ = fs::copy(src, dst); + } + _ => {} + } + } +} + 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 index b97e9924..e275049c 100644 --- a/gen/build/src/out.rs +++ b/gen/build/src/out.rs @@ -14,7 +14,7 @@ pub(crate) fn write(path: impl AsRef<Path>, content: &[u8]) -> Result<()> { return Ok(()); } } - let _ = fs::remove_file(path); + best_effort_remove(path); } else { let parent = path.parent().unwrap(); create_dir_error = fs::create_dir_all(parent).err(); @@ -28,19 +28,19 @@ pub(crate) fn write(path: impl AsRef<Path>, content: &[u8]) -> Result<()> { } } -pub(crate) fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> { - let src = src.as_ref(); - let dst = dst.as_ref(); +pub(crate) fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> { + let original = original.as_ref(); + let link = link.as_ref(); let mut create_dir_error = None; - if dst.exists() { - let _ = fs::remove_file(dst).unwrap(); + if link.exists() { + best_effort_remove(link); } else { - let parent = dst.parent().unwrap(); + let parent = link.parent().unwrap(); create_dir_error = fs::create_dir_all(parent).err(); } - match paths::symlink_or_copy(src, dst) { + match paths::symlink_or_copy(original, link) { // 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. @@ -48,22 +48,47 @@ pub(crate) fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Resu } } -pub(crate) fn symlink_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> { - let src = src.as_ref(); - let dst = dst.as_ref(); +pub(crate) fn symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> { + let original = original.as_ref(); + let link = link.as_ref(); let mut create_dir_error = None; - if dst.exists() { - let _ = paths::remove_symlink_dir(dst).unwrap(); + if link.exists() { + best_effort_remove(link); } else { - let parent = dst.parent().unwrap(); + let parent = link.parent().unwrap(); create_dir_error = fs::create_dir_all(parent).err(); } - match paths::symlink_dir(src, dst) { + match fs::symlink_dir(original, link) { // 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))), } } + +fn best_effort_remove(path: &Path) { + use std::fs; + + let file_type = match if cfg!(windows) { + // On Windows, the correct choice of remove_file vs remove_dir needs to + // be used according to what the symlink *points to*. Trying to use + // remove_file to remove a symlink which points to a directory fails + // with "Access is denied". + fs::metadata(path) + } else { + // On non-Windows, we check metadata not following symlinks. All + // symlinks are removed using remove_file. + fs::symlink_metadata(path) + } { + Ok(metadata) => metadata.file_type(), + Err(_) => return, + }; + + if file_type.is_dir() { + let _ = fs::remove_dir_all(path); + } else { + let _ = fs::remove_file(path); + } +} diff --git a/gen/build/src/paths.rs b/gen/build/src/paths.rs index 4459363c..c514a570 100644 --- a/gen/build/src/paths.rs +++ b/gen/build/src/paths.rs @@ -43,32 +43,25 @@ impl PathExt for Path { pub(crate) use self::fs::symlink_file as symlink_or_copy; #[cfg(windows)] -pub(crate) fn symlink_or_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> fs::Result<()> { +pub(crate) fn symlink_or_copy( + original: impl AsRef<Path>, + link: 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. - let src = src.as_ref(); - let dst = dst.as_ref(); - if fs::symlink_file(src, dst).is_err() { - fs::copy(src, dst)?; + let original = original.as_ref(); + let link = link.as_ref(); + if fs::symlink_file(original, link).is_err() { + fs::copy(original, link)?; } Ok(()) } #[cfg(not(any(unix, windows)))] -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<()> { +pub(crate) fn symlink_or_copy( + original: impl AsRef<Path>, + copy: impl AsRef<Path>, +) -> fs::Result<()> { + fs::copy(original, copy)?; 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 index 58ada3a7..4c9a9f3d 100644 --- a/gen/build/src/target.rs +++ b/gen/build/src/target.rs @@ -1,4 +1,5 @@ use std::env; +use std::ffi::OsStr; use std::path::{Path, PathBuf}; pub(crate) enum TargetDir { @@ -24,7 +25,13 @@ pub(crate) fn find_target_dir(out_dir: &Path) -> TargetDir { let mut dir = out_dir.to_owned(); loop { - if dir.join(".rustc_info.json").exists() || dir.join("CACHEDIR.TAG").exists() { + if dir.join(".rustc_info.json").exists() + || dir.join("CACHEDIR.TAG").exists() + || dir.file_name() == Some(OsStr::new("target")) + && dir + .parent() + .map_or(false, |parent| parent.join("Cargo.toml").exists()) + { return TargetDir::Path(dir); } if dir.pop() { diff --git a/gen/cmd/Cargo.toml b/gen/cmd/Cargo.toml index f3bdebf8..7db02062 100644 --- a/gen/cmd/Cargo.toml +++ b/gen/cmd/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "cxxbridge-cmd" -version = "0.5.9" +version = "1.0.42" 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" +homepage = "https://cxx.rs" exclude = ["build.rs"] keywords = ["ffi"] categories = ["development-tools::ffi"] @@ -16,10 +17,10 @@ path = "src/main.rs" [dependencies] clap = "2.33" -codespan-reporting = "0.9" -proc-macro2 = { version = "1.0.17", default-features = false, features = ["span-locations"] } +codespan-reporting = "0.11" +proc-macro2 = { version = "1.0.26", default-features = false, features = ["span-locations"] } quote = { version = "1.0", default-features = false } -syn = { version = "1.0.20", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] } +syn = { version = "1.0.68", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/gen/cmd/src/app.rs b/gen/cmd/src/app.rs index e2945a1d..c233ad77 100644 --- a/gen/cmd/src/app.rs +++ b/gen/cmd/src/app.rs @@ -94,8 +94,8 @@ pub(super) fn from_args() -> Opt { Opt { input, - cxx_impl_annotations, header, + cxx_impl_annotations, include, outputs, } diff --git a/gen/cmd/src/main.rs b/gen/cmd/src/main.rs index c7230393..f419dad4 100644 --- a/gen/cmd/src/main.rs +++ b/gen/cmd/src/main.rs @@ -1,10 +1,30 @@ #![allow( + clippy::cast_sign_loss, clippy::cognitive_complexity, + clippy::default_trait_access, + clippy::enum_glob_use, clippy::inherent_to_string, + clippy::items_after_statements, clippy::large_enum_variant, + clippy::match_bool, + clippy::match_on_vec_items, + clippy::match_same_arms, + clippy::module_name_repetitions, + clippy::needless_pass_by_value, clippy::new_without_default, + clippy::nonminimal_bool, + clippy::option_if_let_else, clippy::or_fun_call, - clippy::toplevel_ref_arg + clippy::redundant_else, + clippy::shadow_unrelated, + clippy::similar_names, + clippy::single_match_else, + clippy::struct_excessive_bools, + clippy::too_many_arguments, + clippy::too_many_lines, + clippy::toplevel_ref_arg, + // clippy bug: https://github.com/rust-lang/rust-clippy/issues/6983 + clippy::wrong_self_convention )] mod app; @@ -51,7 +71,11 @@ fn try_main() -> Result<()> { for output in opt.outputs { let kind = if opt.input.is_none() { Kind::Header - } else if opt.header || output.ends_with(".h") { + } else if opt.header + || output.ends_with(".h") + || output.ends_with(".hh") + || output.ends_with(".hpp") + { gen_header = true; Kind::GeneratedHeader } else { diff --git a/gen/lib/Cargo.toml b/gen/lib/Cargo.toml index 2de0f28c..ff33d979 100644 --- a/gen/lib/Cargo.toml +++ b/gen/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cxx-gen" -version = "0.6.6" +version = "0.7.42" authors = ["Adrian Taylor <adetaylor@chromium.org>"] edition = "2018" license = "MIT OR Apache-2.0" @@ -12,10 +12,10 @@ 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"] } +codespan-reporting = "0.11" +proc-macro2 = { version = "1.0.26", default-features = false, features = ["span-locations"] } quote = { version = "1.0", default-features = false } -syn = { version = "1.0.20", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] } +syn = { version = "1.0.68", default-features = false, features = ["parsing", "printing", "clone-impls", "full"] } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/gen/lib/src/lib.rs b/gen/lib/src/lib.rs index 963e8707..e3eca5e7 100644 --- a/gen/lib/src/lib.rs +++ b/gen/lib/src/lib.rs @@ -9,10 +9,31 @@ #![allow(dead_code)] #![allow( + clippy::cast_sign_loss, + clippy::default_trait_access, + clippy::enum_glob_use, clippy::inherent_to_string, + clippy::items_after_statements, + clippy::match_bool, + clippy::match_on_vec_items, + clippy::match_same_arms, + clippy::missing_errors_doc, + clippy::module_name_repetitions, + clippy::needless_pass_by_value, clippy::new_without_default, + clippy::nonminimal_bool, + clippy::option_if_let_else, clippy::or_fun_call, - clippy::toplevel_ref_arg + clippy::redundant_else, + clippy::shadow_unrelated, + clippy::similar_names, + clippy::single_match_else, + clippy::struct_excessive_bools, + clippy::too_many_arguments, + clippy::too_many_lines, + clippy::toplevel_ref_arg, + // clippy bug: https://github.com/rust-lang/rust-clippy/issues/6983 + clippy::wrong_self_convention )] mod error; diff --git a/gen/lib/tests/test.rs b/gen/lib/tests/test.rs index 73a25f33..d035b522 100644 --- a/gen/lib/tests/test.rs +++ b/gen/lib/tests/test.rs @@ -6,7 +6,7 @@ fn test_positive() { let rs = quote! { #[cxx::bridge] mod ffi { - extern "C" { + unsafe extern "C++" { fn in_C(); } extern "Rust" { diff --git a/gen/src/builtin.rs b/gen/src/builtin.rs index 41d037a8..eaaa08d1 100644 --- a/gen/src/builtin.rs +++ b/gen/src/builtin.rs @@ -12,12 +12,16 @@ pub struct Builtins<'a> { pub rust_vec: bool, pub rust_fn: bool, pub rust_isize: bool, + pub opaque: bool, + pub layout: bool, pub unsafe_bitcopy: bool, + pub unsafe_bitcopy_t: bool, pub rust_error: bool, pub manually_drop: bool, pub maybe_uninit: bool, pub trycatch: bool, pub ptr_len: bool, + pub repr_fat: bool, pub rust_str_new_unchecked: bool, pub rust_str_repr: bool, pub rust_slice_new: bool, @@ -25,6 +29,8 @@ pub struct Builtins<'a> { pub exception: bool, pub relocatable: bool, pub friend_impl: bool, + pub is_complete: bool, + pub deleter_if: bool, pub content: Content<'a>, } @@ -50,28 +56,46 @@ pub(super) fn write(out: &mut OutFile) { } if builtin.rust_str { + include.array = true; include.cstdint = true; include.string = true; builtin.friend_impl = true; } - if builtin.rust_slice { - builtin.friend_impl = true; - } - - if builtin.rust_box { + if builtin.rust_vec { + include.algorithm = true; + include.array = true; + include.cassert = true; + include.cstddef = true; + include.cstdint = true; + include.initializer_list = true; + include.iterator = true; include.new = true; + include.stdexcept = true; include.type_traits = true; include.utility = true; + builtin.panic = true; + builtin.rust_slice = true; + builtin.unsafe_bitcopy_t = true; } - if builtin.rust_vec { + if builtin.rust_slice { include.array = true; + include.cassert = true; + include.cstddef = true; + include.cstdint = true; + include.iterator = true; + include.stdexcept = true; + include.type_traits = true; + builtin.friend_impl = true; + builtin.layout = true; + builtin.panic = true; + } + + if builtin.rust_box { include.new = true; include.type_traits = true; include.utility = true; - builtin.panic = true; - builtin.unsafe_bitcopy = true; } if builtin.rust_fn { @@ -85,40 +109,122 @@ pub(super) fn write(out: &mut OutFile) { if builtin.rust_isize { include.basetsd = true; + include.sys_types = true; } if builtin.relocatable { include.type_traits = true; } + if builtin.layout { + include.type_traits = true; + include.cstddef = true; + builtin.is_complete = true; + } + + if builtin.is_complete { + include.cstddef = true; + include.type_traits = true; + } + + if builtin.unsafe_bitcopy { + builtin.unsafe_bitcopy_t = true; + } + out.begin_block(Block::Namespace("rust")); - out.begin_block(Block::InlineNamespace("cxxbridge05")); - writeln!(out, "// #include \"rust/cxx.h\""); + out.begin_block(Block::InlineNamespace("cxxbridge1")); - ifndef::write(out, builtin.panic, "CXXBRIDGE05_PANIC"); + let cxx_header = include.has_cxx_header(); + if !cxx_header { + writeln!(out, "// #include \"rust/cxx.h\""); + + ifndef::write(out, builtin.panic, "CXXBRIDGE1_PANIC"); + + if builtin.rust_string { + out.next_section(); + writeln!(out, "struct unsafe_bitcopy_t;"); + } + + if builtin.friend_impl { + out.begin_block(Block::AnonymousNamespace); + writeln!(out, "template <typename T>"); + writeln!(out, "class impl;"); + out.end_block(Block::AnonymousNamespace); + } - if builtin.rust_string { out.next_section(); - writeln!(out, "struct unsafe_bitcopy_t;"); + if builtin.rust_str && !builtin.rust_string { + writeln!(out, "class String;"); + } + if builtin.layout && !builtin.opaque { + writeln!(out, "class Opaque;"); + } + + if builtin.rust_slice { + out.next_section(); + writeln!(out, "template <typename T>"); + writeln!(out, "::std::size_t size_of();"); + writeln!(out, "template <typename T>"); + writeln!(out, "::std::size_t align_of();"); + } + + ifndef::write(out, builtin.rust_string, "CXXBRIDGE1_RUST_STRING"); + ifndef::write(out, builtin.rust_str, "CXXBRIDGE1_RUST_STR"); + ifndef::write(out, builtin.rust_slice, "CXXBRIDGE1_RUST_SLICE"); + ifndef::write(out, builtin.rust_box, "CXXBRIDGE1_RUST_BOX"); + ifndef::write(out, builtin.unsafe_bitcopy_t, "CXXBRIDGE1_RUST_BITCOPY_T"); + ifndef::write(out, builtin.unsafe_bitcopy, "CXXBRIDGE1_RUST_BITCOPY"); + ifndef::write(out, builtin.rust_vec, "CXXBRIDGE1_RUST_VEC"); + ifndef::write(out, builtin.rust_fn, "CXXBRIDGE1_RUST_FN"); + ifndef::write(out, builtin.rust_error, "CXXBRIDGE1_RUST_ERROR"); + ifndef::write(out, builtin.rust_isize, "CXXBRIDGE1_RUST_ISIZE"); + ifndef::write(out, builtin.opaque, "CXXBRIDGE1_RUST_OPAQUE"); + ifndef::write(out, builtin.is_complete, "CXXBRIDGE1_IS_COMPLETE"); + ifndef::write(out, builtin.layout, "CXXBRIDGE1_LAYOUT"); + ifndef::write(out, builtin.relocatable, "CXXBRIDGE1_RELOCATABLE"); + } + + if builtin.rust_str_new_unchecked { + out.next_section(); + writeln!(out, "class Str::uninit {{}};"); + writeln!(out, "inline Str::Str(uninit) noexcept {{}}"); + } + + if builtin.rust_slice_new { + out.next_section(); + writeln!(out, "template <typename T>"); + writeln!(out, "class Slice<T>::uninit {{}};"); + writeln!(out, "template <typename T>"); + writeln!(out, "inline Slice<T>::Slice(uninit) noexcept {{}}"); } - if builtin.friend_impl { - out.begin_block(Block::AnonymousNamespace); + out.begin_block(Block::Namespace("detail")); + + if builtin.maybe_uninit { + include.cstddef = true; + include.new = true; + out.next_section(); + writeln!(out, "template <typename T, typename = void *>"); + writeln!(out, "struct operator_new {{"); + writeln!( + out, + " void *operator()(::std::size_t sz) {{ return ::operator new(sz); }}", + ); + writeln!(out, "}};"); + out.next_section(); writeln!(out, "template <typename T>"); - writeln!(out, "class impl;"); - out.end_block(Block::AnonymousNamespace); + writeln!( + out, + "struct operator_new<T, decltype(T::operator new(sizeof(T)))> {{", + ); + writeln!( + out, + " void *operator()(::std::size_t sz) {{ return T::operator new(sz); }}", + ); + writeln!(out, "}};"); } - 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"); + out.end_block(Block::Namespace("detail")); if builtin.manually_drop { out.next_section(); @@ -135,10 +241,15 @@ pub(super) fn write(out: &mut OutFile) { } if builtin.maybe_uninit { + include.cstddef = true; out.next_section(); writeln!(out, "template <typename T>"); writeln!(out, "union MaybeUninit {{"); writeln!(out, " T value;"); + writeln!( + out, + " void *operator new(::std::size_t sz) {{ return detail::operator_new<T>{{}}(sz); }}", + ); writeln!(out, " MaybeUninit() {{}}"); writeln!(out, " ~MaybeUninit() {{}}"); writeln!(out, "}};"); @@ -146,11 +257,22 @@ pub(super) fn write(out: &mut OutFile) { out.begin_block(Block::AnonymousNamespace); + if builtin.repr_fat { + include.array = true; + include.cstdint = true; + out.next_section(); + out.begin_block(Block::Namespace("repr")); + writeln!(out, "using Fat = ::std::array<::std::uintptr_t, 2>;"); + out.end_block(Block::Namespace("repr")); + } + if builtin.ptr_len { + include.cstddef = true; + out.next_section(); out.begin_block(Block::Namespace("repr")); writeln!(out, "struct PtrLen final {{"); - writeln!(out, " const void *ptr;"); - writeln!(out, " size_t len;"); + writeln!(out, " void *ptr;"); + writeln!(out, " ::std::size_t len;"); writeln!(out, "}};"); out.end_block(Block::Namespace("repr")); } @@ -163,17 +285,16 @@ pub(super) fn write(out: &mut OutFile) { if builtin.rust_str_new_unchecked { writeln!( out, - " static Str new_unchecked(repr::PtrLen repr) noexcept {{", + " static Str new_unchecked(repr::Fat repr) noexcept {{", ); - writeln!(out, " Str str;"); - writeln!(out, " str.ptr = static_cast<const char *>(repr.ptr);"); - writeln!(out, " str.len = repr.len;"); + writeln!(out, " Str str = Str::uninit{{}};"); + writeln!(out, " str.repr = repr;"); writeln!(out, " return str;"); writeln!(out, " }}"); } if builtin.rust_str_repr { - writeln!(out, " static repr::PtrLen repr(Str str) noexcept {{"); - writeln!(out, " return repr::PtrLen{{str.ptr, str.len}};"); + writeln!(out, " static repr::Fat repr(Str str) noexcept {{"); + writeln!(out, " return str.repr;"); writeln!(out, " }}"); } writeln!(out, "}};"); @@ -185,22 +306,15 @@ pub(super) fn write(out: &mut OutFile) { 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, " static Slice<T> slice(repr::Fat repr) noexcept {{"); + writeln!(out, " Slice<T> slice = typename Slice<T>::uninit{{}};"); + writeln!(out, " slice.repr = repr;"); + writeln!(out, " return slice;"); writeln!(out, " }}"); } if builtin.rust_slice_repr { - writeln!( - out, - " static repr::PtrLen repr(Slice<T> slice) noexcept {{", - ); - writeln!(out, " return repr::PtrLen{{slice.ptr, slice.len}};"); + writeln!(out, " static repr::Fat repr(Slice<T> slice) noexcept {{"); + writeln!(out, " return slice.repr;"); writeln!(out, " }}"); } writeln!(out, "}};"); @@ -220,8 +334,22 @@ pub(super) fn write(out: &mut OutFile) { writeln!(out, "}};"); } + if builtin.deleter_if { + out.next_section(); + writeln!(out, "template <bool> struct deleter_if {{"); + writeln!(out, " template <typename T> void operator()(T *) {{}}"); + writeln!(out, "}};"); + out.next_section(); + writeln!(out, "template <> struct deleter_if<true> {{"); + writeln!( + out, + " template <typename T> void operator()(T *ptr) {{ ptr->~T(); }}", + ); + writeln!(out, "}};"); + } + out.end_block(Block::AnonymousNamespace); - out.end_block(Block::InlineNamespace("cxxbridge05")); + out.end_block(Block::InlineNamespace("cxxbridge1")); if builtin.trycatch { out.begin_block(Block::Namespace("behavior")); @@ -249,10 +377,11 @@ pub(super) fn write(out: &mut OutFile) { out.end_block(Block::Namespace("rust")); if builtin.exception { + include.cstddef = true; out.begin_block(Block::ExternC); writeln!( out, - "const char *cxxbridge05$exception(const char *, size_t);", + "const char *cxxbridge1$exception(const char *, ::std::size_t);", ); out.end_block(Block::ExternC); } diff --git a/gen/src/error.rs b/gen/src/error.rs index 2335d43e..2c8287f3 100644 --- a/gen/src/error.rs +++ b/gen/src/error.rs @@ -68,6 +68,13 @@ pub(super) fn format_err(path: &Path, source: &str, error: Error) -> ! { display_syn_error(stderr, path, source, error); } } + Error::NoBridgeMod => { + let _ = writeln!( + io::stderr(), + "cxxbridge: no #[cxx::bridge] module found in {}", + path.display(), + ); + } _ => { let _ = writeln!(io::stderr(), "cxxbridge: {}", report(error)); } diff --git a/gen/src/fs.rs b/gen/src/fs.rs index 8f94f005..7053cc44 100644 --- a/gen/src/fs.rs +++ b/gen/src/fs.rs @@ -9,7 +9,7 @@ pub(crate) type Result<T> = std::result::Result<T, Error>; #[derive(Debug)] pub(crate) struct Error { - source: io::Error, + source: Option<io::Error>, message: String, } @@ -21,14 +21,15 @@ impl Display for Error { impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { - Some(&self.source) + let source = self.source.as_ref()?; + Some(source) } } macro_rules! err { ($io_error:expr, $fmt:expr $(, $path:expr)* $(,)?) => { Err(Error { - source: $io_error, + source: Option::from($io_error), message: format!($fmt $(, $path.display())*), }) } @@ -91,44 +92,57 @@ pub(crate) fn remove_dir(path: impl AsRef<Path>) -> Result<()> { } fn symlink<'a>( - src: &'a Path, - dst: &'a Path, + original: &'a Path, + link: &'a Path, fun: fn(&'a Path, &'a Path) -> io::Result<()>, ) -> Result<()> { - match fun(src, dst) { + match fun(original, link) { Ok(()) => Ok(()), Err(e) => err!( e, "Failed to create symlink `{}` pointing to `{}`", - dst, - src, + link, + original, ), } } +pub(crate) fn symlink_fail(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> { + err!( + None, + "Failed to create symlink `{}` pointing to `{}`", + link.as_ref(), + original.as_ref(), + ) +} + #[cfg(unix)] #[allow(unused_imports)] pub(crate) use self::symlink_file as symlink_dir; +#[cfg(not(any(unix, windows)))] +#[allow(unused_imports)] +pub(crate) use self::symlink_fail as symlink_dir; + #[cfg(unix)] -pub(crate) fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> { - symlink(src.as_ref(), dst.as_ref(), std::os::unix::fs::symlink) +pub(crate) fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> { + symlink(original.as_ref(), link.as_ref(), std::os::unix::fs::symlink) } #[cfg(windows)] -pub(crate) fn symlink_file(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> { +pub(crate) fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> { symlink( - src.as_ref(), - dst.as_ref(), + original.as_ref(), + link.as_ref(), std::os::windows::fs::symlink_file, ) } #[cfg(windows)] -pub(crate) fn symlink_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> { +pub(crate) fn symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> { symlink( - src.as_ref(), - dst.as_ref(), + original.as_ref(), + link.as_ref(), std::os::windows::fs::symlink_dir, ) } diff --git a/gen/src/include.rs b/gen/src/include.rs index cf718dab..62c92320 100644 --- a/gen/src/include.rs +++ b/gen/src/include.rs @@ -21,18 +21,25 @@ pub struct Include { #[derive(Default, PartialEq)] pub struct Includes<'a> { pub custom: Vec<Include>, + pub algorithm: bool, pub array: bool, + pub cassert: bool, pub cstddef: bool, pub cstdint: bool, pub cstring: bool, pub exception: bool, + pub functional: bool, + pub initializer_list: bool, + pub iterator: bool, pub memory: bool, pub new: bool, + pub stdexcept: bool, pub string: bool, pub type_traits: bool, pub utility: bool, pub vector: bool, pub basetsd: bool, + pub sys_types: bool, pub content: Content<'a>, } @@ -44,11 +51,18 @@ impl<'a> Includes<'a> { pub fn insert(&mut self, include: impl Into<Include>) { self.custom.push(include.into()); } + + pub fn has_cxx_header(&self) -> bool { + self.custom + .iter() + .any(|header| header.path == "rust/cxx.h" || header.path == "rust\\cxx.h") + } } pub(super) fn write(out: &mut OutFile) { let header = out.header; let include = &mut out.include; + let cxx_header = include.has_cxx_header(); let out = &mut include.content; if header { @@ -66,42 +80,96 @@ pub(super) fn write(out: &mut OutFile) { } } - if include.array { + let Includes { + custom: _, + algorithm, + array, + cassert, + cstddef, + cstdint, + cstring, + exception, + functional, + initializer_list, + iterator, + memory, + new, + stdexcept, + string, + type_traits, + utility, + vector, + basetsd, + sys_types, + content: _, + } = *include; + + if algorithm && !cxx_header { + writeln!(out, "#include <algorithm>"); + } + if array && !cxx_header { writeln!(out, "#include <array>"); } - if include.cstddef { + if cassert && !cxx_header { + writeln!(out, "#include <cassert>"); + } + if cstddef && !cxx_header { writeln!(out, "#include <cstddef>"); } - if include.cstdint { + if cstdint && !cxx_header { writeln!(out, "#include <cstdint>"); } - if include.cstring { + if cstring { writeln!(out, "#include <cstring>"); } - if include.exception { + if exception && !cxx_header { writeln!(out, "#include <exception>"); } - if include.memory { + if functional { + writeln!(out, "#include <functional>"); + } + if initializer_list && !cxx_header { + writeln!(out, "#include <initializer_list>"); + } + if iterator && !cxx_header { + writeln!(out, "#include <iterator>"); + } + if memory { writeln!(out, "#include <memory>"); } - if include.new { + if new && !cxx_header { writeln!(out, "#include <new>"); } - if include.string { + if stdexcept && !cxx_header { + writeln!(out, "#include <stdexcept>"); + } + if string && !cxx_header { writeln!(out, "#include <string>"); } - if include.type_traits { + if type_traits && !cxx_header { writeln!(out, "#include <type_traits>"); } - if include.utility { + if utility && !cxx_header { writeln!(out, "#include <utility>"); } - if include.vector { + if vector && !cxx_header { writeln!(out, "#include <vector>"); } - if include.basetsd { + if basetsd && !cxx_header { writeln!(out, "#if defined(_WIN32)"); writeln!(out, "#include <basetsd.h>"); + } + if sys_types && !cxx_header { + if basetsd { + writeln!(out, "#else"); + } else { + writeln!(out, "#if not defined(_WIN32)"); + } + } + if sys_types && !cxx_header { + writeln!(out, "#include <sys/types.h>"); + } + if (basetsd || sys_types) && !cxx_header { writeln!(out, "#endif"); } } diff --git a/gen/src/nested.rs b/gen/src/nested.rs index 22b0c9f6..2129d10f 100644 --- a/gen/src/nested.rs +++ b/gen/src/nested.rs @@ -1,6 +1,6 @@ +use crate::syntax::map::UnorderedMap as Map; use crate::syntax::Api; use proc_macro2::Ident; -use std::collections::HashMap as Map; pub struct NamespaceEntries<'a> { direct: Vec<&'a Api>, @@ -51,10 +51,12 @@ fn sort_by_inner_namespace(apis: Vec<&Api>, depth: usize) -> NamespaceEntries { #[cfg(test)] mod tests { use super::NamespaceEntries; + use crate::syntax::attrs::OtherAttrs; use crate::syntax::namespace::Namespace; - use crate::syntax::{Api, Doc, ExternType, Pair}; + use crate::syntax::{Api, Doc, ExternType, ForeignName, Lang, Lifetimes, Pair}; use proc_macro2::{Ident, Span}; use std::iter::FromIterator; + use syn::punctuated::Punctuated; use syn::Token; #[test] @@ -117,7 +119,7 @@ mod tests { fn assert_ident(api: &Api, expected: &str) { if let Api::CxxType(cxx_type) = api { - assert_eq!(cxx_type.name.cxx, expected); + assert_eq!(cxx_type.name.cxx.to_string(), expected); } else { unreachable!() } @@ -126,9 +128,24 @@ mod tests { fn make_api(ns: Option<&str>, ident: &str) -> Api { let ns = ns.map_or(Namespace::ROOT, |ns| syn::parse_str(ns).unwrap()); Api::CxxType(ExternType { + lang: Lang::Rust, doc: Doc::new(), + derives: Vec::new(), + attrs: OtherAttrs::none(), + visibility: Token![pub](Span::call_site()), type_token: Token![type](Span::call_site()), - name: Pair::new(ns, Ident::new(ident, Span::call_site())), + name: Pair { + namespace: ns, + cxx: ForeignName::parse(ident, Span::call_site()).unwrap(), + rust: Ident::new(ident, Span::call_site()), + }, + generics: Lifetimes { + lt_token: None, + lifetimes: Punctuated::new(), + gt_token: None, + }, + colon_token: None, + bounds: Vec::new(), semi_token: Token![;](Span::call_site()), trusted: false, }) diff --git a/gen/src/out.rs b/gen/src/out.rs index bf880cc8..3b4d7392 100644 --- a/gen/src/out.rs +++ b/gen/src/out.rs @@ -102,7 +102,7 @@ impl<'a> Write for Content<'a> { } impl<'a> PartialEq for Content<'a> { - fn eq(&self, _other: &Content) -> bool { + fn eq(&self, _other: &Self) -> bool { true } } diff --git a/gen/src/write.rs b/gen/src/write.rs index 5fe8ee30..9f9c0391 100644 --- a/gen/src/write.rs +++ b/gen/src/write.rs @@ -3,13 +3,16 @@ use crate::gen::nested::NamespaceEntries; use crate::gen::out::OutFile; use crate::gen::{builtin, include, Opt}; use crate::syntax::atom::Atom::{self, *}; +use crate::syntax::instantiate::{ImplKey, NamedImplKey}; +use crate::syntax::map::UnorderedMap as Map; +use crate::syntax::set::UnorderedSet; use crate::syntax::symbol::Symbol; +use crate::syntax::trivial::{self, TrivialReason}; use crate::syntax::{ - mangle, Api, Enum, ExternFn, ExternType, Pair, ResolvableName, Signature, Struct, Type, Types, - Var, + derive, mangle, Api, Enum, ExternFn, ExternType, Pair, Signature, Struct, Trait, Type, + TypeAlias, Types, Var, }; use proc_macro2::Ident; -use std::collections::{HashMap, HashSet}; pub(super) fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec<u8> { let mut out_file = OutFile::new(header, opt, types); @@ -47,10 +50,10 @@ fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) { for api in apis { write!(out, "{:1$}", "", indent); match api { - Api::Struct(strct) => write_struct_decl(out, &strct.name.cxx), + Api::Struct(strct) => write_struct_decl(out, &strct.name), Api::Enum(enm) => write_enum_decl(out, enm), Api::CxxType(ety) => write_struct_using(out, &ety.name), - Api::RustType(ety) => write_struct_decl(out, &ety.name.cxx), + Api::RustType(ety) => write_struct_decl(out, &ety.name), _ => unreachable!(), } } @@ -64,7 +67,7 @@ fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) { } fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) { - let mut methods_for_type = HashMap::new(); + let mut methods_for_type = Map::new(); for api in apis { if let Api::CxxFunction(efn) | Api::RustFunction(efn) = api { if let Some(receiver) = &efn.sig.receiver { @@ -76,7 +79,7 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) { } } - let mut structs_written = HashSet::new(); + let mut structs_written = UnorderedSet::new(); let mut toposorted_structs = out.types.toposorted_structs.iter(); for api in apis { match api { @@ -105,20 +108,28 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) { } } Api::RustType(ety) => { - if let Some(methods) = methods_for_type.get(&ety.name.rust) { - out.next_section(); - write_struct_with_methods(out, ety, methods); - } + out.next_section(); + let methods = methods_for_type + .get(&ety.name.rust) + .map(Vec::as_slice) + .unwrap_or_default(); + write_opaque_type(out, ety, methods); } _ => {} } } + if out.header { + return; + } + + out.set_namespace(Default::default()); + out.next_section(); for api in apis { if let Api::TypeAlias(ety) = api { - if out.types.required_trivial.contains_key(&ety.name.rust) { - check_trivial_extern_type(out, &ety.name) + if let Some(reasons) = out.types.required_trivial.get(&ety.name.rust) { + check_trivial_extern_type(out, ety, reasons) } } } @@ -128,19 +139,60 @@ fn write_functions<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) { if !out.header { for api in apis { match api { + Api::Struct(strct) => write_struct_operator_decls(out, strct), + Api::RustType(ety) => write_opaque_type_layout_decls(out, ety), Api::CxxFunction(efn) => write_cxx_function_shim(out, efn), Api::RustFunction(efn) => write_rust_function_decl(out, efn), _ => {} } } + + write_std_specializations(out, apis); + } + + for api in apis { + match api { + Api::Struct(strct) => write_struct_operators(out, strct), + Api::RustType(ety) => write_opaque_type_layout(out, ety), + Api::RustFunction(efn) => { + out.next_section(); + write_rust_function_shim(out, efn); + } + _ => {} + } } +} + +fn write_std_specializations(out: &mut OutFile, apis: &[Api]) { + out.set_namespace(Default::default()); + out.begin_block(Block::Namespace("std")); for api in apis { - if let Api::RustFunction(efn) = api { - out.next_section(); - write_rust_function_shim(out, efn); + if let Api::Struct(strct) = api { + if derive::contains(&strct.derives, Trait::Hash) { + out.next_section(); + out.include.cstddef = true; + out.include.functional = true; + let qualified = strct.name.to_fully_qualified(); + writeln!(out, "template <> struct hash<{}> {{", qualified); + writeln!( + out, + " ::std::size_t operator()(const {} &self) const noexcept {{", + qualified, + ); + let link_name = mangle::operator(&strct.name, "hash"); + write!(out, " return ::"); + for name in &strct.name.namespace { + write!(out, "{}::", name); + } + writeln!(out, "{}(self);", link_name); + writeln!(out, " }}"); + writeln!(out, "}};"); + } } } + + out.end_block(Block::Namespace("std")); } fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) { @@ -159,41 +211,46 @@ fn pick_includes_and_builtins(out: &mut OutFile, apis: &[Api]) { Some(Isize) => out.builtin.rust_isize = true, Some(CxxString) => out.include.string = true, Some(RustString) => out.builtin.rust_string = true, - Some(Bool) | Some(F32) | Some(F64) | None => {} + Some(Bool) | Some(Char) | Some(F32) | Some(F64) | None => {} }, Type::RustBox(_) => out.builtin.rust_box = true, Type::RustVec(_) => out.builtin.rust_vec = true, Type::UniquePtr(_) => out.include.memory = true, + Type::SharedPtr(_) | Type::WeakPtr(_) => out.include.memory = true, Type::Str(_) => out.builtin.rust_str = true, Type::CxxVector(_) => out.include.vector = true, Type::Fn(_) => out.builtin.rust_fn = true, - Type::Slice(_) => out.builtin.rust_slice = true, - Type::SliceRefU8(_) => { - out.include.cstdint = true; - out.builtin.rust_slice = true; - } - Type::Ref(_) | Type::Void(_) => {} + Type::SliceRef(_) => out.builtin.rust_slice = true, + Type::Array(_) => out.include.array = true, + Type::Ref(_) | Type::Void(_) | Type::Ptr(_) => {} } } } fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&ExternFn]) { + let operator_eq = derive::contains(&strct.derives, Trait::PartialEq); + let operator_ord = derive::contains(&strct.derives, Trait::PartialOrd); + out.set_namespace(&strct.name.namespace); - let guard = format!("CXXBRIDGE05_STRUCT_{}", strct.name.to_symbol()); + let guard = format!("CXXBRIDGE1_STRUCT_{}", strct.name.to_symbol()); writeln!(out, "#ifndef {}", guard); writeln!(out, "#define {}", guard); for line in strct.doc.to_string().lines() { writeln!(out, "//{}", line); } writeln!(out, "struct {} final {{", strct.name.cxx); + for field in &strct.fields { + for line in field.doc.to_string().lines() { + writeln!(out, " //{}", line); + } write!(out, " "); write_type_space(out, &field.ty); - writeln!(out, "{};", field.ident); - } - if !methods.is_empty() { - writeln!(out); + writeln!(out, "{};", field.name.cxx); } + + writeln!(out); + for method in methods { write!(out, " "); let sig = &method.sig; @@ -201,12 +258,52 @@ fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&Extern write_rust_function_shim_decl(out, &local_name, sig, false); writeln!(out, ";"); } + + if operator_eq { + writeln!( + out, + " bool operator==(const {} &) const noexcept;", + strct.name.cxx, + ); + writeln!( + out, + " bool operator!=(const {} &) const noexcept;", + strct.name.cxx, + ); + } + + if operator_ord { + writeln!( + out, + " bool operator<(const {} &) const noexcept;", + strct.name.cxx, + ); + writeln!( + out, + " bool operator<=(const {} &) const noexcept;", + strct.name.cxx, + ); + writeln!( + out, + " bool operator>(const {} &) const noexcept;", + strct.name.cxx, + ); + writeln!( + out, + " bool operator>=(const {} &) const noexcept;", + strct.name.cxx, + ); + } + + out.include.type_traits = true; + writeln!(out, " using IsRelocatable = ::std::true_type;"); + writeln!(out, "}};"); writeln!(out, "#endif // {}", guard); } -fn write_struct_decl(out: &mut OutFile, ident: &Ident) { - writeln!(out, "struct {};", ident); +fn write_struct_decl(out: &mut OutFile, ident: &Pair) { + writeln!(out, "struct {};", ident.cxx); } fn write_enum_decl(out: &mut OutFile, enm: &Enum) { @@ -219,25 +316,22 @@ 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], -) { +fn write_opaque_type<'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()); + let guard = format!("CXXBRIDGE1_STRUCT_{}", ety.name.to_symbol()); writeln!(out, "#ifndef {}", guard); writeln!(out, "#define {}", guard); for line in ety.doc.to_string().lines() { writeln!(out, "//{}", line); } - writeln!(out, "struct {} final {{", ety.name.cxx); - writeln!(out, " {}() = delete;", ety.name.cxx); + + out.builtin.opaque = true; writeln!( out, - " {}(const {} &) = delete;", - ety.name.cxx, ety.name.cxx, + "struct {} final : public ::rust::Opaque {{", + ety.name.cxx, ); + for method in methods { write!(out, " "); let sig = &method.sig; @@ -245,13 +339,25 @@ fn write_struct_with_methods<'a>( write_rust_function_shim_decl(out, &local_name, sig, false); writeln!(out, ";"); } + + writeln!(out, " ~{}() = delete;", ety.name.cxx); + writeln!(out); + + out.builtin.layout = true; + out.include.cstddef = true; + writeln!(out, "private:"); + writeln!(out, " friend ::rust::layout;"); + writeln!(out, " struct layout {{"); + writeln!(out, " static ::std::size_t size() noexcept;"); + writeln!(out, " static ::std::size_t align() noexcept;"); + writeln!(out, " }};"); writeln!(out, "}};"); writeln!(out, "#endif // {}", guard); } fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) { out.set_namespace(&enm.name.namespace); - let guard = format!("CXXBRIDGE05_ENUM_{}", enm.name.to_symbol()); + let guard = format!("CXXBRIDGE1_ENUM_{}", enm.name.to_symbol()); writeln!(out, "#ifndef {}", guard); writeln!(out, "#define {}", guard); for line in enm.doc.to_string().lines() { @@ -261,7 +367,10 @@ fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) { write_atom(out, enm.repr); writeln!(out, " {{"); for variant in &enm.variants { - writeln!(out, " {} = {},", variant.ident, variant.discriminant); + for line in variant.doc.to_string().lines() { + writeln!(out, " //{}", line); + } + writeln!(out, " {} = {},", variant.name.cxx, variant.discriminant); } writeln!(out, "}};"); writeln!(out, "#endif // {}", guard); @@ -269,6 +378,12 @@ fn write_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) { fn check_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) { out.set_namespace(&enm.name.namespace); + out.include.type_traits = true; + writeln!( + out, + "static_assert(::std::is_enum<{}>::value, \"expected enum\");", + enm.name.cxx, + ); write!(out, "static_assert(sizeof({}) == sizeof(", enm.name.cxx); write_atom(out, enm.repr); writeln!(out, "), \"incorrect size\");"); @@ -278,12 +393,12 @@ fn check_enum<'a>(out: &mut OutFile<'a>, enm: &'a Enum) { writeln!( out, ">({}::{}) == {}, \"disagrees with the value in #[cxx::bridge]\");", - enm.name.cxx, variant.ident, variant.discriminant, + enm.name.cxx, variant.name.cxx, variant.discriminant, ); } } -fn check_trivial_extern_type(out: &mut OutFile, id: &Pair) { +fn check_trivial_extern_type(out: &mut OutFile, alias: &TypeAlias, reasons: &[TrivialReason]) { // NOTE: The following static assertion is just nice-to-have and not // necessary for soundness. That's because triviality is always declared by // the user in the form of an unsafe impl of cxx::ExternType: @@ -317,24 +432,221 @@ fn check_trivial_extern_type(out: &mut OutFile, id: &Pair) { // + struct rust::IsRelocatable<MyType> : std::true_type {}; // - let id = id.to_fully_qualified(); + let id = alias.name.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, + " \"type {} should be trivially move constructible and trivially destructible in C++ to be used as {} in Rust\");", + id.trim_start_matches("::"), + trivial::as_what(&alias.name, reasons), ); } -fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { - out.next_section(); - out.set_namespace(&efn.name.namespace); +fn write_struct_operator_decls<'a>(out: &mut OutFile<'a>, strct: &'a Struct) { + out.set_namespace(&strct.name.namespace); out.begin_block(Block::ExternC); + + if derive::contains(&strct.derives, Trait::PartialEq) { + let link_name = mangle::operator(&strct.name, "eq"); + writeln!( + out, + "bool {}(const {1} &, const {1} &) noexcept;", + link_name, strct.name.cxx, + ); + + if !derive::contains(&strct.derives, Trait::Eq) { + let link_name = mangle::operator(&strct.name, "ne"); + writeln!( + out, + "bool {}(const {1} &, const {1} &) noexcept;", + link_name, strct.name.cxx, + ); + } + } + + if derive::contains(&strct.derives, Trait::PartialOrd) { + let link_name = mangle::operator(&strct.name, "lt"); + writeln!( + out, + "bool {}(const {1} &, const {1} &) noexcept;", + link_name, strct.name.cxx, + ); + + let link_name = mangle::operator(&strct.name, "le"); + writeln!( + out, + "bool {}(const {1} &, const {1} &) noexcept;", + link_name, strct.name.cxx, + ); + + if !derive::contains(&strct.derives, Trait::Ord) { + let link_name = mangle::operator(&strct.name, "gt"); + writeln!( + out, + "bool {}(const {1} &, const {1} &) noexcept;", + link_name, strct.name.cxx, + ); + + let link_name = mangle::operator(&strct.name, "ge"); + writeln!( + out, + "bool {}(const {1} &, const {1} &) noexcept;", + link_name, strct.name.cxx, + ); + } + } + + if derive::contains(&strct.derives, Trait::Hash) { + out.include.cstddef = true; + let link_name = mangle::operator(&strct.name, "hash"); + writeln!( + out, + "::std::size_t {}(const {} &) noexcept;", + link_name, strct.name.cxx, + ); + } + + out.end_block(Block::ExternC); +} + +fn write_struct_operators<'a>(out: &mut OutFile<'a>, strct: &'a Struct) { + if out.header { + return; + } + + out.set_namespace(&strct.name.namespace); + + if derive::contains(&strct.derives, Trait::PartialEq) { + out.next_section(); + writeln!( + out, + "bool {0}::operator==(const {0} &rhs) const noexcept {{", + strct.name.cxx, + ); + let link_name = mangle::operator(&strct.name, "eq"); + writeln!(out, " return {}(*this, rhs);", link_name); + writeln!(out, "}}"); + + out.next_section(); + writeln!( + out, + "bool {0}::operator!=(const {0} &rhs) const noexcept {{", + strct.name.cxx, + ); + if derive::contains(&strct.derives, Trait::Eq) { + writeln!(out, " return !(*this == rhs);"); + } else { + let link_name = mangle::operator(&strct.name, "ne"); + writeln!(out, " return {}(*this, rhs);", link_name); + } + writeln!(out, "}}"); + } + + if derive::contains(&strct.derives, Trait::PartialOrd) { + out.next_section(); + writeln!( + out, + "bool {0}::operator<(const {0} &rhs) const noexcept {{", + strct.name.cxx, + ); + let link_name = mangle::operator(&strct.name, "lt"); + writeln!(out, " return {}(*this, rhs);", link_name); + writeln!(out, "}}"); + + out.next_section(); + writeln!( + out, + "bool {0}::operator<=(const {0} &rhs) const noexcept {{", + strct.name.cxx, + ); + let link_name = mangle::operator(&strct.name, "le"); + writeln!(out, " return {}(*this, rhs);", link_name); + writeln!(out, "}}"); + + out.next_section(); + writeln!( + out, + "bool {0}::operator>(const {0} &rhs) const noexcept {{", + strct.name.cxx, + ); + if derive::contains(&strct.derives, Trait::Ord) { + writeln!(out, " return !(*this <= rhs);"); + } else { + let link_name = mangle::operator(&strct.name, "gt"); + writeln!(out, " return {}(*this, rhs);", link_name); + } + writeln!(out, "}}"); + + out.next_section(); + writeln!( + out, + "bool {0}::operator>=(const {0} &rhs) const noexcept {{", + strct.name.cxx, + ); + if derive::contains(&strct.derives, Trait::Ord) { + writeln!(out, " return !(*this < rhs);"); + } else { + let link_name = mangle::operator(&strct.name, "ge"); + writeln!(out, " return {}(*this, rhs);", link_name); + } + writeln!(out, "}}"); + } +} + +fn write_opaque_type_layout_decls<'a>(out: &mut OutFile<'a>, ety: &'a ExternType) { + out.set_namespace(&ety.name.namespace); + out.begin_block(Block::ExternC); + + let link_name = mangle::operator(&ety.name, "sizeof"); + writeln!(out, "::std::size_t {}() noexcept;", link_name); + + let link_name = mangle::operator(&ety.name, "alignof"); + writeln!(out, "::std::size_t {}() noexcept;", link_name); + + out.end_block(Block::ExternC); +} + +fn write_opaque_type_layout<'a>(out: &mut OutFile<'a>, ety: &'a ExternType) { + if out.header { + return; + } + + out.set_namespace(&ety.name.namespace); + + out.next_section(); + let link_name = mangle::operator(&ety.name, "sizeof"); + writeln!( + out, + "::std::size_t {}::layout::size() noexcept {{", + ety.name.cxx, + ); + writeln!(out, " return {}();", link_name); + writeln!(out, "}}"); + + out.next_section(); + let link_name = mangle::operator(&ety.name, "alignof"); + writeln!( + out, + "::std::size_t {}::layout::align() noexcept {{", + ety.name.cxx, + ); + writeln!(out, " return {}();", link_name); + writeln!(out, "}}"); +} + +fn begin_function_definition(out: &mut OutFile) { if let Some(annotation) = &out.opt.cxx_impl_annotations { write!(out, "{} ", annotation); } +} + +fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { + out.next_section(); + out.set_namespace(&efn.name.namespace); + out.begin_block(Block::ExternC); + begin_function_definition(out); if efn.throws { out.builtin.ptr_len = true; write!(out, "::rust::repr::PtrLen "); @@ -344,13 +656,13 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { let mangled = mangle::extern_fn(efn, out.types); write!(out, "{}(", mangled); if let Some(receiver) = &efn.receiver { - if receiver.mutability.is_none() { + if !receiver.mutable { write!(out, "const "); } write!( out, "{} &self", - out.types.resolve(&receiver.ty).to_fully_qualified(), + out.types.resolve(&receiver.ty).name.to_fully_qualified(), ); } for (i, arg) in efn.args.iter().enumerate() { @@ -380,7 +692,7 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { Some(receiver) => write!( out, "({}::*{}$)(", - out.types.resolve(&receiver.ty).to_fully_qualified(), + out.types.resolve(&receiver.ty).name.to_fully_qualified(), efn.name.rust, ), } @@ -392,7 +704,7 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { } write!(out, ")"); if let Some(receiver) = &efn.receiver { - if receiver.mutability.is_none() { + if !receiver.mutable { write!(out, " const"); } } @@ -402,7 +714,7 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { Some(receiver) => write!( out, "&{}::{}", - out.types.resolve(&receiver.ty).to_fully_qualified(), + out.types.resolve(&receiver.ty).name.to_fully_qualified(), efn.name.cxx, ), } @@ -430,9 +742,11 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { out.builtin.rust_str_repr = true; write!(out, "::rust::impl<::rust::Str>::repr("); } - Some(Type::SliceRefU8(_)) if !indirect_return => { + Some(ty @ Type::SliceRef(_)) if !indirect_return => { out.builtin.rust_slice_repr = true; - write!(out, "::rust::impl<::rust::Slice<uint8_t>>::repr(") + write!(out, "::rust::impl<"); + write_type(out, ty); + write!(out, ">::repr("); } _ => {} } @@ -446,46 +760,33 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { } if let Type::RustBox(_) = &arg.ty { write_type(out, &arg.ty); - write!(out, "::from_raw({})", arg.ident); + write!(out, "::from_raw({})", arg.name.cxx); } 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, - ); + write!(out, "({})", arg.name.cxx); } else if arg.ty == RustString { out.builtin.unsafe_bitcopy = true; write!( out, "::rust::String(::rust::unsafe_bitcopy, *{})", - arg.ident, + arg.name.cxx, ); } else if let Type::RustVec(_) = arg.ty { out.builtin.unsafe_bitcopy = true; write_type(out, &arg.ty); - write!(out, "(::rust::unsafe_bitcopy, *{})", arg.ident); - } else if let Type::SliceRefU8(_) = arg.ty { - write!( - out, - "::rust::Slice<uint8_t>(static_cast<const uint8_t *>({0}.ptr), {0}.len)", - arg.ident, - ); + write!(out, "(::rust::unsafe_bitcopy, *{})", arg.name.cxx); } else if out.types.needs_indirect_abi(&arg.ty) { out.include.utility = true; - write!(out, "::std::move(*{})", arg.ident); + write!(out, "::std::move(*{})", arg.name.cxx); } else { - write!(out, "{}", arg.ident); + write!(out, "{}", arg.name.cxx); } } write!(out, ")"); match &efn.ret { Some(Type::RustBox(_)) => write!(out, ".into_raw()"), Some(Type::UniquePtr(_)) => write!(out, ".release()"), - Some(Type::Str(_)) | Some(Type::SliceRefU8(_)) if !indirect_return => write!(out, ")"), + Some(Type::Str(_)) | Some(Type::SliceRef(_)) if !indirect_return => write!(out, ")"), _ => {} } if indirect_return { @@ -501,7 +802,7 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { writeln!(out, " throw$.len = ::std::strlen(catch$);"); writeln!( out, - " throw$.ptr = ::cxxbridge05$exception(catch$, throw$.len);", + " throw$.ptr = const_cast<char *>(::cxxbridge1$exception(catch$, throw$.len));", ); writeln!(out, " }});"); writeln!(out, " return throw$;"); @@ -509,19 +810,14 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { writeln!(out, "}}"); for arg in &efn.args { if let Type::Fn(f) = &arg.ty { - let var = &arg.ident; + let var = &arg.name; write_function_pointer_trampoline(out, efn, var, f); } } out.end_block(Block::ExternC); } -fn write_function_pointer_trampoline( - out: &mut OutFile, - efn: &ExternFn, - var: &Ident, - f: &Signature, -) { +fn write_function_pointer_trampoline(out: &mut OutFile, efn: &ExternFn, var: &Pair, f: &Signature) { let r_trampoline = mangle::r_trampoline(efn, var, out.types); let indirect_call = true; write_rust_function_decl_impl(out, &r_trampoline, f, indirect_call); @@ -556,13 +852,13 @@ fn write_rust_function_decl_impl( write!(out, "{}(", link_name); let mut needs_comma = false; if let Some(receiver) = &sig.receiver { - if receiver.mutability.is_none() { + if !receiver.mutable { write!(out, "const "); } write!( out, "{} &self", - out.types.resolve(&receiver.ty).to_fully_qualified(), + out.types.resolve(&receiver.ty).name.to_fully_qualified(), ); needs_comma = true; } @@ -577,7 +873,13 @@ fn write_rust_function_decl_impl( if needs_comma { write!(out, ", "); } - write_return_type(out, &sig.ret); + match sig.ret.as_ref().unwrap() { + Type::Ref(ret) => { + write_pointee_type(out, &ret.inner, ret.mutable); + write!(out, " *"); + } + ret => write_type_space(out, ret), + } write!(out, "*return$"); needs_comma = true; } @@ -597,7 +899,11 @@ fn write_rust_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { } let local_name = match &efn.sig.receiver { None => efn.name.cxx.to_string(), - Some(receiver) => format!("{}::{}", out.types.resolve(&receiver.ty).cxx, efn.name.cxx), + Some(receiver) => format!( + "{}::{}", + out.types.resolve(&receiver.ty).name.cxx, + efn.name.cxx, + ), }; let invoke = mangle::extern_fn(efn, out.types); let indirect_call = false; @@ -610,6 +916,7 @@ fn write_rust_function_shim_decl( sig: &Signature, indirect_call: bool, ) { + begin_function_definition(out); write_return_type(out, &sig.ret); write!(out, "{}(", local_name); for (i, arg) in sig.args.iter().enumerate() { @@ -617,7 +924,7 @@ fn write_rust_function_shim_decl( write!(out, ", "); } write_type_space(out, &arg.ty); - write!(out, "{}", arg.ident); + write!(out, "{}", arg.name.cxx); } if indirect_call { if !sig.args.is_empty() { @@ -627,7 +934,7 @@ fn write_rust_function_shim_decl( } write!(out, ")"); if let Some(receiver) = &sig.receiver { - if receiver.mutability.is_none() { + if !receiver.mutable { write!(out, " const"); } } @@ -659,7 +966,7 @@ fn write_rust_function_shim_impl( out.builtin.manually_drop = true; write!(out, " ::rust::ManuallyDrop<"); write_type(out, &arg.ty); - writeln!(out, "> {}$(::std::move({0}));", arg.ident); + writeln!(out, "> {}$(::std::move({0}));", arg.name.cxx); } } write!(out, " "); @@ -667,7 +974,13 @@ fn write_rust_function_shim_impl( if indirect_return { out.builtin.maybe_uninit = true; write!(out, "::rust::MaybeUninit<"); - write_type(out, sig.ret.as_ref().unwrap()); + match sig.ret.as_ref().unwrap() { + Type::Ref(ret) => { + write_pointee_type(out, &ret.inner, ret.mutable); + write!(out, " *"); + } + ret => write_type(out, ret), + } writeln!(out, "> return$;"); write!(out, " "); } else if let Some(ret) = &sig.ret { @@ -686,9 +999,11 @@ fn write_rust_function_shim_impl( out.builtin.rust_str_new_unchecked = true; write!(out, "::rust::impl<::rust::Str>::new_unchecked("); } - Type::SliceRefU8(_) => { + Type::SliceRef(_) => { out.builtin.rust_slice_new = true; - write!(out, "::rust::impl<::rust::Slice<uint8_t>>::slice("); + write!(out, "::rust::impl<"); + write_type(out, ret); + write!(out, ">::slice("); } _ => {} } @@ -707,23 +1022,13 @@ fn write_rust_function_shim_impl( if needs_comma { write!(out, ", "); } - match &arg.ty { - 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, "&"), - _ => {} + if out.types.needs_indirect_abi(&arg.ty) { + write!(out, "&"); } - write!(out, "{}", arg.ident); + write!(out, "{}", arg.name.cxx); match &arg.ty { Type::RustBox(_) => write!(out, ".into_raw()"), Type::UniquePtr(_) => write!(out, ".release()"), - Type::Str(_) | Type::SliceRefU8(_) => write!(out, ")"), ty if ty != RustString && out.types.needs_indirect_abi(ty) => write!(out, "$.value"), _ => {} } @@ -745,8 +1050,7 @@ fn write_rust_function_shim_impl( write!(out, ")"); if !indirect_return { if let Some(ret) = &sig.ret { - if let Type::RustBox(_) | Type::UniquePtr(_) | Type::Str(_) | Type::SliceRefU8(_) = ret - { + if let Type::RustBox(_) | Type::UniquePtr(_) | Type::Str(_) | Type::SliceRef(_) = ret { write!(out, ")"); } } @@ -759,8 +1063,15 @@ fn write_rust_function_shim_impl( writeln!(out, " }}"); } if indirect_return { - out.include.utility = true; - writeln!(out, " return ::std::move(return$.value);"); + write!(out, " return "); + match sig.ret.as_ref().unwrap() { + Type::Ref(_) => write!(out, "*return$.value"), + _ => { + out.include.utility = true; + write!(out, "::std::move(return$.value)"); + } + } + writeln!(out, ";"); } writeln!(out, "}}"); } @@ -785,7 +1096,7 @@ fn write_indirect_return_type(out: &mut OutFile, ty: &Type) { write!(out, "*"); } Type::Ref(ty) => { - if ty.mutability.is_none() { + if !ty.mutable { write!(out, "const "); } write_type(out, &ty.inner); @@ -799,7 +1110,7 @@ fn write_indirect_return_type_space(out: &mut OutFile, ty: &Type) { write_indirect_return_type(out, ty); match ty { Type::RustBox(_) | Type::UniquePtr(_) | Type::Ref(_) => {} - Type::Str(_) | Type::SliceRefU8(_) => write!(out, " "), + Type::Str(_) | Type::SliceRef(_) => write!(out, " "), _ => write_space_after_type(out, ty), } } @@ -811,15 +1122,15 @@ fn write_extern_return_type_space(out: &mut OutFile, ty: &Option<Type>) { write!(out, "*"); } Some(Type::Ref(ty)) => { - if ty.mutability.is_none() { + if !ty.mutable { write!(out, "const "); } write_type(out, &ty.inner); write!(out, " *"); } - Some(Type::Str(_)) | Some(Type::SliceRefU8(_)) => { - out.builtin.ptr_len = true; - write!(out, "::rust::repr::PtrLen "); + Some(Type::Str(_)) | Some(Type::SliceRef(_)) => { + out.builtin.repr_fat = true; + write!(out, "::rust::repr::Fat "); } Some(ty) if out.types.needs_indirect_abi(ty) => write!(out, "void "), _ => write_return_type(out, ty), @@ -832,23 +1143,23 @@ fn write_extern_arg(out: &mut OutFile, arg: &Var) { write_type_space(out, &ty.inner); write!(out, "*"); } - Type::Str(_) | Type::SliceRefU8(_) => { - out.builtin.ptr_len = true; - write!(out, "::rust::repr::PtrLen "); - } _ => write_type_space(out, &arg.ty), } if out.types.needs_indirect_abi(&arg.ty) { write!(out, "*"); } - write!(out, "{}", arg.ident); + write!(out, "{}", arg.name.cxx); } fn write_type(out: &mut OutFile, ty: &Type) { match ty { Type::Ident(ident) => match Atom::from(&ident.rust) { Some(atom) => write_atom(out, atom), - None => write!(out, "{}", out.types.resolve(ident).to_fully_qualified()), + None => write!( + out, + "{}", + out.types.resolve(ident).name.to_fully_qualified(), + ), }, Type::RustBox(ty) => { write!(out, "::rust::Box<"); @@ -865,30 +1176,42 @@ fn write_type(out: &mut OutFile, ty: &Type) { write_type(out, &ptr.inner); write!(out, ">"); } + Type::SharedPtr(ptr) => { + write!(out, "::std::shared_ptr<"); + write_type(out, &ptr.inner); + write!(out, ">"); + } + Type::WeakPtr(ptr) => { + write!(out, "::std::weak_ptr<"); + write_type(out, &ptr.inner); + write!(out, ">"); + } Type::CxxVector(ty) => { write!(out, "::std::vector<"); write_type(out, &ty.inner); write!(out, ">"); } Type::Ref(r) => { - if r.mutability.is_none() { - write!(out, "const "); - } - write_type(out, &r.inner); + write_pointee_type(out, &r.inner, r.mutable); write!(out, " &"); } - Type::Slice(_) => { - // For now, only U8 slices are supported, which are covered separately below - unreachable!() + Type::Ptr(p) => { + write_pointee_type(out, &p.inner, p.mutable); + write!(out, " *"); } Type::Str(_) => { write!(out, "::rust::Str"); } - Type::SliceRefU8(_) => { - write!(out, "::rust::Slice<uint8_t>"); + Type::SliceRef(slice) => { + write!(out, "::rust::Slice<"); + if slice.mutability.is_none() { + write!(out, "const "); + } + write_type(out, &slice.inner); + write!(out, ">"); } Type::Fn(f) => { - write!(out, "::rust::{}<", if f.throws { "TryFn" } else { "Fn" }); + write!(out, "::rust::Fn<"); match &f.ret { Some(ret) => write_type(out, ret), None => write!(out, "void"), @@ -902,22 +1225,43 @@ fn write_type(out: &mut OutFile, ty: &Type) { } write!(out, ")>"); } + Type::Array(a) => { + write!(out, "::std::array<"); + write_type(out, &a.inner); + write!(out, ", {}>", &a.len); + } Type::Void(_) => unreachable!(), } } +// Write just the T type behind a &T or &mut T or *const T or *mut T. +fn write_pointee_type(out: &mut OutFile, inner: &Type, mutable: bool) { + if let Type::Ptr(_) = inner { + write_type_space(out, inner); + if !mutable { + write!(out, "const"); + } + } else { + if !mutable { + write!(out, "const "); + } + write_type(out, inner); + } +} + fn write_atom(out: &mut OutFile, atom: Atom) { match atom { Bool => write!(out, "bool"), - U8 => write!(out, "uint8_t"), - U16 => write!(out, "uint16_t"), - U32 => write!(out, "uint32_t"), - U64 => write!(out, "uint64_t"), - Usize => write!(out, "size_t"), - I8 => write!(out, "int8_t"), - I16 => write!(out, "int16_t"), - I32 => write!(out, "int32_t"), - I64 => write!(out, "int64_t"), + Char => write!(out, "char"), + U8 => write!(out, "::std::uint8_t"), + U16 => write!(out, "::std::uint16_t"), + U32 => write!(out, "::std::uint32_t"), + U64 => write!(out, "::std::uint64_t"), + Usize => write!(out, "::std::size_t"), + I8 => write!(out, "::std::int8_t"), + I16 => write!(out, "::std::int16_t"), + I32 => write!(out, "::std::int32_t"), + I64 => write!(out, "::std::int64_t"), Isize => write!(out, "::rust::isize"), F32 => write!(out, "float"), F64 => write!(out, "double"), @@ -936,33 +1280,62 @@ fn write_space_after_type(out: &mut OutFile, ty: &Type) { Type::Ident(_) | Type::RustBox(_) | Type::UniquePtr(_) + | Type::SharedPtr(_) + | Type::WeakPtr(_) | Type::Str(_) | Type::CxxVector(_) | Type::RustVec(_) - | Type::SliceRefU8(_) - | Type::Fn(_) => write!(out, " "), - Type::Ref(_) => {} - Type::Void(_) | Type::Slice(_) => unreachable!(), + | Type::SliceRef(_) + | Type::Fn(_) + | Type::Array(_) => write!(out, " "), + Type::Ref(_) | Type::Ptr(_) => {} + Type::Void(_) => unreachable!(), } } -// Only called for legal referent types of unique_ptr and element types of -// std::vector and Vec. -fn to_typename(ty: &Type, types: &Types) -> String { - match ty { - Type::Ident(ident) => types.resolve(&ident).to_fully_qualified(), - Type::CxxVector(ptr) => format!("::std::vector<{}>", to_typename(&ptr.inner, types)), - _ => unreachable!(), +#[derive(Copy, Clone)] +enum UniquePtr<'a> { + Ident(&'a Ident), + CxxVector(&'a Ident), +} + +trait ToTypename { + fn to_typename(&self, types: &Types) -> String; +} + +impl ToTypename for Ident { + fn to_typename(&self, types: &Types) -> String { + types.resolve(self).name.to_fully_qualified() } } -// Only called for legal referent types of unique_ptr and element types of -// std::vector and Vec. -fn to_mangled(ty: &Type, types: &Types) -> Symbol { - match ty { - Type::Ident(ident) => ident.to_symbol(types), - Type::CxxVector(ptr) => to_mangled(&ptr.inner, types).prefix_with("std$vector$"), - _ => unreachable!(), +impl<'a> ToTypename for UniquePtr<'a> { + fn to_typename(&self, types: &Types) -> String { + match self { + UniquePtr::Ident(ident) => ident.to_typename(types), + UniquePtr::CxxVector(element) => { + format!("::std::vector<{}>", element.to_typename(types)) + } + } + } +} + +trait ToMangled { + fn to_mangled(&self, types: &Types) -> Symbol; +} + +impl ToMangled for Ident { + fn to_mangled(&self, types: &Types) -> Symbol { + types.resolve(self).name.to_symbol() + } +} + +impl<'a> ToMangled for UniquePtr<'a> { + fn to_mangled(&self, types: &Types) -> Symbol { + match self { + UniquePtr::Ident(ident) => ident.to_mangled(types), + UniquePtr::CxxVector(element) => element.to_mangled(types).prefix_with("std$vector$"), + } } } @@ -974,233 +1347,252 @@ fn write_generic_instantiations(out: &mut OutFile) { 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, &out.types.resolve(&inner)); - } - } else if let Type::RustVec(ty) = ty { - if let Type::Ident(inner) = &ty.inner { - 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.rust).is_none() - && (!out.types.aliases.contains_key(&inner.rust) - || out.types.explicit_impls.contains(ty)) - { - out.next_section(); - write_unique_ptr(out, inner); - } - } - } else if let Type::CxxVector(ptr) = ty { - if let Type::Ident(inner) = &ptr.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); - } - } + for impl_key in out.types.impls.keys() { + out.next_section(); + match *impl_key { + ImplKey::RustBox(ident) => write_rust_box_extern(out, ident), + ImplKey::RustVec(ident) => write_rust_vec_extern(out, ident), + ImplKey::UniquePtr(ident) => write_unique_ptr(out, ident), + ImplKey::SharedPtr(ident) => write_shared_ptr(out, ident), + ImplKey::WeakPtr(ident) => write_weak_ptr(out, ident), + ImplKey::CxxVector(ident) => write_cxx_vector(out, ident), } } out.end_block(Block::ExternC); out.begin_block(Block::Namespace("rust")); - out.begin_block(Block::InlineNamespace("cxxbridge05")); - for ty in out.types { - if let Type::RustBox(ty) = ty { - if let Type::Ident(inner) = &ty.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.rust).is_none() { - write_rust_vec_impl(out, inner); - } - } + out.begin_block(Block::InlineNamespace("cxxbridge1")); + for impl_key in out.types.impls.keys() { + match *impl_key { + ImplKey::RustBox(ident) => write_rust_box_impl(out, ident), + ImplKey::RustVec(ident) => write_rust_vec_impl(out, ident), + _ => {} } } - out.end_block(Block::InlineNamespace("cxxbridge05")); + out.end_block(Block::InlineNamespace("cxxbridge1")); out.end_block(Block::Namespace("rust")); } -fn write_rust_box_extern(out: &mut OutFile, ident: &Pair) { - let inner = ident.to_fully_qualified(); - let instance = ident.to_symbol(); +fn write_rust_box_extern(out: &mut OutFile, key: NamedImplKey) { + let resolve = out.types.resolve(&key); + let inner = resolve.name.to_fully_qualified(); + let instance = resolve.name.to_symbol(); - writeln!(out, "#ifndef CXXBRIDGE05_RUST_BOX_{}", instance); - writeln!(out, "#define CXXBRIDGE05_RUST_BOX_{}", instance); writeln!( out, - "void cxxbridge05$box${}$uninit(::rust::Box<{}> *ptr) noexcept;", + "{} *cxxbridge1$box${}$alloc() noexcept;", + inner, instance, + ); + writeln!( + out, + "void cxxbridge1$box${}$dealloc({} *) noexcept;", instance, inner, ); writeln!( out, - "void cxxbridge05$box${}$drop(::rust::Box<{}> *ptr) noexcept;", + "void cxxbridge1$box${}$drop(::rust::Box<{}> *ptr) noexcept;", instance, inner, ); - writeln!(out, "#endif // CXXBRIDGE05_RUST_BOX_{}", instance); } -fn write_rust_vec_extern(out: &mut OutFile, element: &ResolvableName) { - let element = Type::Ident(element.clone()); - let inner = to_typename(&element, out.types); - let instance = to_mangled(&element, out.types); +fn write_rust_vec_extern(out: &mut OutFile, key: NamedImplKey) { + let element = key.rust; + let inner = element.to_typename(out.types); + let instance = element.to_mangled(out.types); + + out.include.cstddef = true; - writeln!(out, "#ifndef CXXBRIDGE05_RUST_VEC_{}", instance); - writeln!(out, "#define CXXBRIDGE05_RUST_VEC_{}", instance); writeln!( out, - "void cxxbridge05$rust_vec${}$new(const ::rust::Vec<{}> *ptr) noexcept;", + "void cxxbridge1$rust_vec${}$new(const ::rust::Vec<{}> *ptr) noexcept;", instance, inner, ); writeln!( out, - "void cxxbridge05$rust_vec${}$drop(::rust::Vec<{}> *ptr) noexcept;", + "void cxxbridge1$rust_vec${}$drop(::rust::Vec<{}> *ptr) noexcept;", instance, inner, ); writeln!( out, - "size_t cxxbridge05$rust_vec${}$len(const ::rust::Vec<{}> *ptr) noexcept;", + "::std::size_t cxxbridge1$rust_vec${}$len(const ::rust::Vec<{}> *ptr) noexcept;", instance, inner, ); writeln!( out, - "const {} *cxxbridge05$rust_vec${}$data(const ::rust::Vec<{0}> *ptr) noexcept;", - inner, instance, + "::std::size_t cxxbridge1$rust_vec${}$capacity(const ::rust::Vec<{}> *ptr) noexcept;", + instance, inner, ); writeln!( out, - "void cxxbridge05$rust_vec${}$reserve_total(::rust::Vec<{}> *ptr, size_t cap) noexcept;", - instance, inner, + "const {} *cxxbridge1$rust_vec${}$data(const ::rust::Vec<{0}> *ptr) noexcept;", + inner, instance, ); writeln!( out, - "void cxxbridge05$rust_vec${}$set_len(::rust::Vec<{}> *ptr, size_t len) noexcept;", + "void cxxbridge1$rust_vec${}$reserve_total(::rust::Vec<{}> *ptr, ::std::size_t cap) noexcept;", instance, inner, ); writeln!( out, - "size_t cxxbridge05$rust_vec${}$stride() noexcept;", - instance, + "void cxxbridge1$rust_vec${}$set_len(::rust::Vec<{}> *ptr, ::std::size_t len) noexcept;", + instance, inner, ); - writeln!(out, "#endif // CXXBRIDGE05_RUST_VEC_{}", instance); } -fn write_rust_box_impl(out: &mut OutFile, ident: &Pair) { - let inner = ident.to_fully_qualified(); - let instance = ident.to_symbol(); +fn write_rust_box_impl(out: &mut OutFile, key: NamedImplKey) { + let resolve = out.types.resolve(&key); + let inner = resolve.name.to_fully_qualified(); + let instance = resolve.name.to_symbol(); + + writeln!(out, "template <>"); + begin_function_definition(out); + writeln!( + out, + "{} *Box<{}>::allocation::alloc() noexcept {{", + inner, inner, + ); + writeln!(out, " return cxxbridge1$box${}$alloc();", instance); + writeln!(out, "}}"); writeln!(out, "template <>"); - writeln!(out, "void Box<{}>::uninit() noexcept {{", inner); - writeln!(out, " cxxbridge05$box${}$uninit(this);", instance); + begin_function_definition(out); + writeln!( + out, + "void Box<{}>::allocation::dealloc({} *ptr) noexcept {{", + inner, inner, + ); + writeln!(out, " cxxbridge1$box${}$dealloc(ptr);", instance); writeln!(out, "}}"); writeln!(out, "template <>"); + begin_function_definition(out); writeln!(out, "void Box<{}>::drop() noexcept {{", inner); - writeln!(out, " cxxbridge05$box${}$drop(this);", instance); + writeln!(out, " cxxbridge1$box${}$drop(this);", instance); writeln!(out, "}}"); } -fn write_rust_vec_impl(out: &mut OutFile, element: &ResolvableName) { - let element = Type::Ident(element.clone()); - let inner = to_typename(&element, out.types); - let instance = to_mangled(&element, out.types); +fn write_rust_vec_impl(out: &mut OutFile, key: NamedImplKey) { + let element = key.rust; + let inner = element.to_typename(out.types); + let instance = element.to_mangled(out.types); + + out.include.cstddef = true; writeln!(out, "template <>"); + begin_function_definition(out); writeln!(out, "Vec<{}>::Vec() noexcept {{", inner); - writeln!(out, " cxxbridge05$rust_vec${}$new(this);", instance); + writeln!(out, " cxxbridge1$rust_vec${}$new(this);", instance); writeln!(out, "}}"); writeln!(out, "template <>"); + begin_function_definition(out); writeln!(out, "void Vec<{}>::drop() noexcept {{", inner); - writeln!( - out, - " return cxxbridge05$rust_vec${}$drop(this);", - instance, - ); + writeln!(out, " return cxxbridge1$rust_vec${}$drop(this);", instance); writeln!(out, "}}"); writeln!(out, "template <>"); - writeln!(out, "size_t Vec<{}>::size() const noexcept {{", inner); - writeln!(out, " return cxxbridge05$rust_vec${}$len(this);", instance); + begin_function_definition(out); + writeln!( + out, + "::std::size_t Vec<{}>::size() const noexcept {{", + inner, + ); + writeln!(out, " return cxxbridge1$rust_vec${}$len(this);", instance); writeln!(out, "}}"); writeln!(out, "template <>"); - writeln!(out, "const {} *Vec<{0}>::data() const noexcept {{", inner); + begin_function_definition(out); writeln!( out, - " return cxxbridge05$rust_vec${}$data(this);", + "::std::size_t Vec<{}>::capacity() const noexcept {{", + inner, + ); + writeln!( + out, + " return cxxbridge1$rust_vec${}$capacity(this);", instance, ); writeln!(out, "}}"); writeln!(out, "template <>"); + begin_function_definition(out); + writeln!(out, "const {} *Vec<{0}>::data() const noexcept {{", inner); + writeln!(out, " return cxxbridge1$rust_vec${}$data(this);", instance); + writeln!(out, "}}"); + + writeln!(out, "template <>"); + begin_function_definition(out); writeln!( out, - "void Vec<{}>::reserve_total(size_t cap) noexcept {{", + "void Vec<{}>::reserve_total(::std::size_t cap) noexcept {{", inner, ); writeln!( out, - " return cxxbridge05$rust_vec${}$reserve_total(this, cap);", + " return cxxbridge1$rust_vec${}$reserve_total(this, cap);", instance, ); writeln!(out, "}}"); writeln!(out, "template <>"); - writeln!(out, "void Vec<{}>::set_len(size_t len) noexcept {{", inner); + begin_function_definition(out); writeln!( out, - " return cxxbridge05$rust_vec${}$set_len(this, len);", + "void Vec<{}>::set_len(::std::size_t len) noexcept {{", + inner, + ); + writeln!( + out, + " return cxxbridge1$rust_vec${}$set_len(this, len);", instance, ); writeln!(out, "}}"); - - writeln!(out, "template <>"); - writeln!(out, "size_t Vec<{}>::stride() noexcept {{", inner); - writeln!(out, " return cxxbridge05$rust_vec${}$stride();", instance); - writeln!(out, "}}"); } -fn write_unique_ptr(out: &mut OutFile, ident: &ResolvableName) { - let ty = Type::Ident(ident.clone()); - let instance = to_mangled(&ty, out.types); - - writeln!(out, "#ifndef CXXBRIDGE05_UNIQUE_PTR_{}", instance); - writeln!(out, "#define CXXBRIDGE05_UNIQUE_PTR_{}", instance); - - write_unique_ptr_common(out, &ty); - - writeln!(out, "#endif // CXXBRIDGE05_UNIQUE_PTR_{}", instance); +fn write_unique_ptr(out: &mut OutFile, key: NamedImplKey) { + let ty = UniquePtr::Ident(key.rust); + write_unique_ptr_common(out, ty); } // Shared by UniquePtr<T> and UniquePtr<CxxVector<T>>. -fn write_unique_ptr_common(out: &mut OutFile, ty: &Type) { +fn write_unique_ptr_common(out: &mut OutFile, ty: UniquePtr) { out.include.new = true; out.include.utility = true; - let inner = to_typename(ty, out.types); - let instance = to_mangled(ty, out.types); + let inner = ty.to_typename(out.types); + let instance = ty.to_mangled(out.types); let can_construct_from_value = match ty { // Some aliases are to opaque types; some are to trivial types. We can't // know at code generation time, so we generate both C++ and Rust side // bindings for a "new" method anyway. But the Rust code can't be called // for Opaque types because the 'new' method is not implemented. - Type::Ident(ident) => { - out.types.structs.contains_key(&ident.rust) - || out.types.aliases.contains_key(&ident.rust) + UniquePtr::Ident(ident) => { + out.types.structs.contains_key(ident) + || out.types.enums.contains_key(ident) + || out.types.aliases.contains_key(ident) } - _ => false, + UniquePtr::CxxVector(_) => false, + }; + + let conditional_delete = match ty { + UniquePtr::Ident(ident) => { + !out.types.structs.contains_key(ident) && !out.types.enums.contains_key(ident) + } + UniquePtr::CxxVector(_) => false, }; + if conditional_delete { + out.builtin.is_complete = true; + let definition = match ty { + UniquePtr::Ident(ty) => &out.types.resolve(ty).name.cxx, + UniquePtr::CxxVector(_) => unreachable!(), + }; + writeln!( + out, + "static_assert(::rust::detail::is_complete<{}>::value, \"definition of {} is required\");", + inner, definition, + ); + } writeln!( out, "static_assert(sizeof(::std::unique_ptr<{}>) == sizeof(void *), \"\");", @@ -1213,77 +1605,220 @@ fn write_unique_ptr_common(out: &mut OutFile, ty: &Type) { ); writeln!( out, - "void cxxbridge05$unique_ptr${}$null(::std::unique_ptr<{}> *ptr) noexcept {{", + "void cxxbridge1$unique_ptr${}$null(::std::unique_ptr<{}> *ptr) noexcept {{", instance, inner, ); - writeln!(out, " new (ptr) ::std::unique_ptr<{}>();", inner); + writeln!(out, " ::new (ptr) ::std::unique_ptr<{}>();", inner); writeln!(out, "}}"); if can_construct_from_value { + out.builtin.maybe_uninit = true; writeln!( out, - "void cxxbridge05$unique_ptr${}$new(::std::unique_ptr<{}> *ptr, {} *value) noexcept {{", - instance, inner, inner, + "{} *cxxbridge1$unique_ptr${}$uninit(::std::unique_ptr<{}> *ptr) noexcept {{", + inner, instance, inner, ); writeln!( out, - " new (ptr) ::std::unique_ptr<{}>(new {}(::std::move(*value)));", - inner, inner, + " {} *uninit = reinterpret_cast<{} *>(new ::rust::MaybeUninit<{}>);", + inner, inner, inner, ); + writeln!(out, " ::new (ptr) ::std::unique_ptr<{}>(uninit);", inner); + writeln!(out, " return uninit;"); writeln!(out, "}}"); } writeln!( out, - "void cxxbridge05$unique_ptr${}$raw(::std::unique_ptr<{}> *ptr, {} *raw) noexcept {{", + "void cxxbridge1$unique_ptr${}$raw(::std::unique_ptr<{}> *ptr, {} *raw) noexcept {{", instance, inner, inner, ); - writeln!(out, " new (ptr) ::std::unique_ptr<{}>(raw);", inner); + writeln!(out, " ::new (ptr) ::std::unique_ptr<{}>(raw);", inner); writeln!(out, "}}"); writeln!( out, - "const {} *cxxbridge05$unique_ptr${}$get(const ::std::unique_ptr<{}>& ptr) noexcept {{", + "const {} *cxxbridge1$unique_ptr${}$get(const ::std::unique_ptr<{}>& ptr) noexcept {{", inner, instance, inner, ); writeln!(out, " return ptr.get();"); writeln!(out, "}}"); writeln!( out, - "{} *cxxbridge05$unique_ptr${}$release(::std::unique_ptr<{}>& ptr) noexcept {{", + "{} *cxxbridge1$unique_ptr${}$release(::std::unique_ptr<{}>& ptr) noexcept {{", inner, instance, inner, ); writeln!(out, " return ptr.release();"); writeln!(out, "}}"); writeln!( out, - "void cxxbridge05$unique_ptr${}$drop(::std::unique_ptr<{}> *ptr) noexcept {{", + "void cxxbridge1$unique_ptr${}$drop(::std::unique_ptr<{}> *ptr) noexcept {{", instance, inner, ); - writeln!(out, " ptr->~unique_ptr();"); + if conditional_delete { + out.builtin.deleter_if = true; + writeln!( + out, + " ::rust::deleter_if<::rust::detail::is_complete<{}>::value>{{}}(ptr);", + inner, + ); + } else { + writeln!(out, " ptr->~unique_ptr();"); + } writeln!(out, "}}"); } -fn write_cxx_vector(out: &mut OutFile, vector_ty: &Type, element: &ResolvableName) { - let element = Type::Ident(element.clone()); - let inner = to_typename(&element, out.types); - let instance = to_mangled(&element, out.types); +fn write_shared_ptr(out: &mut OutFile, key: NamedImplKey) { + let ident = key.rust; + let resolve = out.types.resolve(ident); + let inner = resolve.name.to_fully_qualified(); + let instance = resolve.name.to_symbol(); + + out.include.new = true; + out.include.utility = true; - writeln!(out, "#ifndef CXXBRIDGE05_VECTOR_{}", instance); - writeln!(out, "#define CXXBRIDGE05_VECTOR_{}", instance); + // Some aliases are to opaque types; some are to trivial types. We can't + // know at code generation time, so we generate both C++ and Rust side + // bindings for a "new" method anyway. But the Rust code can't be called for + // Opaque types because the 'new' method is not implemented. + let can_construct_from_value = out.types.structs.contains_key(ident) + || out.types.enums.contains_key(ident) + || out.types.aliases.contains_key(ident); + + writeln!( + out, + "static_assert(sizeof(::std::shared_ptr<{}>) == 2 * sizeof(void *), \"\");", + inner, + ); writeln!( out, - "size_t cxxbridge05$std$vector${}$size(const ::std::vector<{}> &s) noexcept {{", + "static_assert(alignof(::std::shared_ptr<{}>) == alignof(void *), \"\");", + inner, + ); + writeln!( + out, + "void cxxbridge1$shared_ptr${}$null(::std::shared_ptr<{}> *ptr) noexcept {{", instance, inner, ); - writeln!(out, " return s.size();"); + writeln!(out, " ::new (ptr) ::std::shared_ptr<{}>();", inner); writeln!(out, "}}"); + if can_construct_from_value { + out.builtin.maybe_uninit = true; + writeln!( + out, + "{} *cxxbridge1$shared_ptr${}$uninit(::std::shared_ptr<{}> *ptr) noexcept {{", + inner, instance, inner, + ); + writeln!( + out, + " {} *uninit = reinterpret_cast<{} *>(new ::rust::MaybeUninit<{}>);", + inner, inner, inner, + ); + writeln!(out, " ::new (ptr) ::std::shared_ptr<{}>(uninit);", inner); + writeln!(out, " return uninit;"); + writeln!(out, "}}"); + } writeln!( out, - "const {} *cxxbridge05$std$vector${}$get_unchecked(const ::std::vector<{}> &s, size_t pos) noexcept {{", + "void cxxbridge1$shared_ptr${}$clone(const ::std::shared_ptr<{}>& self, ::std::shared_ptr<{}> *ptr) noexcept {{", + instance, inner, inner, + ); + writeln!(out, " ::new (ptr) ::std::shared_ptr<{}>(self);", inner); + writeln!(out, "}}"); + writeln!( + out, + "const {} *cxxbridge1$shared_ptr${}$get(const ::std::shared_ptr<{}>& self) noexcept {{", inner, instance, inner, ); - writeln!(out, " return &s[pos];"); + writeln!(out, " return self.get();"); + writeln!(out, "}}"); + writeln!( + out, + "void cxxbridge1$shared_ptr${}$drop(::std::shared_ptr<{}> *self) noexcept {{", + instance, inner, + ); + writeln!(out, " self->~shared_ptr();"); + writeln!(out, "}}"); +} + +fn write_weak_ptr(out: &mut OutFile, key: NamedImplKey) { + let resolve = out.types.resolve(&key); + let inner = resolve.name.to_fully_qualified(); + let instance = resolve.name.to_symbol(); + + out.include.new = true; + out.include.utility = true; + + writeln!( + out, + "static_assert(sizeof(::std::weak_ptr<{}>) == 2 * sizeof(void *), \"\");", + inner, + ); + writeln!( + out, + "static_assert(alignof(::std::weak_ptr<{}>) == alignof(void *), \"\");", + inner, + ); + writeln!( + out, + "void cxxbridge1$weak_ptr${}$null(::std::weak_ptr<{}> *ptr) noexcept {{", + instance, inner, + ); + writeln!(out, " ::new (ptr) ::std::weak_ptr<{}>();", inner); writeln!(out, "}}"); + writeln!( + out, + "void cxxbridge1$weak_ptr${}$clone(const ::std::weak_ptr<{}>& self, ::std::weak_ptr<{}> *ptr) noexcept {{", + instance, inner, inner, + ); + writeln!(out, " ::new (ptr) ::std::weak_ptr<{}>(self);", inner); + writeln!(out, "}}"); + writeln!( + out, + "void cxxbridge1$weak_ptr${}$downgrade(const ::std::shared_ptr<{}>& shared, ::std::weak_ptr<{}> *weak) noexcept {{", + instance, inner, inner, + ); + writeln!(out, " ::new (weak) ::std::weak_ptr<{}>(shared);", inner); + writeln!(out, "}}"); + writeln!( + out, + "void cxxbridge1$weak_ptr${}$upgrade(const ::std::weak_ptr<{}>& weak, ::std::shared_ptr<{}> *shared) noexcept {{", + instance, inner, inner, + ); + writeln!( + out, + " ::new (shared) ::std::shared_ptr<{}>(weak.lock());", + inner, + ); + writeln!(out, "}}"); + writeln!( + out, + "void cxxbridge1$weak_ptr${}$drop(::std::weak_ptr<{}> *self) noexcept {{", + instance, inner, + ); + writeln!(out, " self->~weak_ptr();"); + writeln!(out, "}}"); +} + +fn write_cxx_vector(out: &mut OutFile, key: NamedImplKey) { + let element = key.rust; + let inner = element.to_typename(out.types); + let instance = element.to_mangled(out.types); - write_unique_ptr_common(out, vector_ty); + out.include.cstddef = true; + + writeln!( + out, + "::std::size_t cxxbridge1$std$vector${}$size(const ::std::vector<{}> &s) noexcept {{", + instance, inner, + ); + writeln!(out, " return s.size();"); + writeln!(out, "}}"); + writeln!( + out, + "{} *cxxbridge1$std$vector${}$get_unchecked(::std::vector<{}> *s, ::std::size_t pos) noexcept {{", + inner, instance, inner, + ); + writeln!(out, " return &(*s)[pos];"); + writeln!(out, "}}"); - writeln!(out, "#endif // CXXBRIDGE05_VECTOR_{}", instance); + out.include.memory = true; + write_unique_ptr_common(out, UniquePtr::CxxVector(element)); } diff --git a/include/cxx.h b/include/cxx.h index 7dfbbcaf..cdc63fbf 100644 --- a/include/cxx.h +++ b/include/cxx.h @@ -1,9 +1,13 @@ #pragma once +#include <algorithm> #include <array> +#include <cassert> #include <cstddef> #include <cstdint> #include <exception> +#include <initializer_list> #include <iosfwd> +#include <iterator> #include <new> #include <stdexcept> #include <string> @@ -12,10 +16,12 @@ #include <vector> #if defined(_WIN32) #include <basetsd.h> +#else +#include <sys/types.h> #endif namespace rust { -inline namespace cxxbridge05 { +inline namespace cxxbridge1 { struct unsafe_bitcopy_t; @@ -24,8 +30,9 @@ template <typename T> class impl; } -#ifndef CXXBRIDGE05_RUST_STRING -#define CXXBRIDGE05_RUST_STRING +#ifndef CXXBRIDGE1_RUST_STRING +#define CXXBRIDGE1_RUST_STRING +// https://cxx.rs/binding/string.html class String final { public: String() noexcept; @@ -35,102 +42,213 @@ public: String(const std::string &); String(const char *); - String(const char *, size_t); + String(const char *, std::size_t); - String &operator=(const String &) noexcept; - String &operator=(String &&) noexcept; + String &operator=(const String &) &noexcept; + String &operator=(String &&) &noexcept; explicit operator std::string() const; // Note: no null terminator. const char *data() const noexcept; - size_t size() const noexcept; - size_t length() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + + const char *c_str() noexcept; + + using iterator = char *; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const String &) const noexcept; + bool operator!=(const String &) const noexcept; + bool operator<(const String &) const noexcept; + bool operator<=(const String &) const noexcept; + bool operator>(const String &) const noexcept; + bool operator>=(const String &) const noexcept; + + void swap(String &) noexcept; // Internal API only intended for the cxxbridge code generator. String(unsafe_bitcopy_t, const String &) noexcept; private: + friend void swap(String &lhs, String &rhs) noexcept { lhs.swap(rhs); } + // Size and alignment statically verified by rust_string.rs. - std::array<uintptr_t, 3> repr; + std::array<std::uintptr_t, 3> repr; }; -#endif // CXXBRIDGE05_RUST_STRING +#endif // CXXBRIDGE1_RUST_STRING -#ifndef CXXBRIDGE05_RUST_STR +#ifndef CXXBRIDGE1_RUST_STR +#define CXXBRIDGE1_RUST_STR +// https://cxx.rs/binding/str.html class Str final { public: Str() noexcept; + Str(const String &) noexcept; Str(const std::string &); Str(const char *); - Str(const char *, size_t); - Str(std::string &&) = delete; + Str(const char *, std::size_t); - Str &operator=(const Str &) noexcept = default; + Str &operator=(const Str &) &noexcept = default; explicit operator std::string() const; // Note: no null terminator. const char *data() const noexcept; - size_t size() const noexcept; - size_t length() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; // Important in order for System V ABI to pass in registers. Str(const Str &) noexcept = default; ~Str() noexcept = default; + using iterator = const char *; + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const Str &) const noexcept; + bool operator!=(const Str &) const noexcept; + bool operator<(const Str &) const noexcept; + bool operator<=(const Str &) const noexcept; + bool operator>(const Str &) const noexcept; + bool operator>=(const Str &) const noexcept; + + void swap(Str &) noexcept; + private: + class uninit; + Str(uninit) noexcept; friend impl<Str>; - // Not necessarily ABI compatible with &str. Codegen will translate to - // cxx::rust_str::RustStr which matches this layout. - const char *ptr; - size_t len; + + std::array<std::uintptr_t, 2> repr; }; -#endif // CXXBRIDGE05_RUST_STR +#endif // CXXBRIDGE1_RUST_STR -#ifndef CXXBRIDGE05_RUST_SLICE +#ifndef CXXBRIDGE1_RUST_SLICE +namespace detail { +template <bool> +struct copy_assignable_if {}; + +template <> +struct copy_assignable_if<false> { + copy_assignable_if() noexcept = default; + copy_assignable_if(const copy_assignable_if &) noexcept = default; + copy_assignable_if &operator=(const copy_assignable_if &) &noexcept = delete; + copy_assignable_if &operator=(copy_assignable_if &&) &noexcept = default; +}; +} // namespace detail + +// https://cxx.rs/binding/slice.html template <typename T> -class Slice final { +class Slice final + : private detail::copy_assignable_if<std::is_const<T>::value> { public: + using value_type = T; + Slice() noexcept; - Slice(const T *, size_t count) noexcept; + Slice(T *, std::size_t count) noexcept; - Slice &operator=(const Slice<T> &) noexcept = default; + Slice &operator=(const Slice<T> &) &noexcept = default; + Slice &operator=(Slice<T> &&) &noexcept = default; - const T *data() const noexcept; - size_t size() const noexcept; - size_t length() const noexcept; + T *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + T &operator[](std::size_t n) const noexcept; + T &at(std::size_t n) const; + T &front() const noexcept; + T &back() const noexcept; // Important in order for System V ABI to pass in registers. Slice(const Slice<T> &) noexcept = default; ~Slice() noexcept = default; + class iterator; + iterator begin() const noexcept; + iterator end() const noexcept; + + void swap(Slice &) noexcept; + private: + class uninit; + Slice(uninit) noexcept; friend impl<Slice>; - // Not necessarily ABI compatible with &[T]. Codegen will translate to - // cxx::rust_sliceu8::RustSliceU8 which matches this layout. - const T *ptr; - size_t len; + friend void sliceInit(void *, const void *, std::size_t) noexcept; + friend void *slicePtr(const void *) noexcept; + friend std::size_t sliceLen(const void *) noexcept; + + std::array<std::uintptr_t, 2> repr; }; -#endif // CXXBRIDGE05_RUST_SLICE -#ifndef CXXBRIDGE05_RUST_BOX template <typename T> -class Box final { +class Slice<T>::iterator final { public: + using iterator_category = std::random_access_iterator_tag; using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = typename std::add_pointer<T>::type; + using reference = typename std::add_lvalue_reference<T>::type; + + reference operator*() const noexcept; + pointer operator->() const noexcept; + reference operator[](difference_type) const noexcept; + + iterator &operator++() noexcept; + iterator operator++(int) noexcept; + iterator &operator--() noexcept; + iterator operator--(int) noexcept; + + iterator &operator+=(difference_type) noexcept; + iterator &operator-=(difference_type) noexcept; + iterator operator+(difference_type) const noexcept; + iterator operator-(difference_type) const noexcept; + difference_type operator-(const iterator &) const noexcept; + + bool operator==(const iterator &) const noexcept; + bool operator!=(const iterator &) const noexcept; + bool operator<(const iterator &) const noexcept; + bool operator<=(const iterator &) const noexcept; + bool operator>(const iterator &) const noexcept; + bool operator>=(const iterator &) const noexcept; + +private: + friend class Slice; + void *pos; + std::size_t stride; +}; +#endif // CXXBRIDGE1_RUST_SLICE + +#ifndef CXXBRIDGE1_RUST_BOX +// https://cxx.rs/binding/box.html +template <typename T> +class Box final { +public: + using element_type = T; using const_pointer = typename std::add_pointer<typename std::add_const<T>::type>::type; using pointer = typename std::add_pointer<T>::type; - Box(const Box &); + Box() = delete; Box(Box &&) noexcept; ~Box() noexcept; explicit Box(const T &); explicit Box(T &&); - Box &operator=(const Box &); - Box &operator=(Box &&) noexcept; + Box &operator=(Box &&) &noexcept; const T *operator->() const noexcept; const T &operator*() const noexcept; @@ -140,118 +258,121 @@ public: template <typename... Fields> static Box in_place(Fields &&...); + void swap(Box &) noexcept; + // Important: requires that `raw` came from an into_raw call. Do not pass a // pointer from `new` or any other source. static Box from_raw(T *) noexcept; T *into_raw() noexcept; + /* Deprecated */ using value_type = element_type; + private: - Box() noexcept; - void uninit() noexcept; + class uninit; + class allocation; + Box(uninit) noexcept; void drop() noexcept; + + friend void swap(Box &lhs, Box &rhs) noexcept { lhs.swap(rhs); } + T *ptr; }; -#endif // CXXBRIDGE05_RUST_BOX +#endif // CXXBRIDGE1_RUST_BOX -#ifndef CXXBRIDGE05_RUST_VEC +#ifndef CXXBRIDGE1_RUST_VEC +// https://cxx.rs/binding/vec.html template <typename T> class Vec final { public: using value_type = T; Vec() noexcept; + Vec(std::initializer_list<T>); + Vec(const Vec &); Vec(Vec &&) noexcept; ~Vec() noexcept; - Vec &operator=(Vec &&) noexcept; + Vec &operator=(Vec &&) &noexcept; + Vec &operator=(const Vec &) &; - size_t size() const noexcept; + std::size_t size() const noexcept; bool empty() const noexcept; const T *data() const noexcept; T *data() noexcept; + std::size_t capacity() const noexcept; - const T &operator[](size_t n) const noexcept; - const T &at(size_t n) const; + const T &operator[](std::size_t n) const noexcept; + const T &at(std::size_t n) const; + const T &front() const noexcept; + const T &back() const noexcept; - const T &front() const; - const T &back() const; + T &operator[](std::size_t n) noexcept; + T &at(std::size_t n); + T &front() noexcept; + T &back() noexcept; - void reserve(size_t new_cap); + void reserve(std::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; - using pointer = - typename std::add_pointer<typename std::add_const<T>::type>::type; - using reference = typename std::add_lvalue_reference< - typename std::add_const<T>::type>::type; - using iterator_category = std::forward_iterator_tag; - - const T &operator*() const noexcept; - const T *operator->() const noexcept; - const_iterator &operator++() noexcept; - const_iterator operator++(int) noexcept; - bool operator==(const const_iterator &) const noexcept; - bool operator!=(const const_iterator &) const noexcept; - - private: - friend class Vec; - const void *pos; - size_t stride; - }; + template <typename... Args> + void emplace_back(Args &&...args); + using iterator = typename Slice<T>::iterator; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = typename Slice<const T>::iterator; const_iterator begin() const noexcept; const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + void swap(Vec &) noexcept; // Internal API only intended for the cxxbridge code generator. Vec(unsafe_bitcopy_t, const Vec &) noexcept; private: - static size_t stride() noexcept; - void reserve_total(size_t cap) noexcept; - void set_len(size_t len) noexcept; + void reserve_total(std::size_t cap) noexcept; + void set_len(std::size_t len) noexcept; void drop() noexcept; + friend void swap(Vec &lhs, Vec &rhs) noexcept { lhs.swap(rhs); } + // Size and alignment statically verified by rust_vec.rs. - std::array<uintptr_t, 3> repr; + std::array<std::uintptr_t, 3> repr; }; -#endif // CXXBRIDGE05_RUST_VEC +#endif // CXXBRIDGE1_RUST_VEC -#ifndef CXXBRIDGE05_RUST_FN -template <typename Signature, bool Throws = false> +#ifndef CXXBRIDGE1_RUST_FN +// https://cxx.rs/binding/fn.html +template <typename Signature> class Fn; -template <typename Ret, typename... Args, bool Throws> -class Fn<Ret(Args...), Throws> final { +template <typename Ret, typename... Args> +class Fn<Ret(Args...)> final { public: - Ret operator()(Args... args) const noexcept(!Throws); + Ret operator()(Args... args) const noexcept; Fn operator*() const noexcept; private: - Ret (*trampoline)(Args..., void *fn) noexcept(!Throws); + Ret (*trampoline)(Args..., void *fn) noexcept; void *fn; }; +#endif // CXXBRIDGE1_RUST_FN -template <typename Signature> -using TryFn = Fn<Signature, true>; -#endif // CXXBRIDGE05_RUST_FN - -#ifndef CXXBRIDGE05_RUST_ERROR -#define CXXBRIDGE05_RUST_ERROR +#ifndef CXXBRIDGE1_RUST_ERROR +#define CXXBRIDGE1_RUST_ERROR +// https://cxx.rs/binding/result.html class Error final : public std::exception { public: Error(const Error &); Error(Error &&) noexcept; - ~Error() noexcept; + ~Error() noexcept override; - Error &operator=(const Error &); - Error &operator=(Error &&) noexcept; + Error &operator=(const Error &) &; + Error &operator=(Error &&) &noexcept; const char *what() const noexcept override; @@ -259,22 +380,38 @@ private: Error() noexcept = default; friend impl<Error>; const char *msg; - size_t len; + std::size_t len; }; -#endif // CXXBRIDGE05_RUST_ERROR +#endif // CXXBRIDGE1_RUST_ERROR -#ifndef CXXBRIDGE05_RUST_ISIZE -#define CXXBRIDGE05_RUST_ISIZE +#ifndef CXXBRIDGE1_RUST_ISIZE +#define CXXBRIDGE1_RUST_ISIZE #if defined(_WIN32) using isize = SSIZE_T; #else using isize = ssize_t; #endif -#endif // CXXBRIDGE05_RUST_ISIZE +#endif // CXXBRIDGE1_RUST_ISIZE std::ostream &operator<<(std::ostream &, const String &); std::ostream &operator<<(std::ostream &, const Str &); +#ifndef CXXBRIDGE1_RUST_OPAQUE +#define CXXBRIDGE1_RUST_OPAQUE +// Base class of generated opaque Rust types. +class Opaque { +public: + Opaque() = delete; + Opaque(const Opaque &) = delete; + ~Opaque() = delete; +}; +#endif // CXXBRIDGE1_RUST_OPAQUE + +template <typename T> +std::size_t size_of(); +template <typename T> +std::size_t align_of(); + // IsRelocatable<T> is used in assertions that a C++ type passed by value // between Rust and C++ is soundly relocatable by Rust. // @@ -296,20 +433,30 @@ std::ostream &operator<<(std::ostream &, const Str &); template <typename T> struct IsRelocatable; +using u8 = std::uint8_t; +using u16 = std::uint16_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; +using usize = std::size_t; // see static asserts in cxx.cc +using i8 = std::int8_t; +using i16 = std::int16_t; +using i32 = std::int32_t; +using i64 = std::int64_t; +using f32 = float; +using f64 = double; + // Snake case aliases for use in code that uses this style for type names. using string = String; using str = Str; -template <class T> +template <typename T> using slice = Slice<T>; -template <class T> +template <typename T> using box = Box<T>; -template <class T> +template <typename 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>; +using fn = Fn<Signature>; template <typename T> using is_relocatable = IsRelocatable<T>; @@ -318,71 +465,254 @@ using is_relocatable = IsRelocatable<T>; //////////////////////////////////////////////////////////////////////////////// /// end public API, begin implementation details -#ifndef CXXBRIDGE05_PANIC -#define CXXBRIDGE05_PANIC +#ifndef CXXBRIDGE1_PANIC +#define CXXBRIDGE1_PANIC template <typename Exception> void panic [[noreturn]] (const char *msg); -#endif // CXXBRIDGE05_PANIC +#endif // CXXBRIDGE1_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) { +#ifndef CXXBRIDGE1_RUST_FN +#define CXXBRIDGE1_RUST_FN +template <typename Ret, typename... Args> +Ret Fn<Ret(Args...)>::operator()(Args... args) const noexcept { return (*this->trampoline)(std::move(args)..., this->fn); } -template <typename Ret, typename... Args, bool Throws> -Fn<Ret(Args...), Throws> Fn<Ret(Args...), Throws>::operator*() const noexcept { +template <typename Ret, typename... Args> +Fn<Ret(Args...)> Fn<Ret(Args...)>::operator*() const noexcept { return *this; } -#endif // CXXBRIDGE05_RUST_FN +#endif // CXXBRIDGE1_RUST_FN -#ifndef CXXBRIDGE05_RUST_BITCOPY -#define CXXBRIDGE05_RUST_BITCOPY +#ifndef CXXBRIDGE1_RUST_BITCOPY_T +#define CXXBRIDGE1_RUST_BITCOPY_T struct unsafe_bitcopy_t final { explicit unsafe_bitcopy_t() = default; }; +#endif // CXXBRIDGE1_RUST_BITCOPY_T +#ifndef CXXBRIDGE1_RUST_BITCOPY +#define CXXBRIDGE1_RUST_BITCOPY constexpr unsafe_bitcopy_t unsafe_bitcopy{}; -#endif // CXXBRIDGE05_RUST_BITCOPY +#endif // CXXBRIDGE1_RUST_BITCOPY -#ifndef CXXBRIDGE05_RUST_STR -#define CXXBRIDGE05_RUST_STR -inline const char *Str::data() const noexcept { return this->ptr; } +#ifndef CXXBRIDGE1_RUST_SLICE +#define CXXBRIDGE1_RUST_SLICE +template <typename T> +Slice<T>::Slice() noexcept { + sliceInit(this, reinterpret_cast<void *>(align_of<T>()), 0); +} -inline size_t Str::size() const noexcept { return this->len; } +template <typename T> +Slice<T>::Slice(T *s, std::size_t count) noexcept { + assert(s != nullptr || count == 0); + sliceInit(this, + s == nullptr && count == 0 + ? reinterpret_cast<void *>(align_of<T>()) + : const_cast<typename std::remove_const<T>::type *>(s), + count); +} -inline size_t Str::length() const noexcept { return this->len; } -#endif // CXXBRIDGE05_RUST_STR +template <typename T> +T *Slice<T>::data() const noexcept { + return reinterpret_cast<T *>(slicePtr(this)); +} -#ifndef CXXBRIDGE05_RUST_SLICE -#define CXXBRIDGE05_RUST_SLICE template <typename T> -Slice<T>::Slice() noexcept : ptr(reinterpret_cast<const T *>(this)), len(0) {} +std::size_t Slice<T>::size() const noexcept { + return sliceLen(this); +} template <typename T> -Slice<T>::Slice(const T *s, size_t count) noexcept : ptr(s), len(count) {} +std::size_t Slice<T>::length() const noexcept { + return this->size(); +} template <typename T> -const T *Slice<T>::data() const noexcept { - return this->ptr; +bool Slice<T>::empty() const noexcept { + return this->size() == 0; +} + +template <typename T> +T &Slice<T>::operator[](std::size_t n) const noexcept { + assert(n < this->size()); + auto pos = static_cast<char *>(slicePtr(this)) + size_of<T>() * n; + return *reinterpret_cast<T *>(pos); +} + +template <typename T> +T &Slice<T>::at(std::size_t n) const { + if (n >= this->size()) { + panic<std::out_of_range>("rust::Slice index out of range"); + } + return (*this)[n]; +} + +template <typename T> +T &Slice<T>::front() const noexcept { + assert(!this->empty()); + return (*this)[0]; +} + +template <typename T> +T &Slice<T>::back() const noexcept { + assert(!this->empty()); + return (*this)[this->size() - 1]; +} + +template <typename T> +typename Slice<T>::iterator::reference +Slice<T>::iterator::operator*() const noexcept { + return *static_cast<T *>(this->pos); +} + +template <typename T> +typename Slice<T>::iterator::pointer +Slice<T>::iterator::operator->() const noexcept { + return static_cast<T *>(this->pos); } template <typename T> -size_t Slice<T>::size() const noexcept { - return this->len; +typename Slice<T>::iterator::reference Slice<T>::iterator::operator[]( + typename Slice<T>::iterator::difference_type n) const noexcept { + auto pos = static_cast<char *>(this->pos) + this->stride * n; + return *reinterpret_cast<T *>(pos); } template <typename T> -size_t Slice<T>::length() const noexcept { - return this->len; +typename Slice<T>::iterator &Slice<T>::iterator::operator++() noexcept { + this->pos = static_cast<char *>(this->pos) + this->stride; + return *this; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator++(int) noexcept { + auto ret = iterator(*this); + this->pos = static_cast<char *>(this->pos) + this->stride; + return ret; } -#endif // CXXBRIDGE05_RUST_SLICE -#ifndef CXXBRIDGE05_RUST_BOX -#define CXXBRIDGE05_RUST_BOX template <typename T> -Box<T>::Box(const Box &other) : Box(*other) {} +typename Slice<T>::iterator &Slice<T>::iterator::operator--() noexcept { + this->pos = static_cast<char *>(this->pos) - this->stride; + return *this; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator--(int) noexcept { + auto ret = iterator(*this); + this->pos = static_cast<char *>(this->pos) - this->stride; + return ret; +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator+=( + typename Slice<T>::iterator::difference_type n) noexcept { + this->pos = static_cast<char *>(this->pos) + this->stride * n; + return *this; +} + +template <typename T> +typename Slice<T>::iterator &Slice<T>::iterator::operator-=( + typename Slice<T>::iterator::difference_type n) noexcept { + this->pos = static_cast<char *>(this->pos) - this->stride * n; + return *this; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator+( + typename Slice<T>::iterator::difference_type n) const noexcept { + auto ret = iterator(*this); + ret.pos = static_cast<char *>(this->pos) + this->stride * n; + return ret; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::iterator::operator-( + typename Slice<T>::iterator::difference_type n) const noexcept { + auto ret = iterator(*this); + ret.pos = static_cast<char *>(this->pos) - this->stride * n; + return ret; +} + +template <typename T> +typename Slice<T>::iterator::difference_type +Slice<T>::iterator::operator-(const iterator &other) const noexcept { + auto diff = std::distance(static_cast<char *>(other.pos), + static_cast<char *>(this->pos)); + return diff / this->stride; +} + +template <typename T> +bool Slice<T>::iterator::operator==(const iterator &other) const noexcept { + return this->pos == other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator!=(const iterator &other) const noexcept { + return this->pos != other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator<(const iterator &other) const noexcept { + return this->pos < other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator<=(const iterator &other) const noexcept { + return this->pos <= other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator>(const iterator &other) const noexcept { + return this->pos > other.pos; +} + +template <typename T> +bool Slice<T>::iterator::operator>=(const iterator &other) const noexcept { + return this->pos >= other.pos; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::begin() const noexcept { + iterator it; + it.pos = slicePtr(this); + it.stride = size_of<T>(); + return it; +} + +template <typename T> +typename Slice<T>::iterator Slice<T>::end() const noexcept { + iterator it = this->begin(); + it.pos = static_cast<char *>(it.pos) + it.stride * this->size(); + return it; +} + +template <typename T> +void Slice<T>::swap(Slice &rhs) noexcept { + std::swap(*this, rhs); +} +#endif // CXXBRIDGE1_RUST_SLICE + +#ifndef CXXBRIDGE1_RUST_BOX +#define CXXBRIDGE1_RUST_BOX +template <typename T> +class Box<T>::uninit {}; + +template <typename T> +class Box<T>::allocation { + static T *alloc() noexcept; + static void dealloc(T *) noexcept; + +public: + allocation() noexcept : ptr(alloc()) {} + ~allocation() noexcept { + if (this->ptr) { + dealloc(this->ptr); + } + } + T *ptr; +}; template <typename T> Box<T>::Box(Box &&other) noexcept : ptr(other.ptr) { @@ -391,14 +721,18 @@ Box<T>::Box(Box &&other) noexcept : ptr(other.ptr) { template <typename T> Box<T>::Box(const T &val) { - this->uninit(); - ::new (this->ptr) T(val); + allocation alloc; + ::new (alloc.ptr) T(val); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; } template <typename T> Box<T>::Box(T &&val) { - this->uninit(); - ::new (this->ptr) T(std::move(val)); + allocation alloc; + ::new (alloc.ptr) T(std::move(val)); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; } template <typename T> @@ -409,20 +743,7 @@ Box<T>::~Box() noexcept { } template <typename T> -Box<T> &Box<T>::operator=(const Box &other) { - if (this != &other) { - if (this->ptr) { - **this = *other; - } else { - this->uninit(); - ::new (this->ptr) T(*other); - } - } - return *this; -} - -template <typename T> -Box<T> &Box<T>::operator=(Box &&other) noexcept { +Box<T> &Box<T>::operator=(Box &&other) &noexcept { if (this->ptr) { this->drop(); } @@ -453,16 +774,23 @@ T &Box<T>::operator*() noexcept { template <typename T> template <typename... Fields> -Box<T> Box<T>::in_place(Fields &&... fields) { - Box box; - box.uninit(); - ::new (box.ptr) T{std::forward<Fields>(fields)...}; - return box; +Box<T> Box<T>::in_place(Fields &&...fields) { + allocation alloc; + auto ptr = alloc.ptr; + ::new (ptr) T{std::forward<Fields>(fields)...}; + alloc.ptr = nullptr; + return from_raw(ptr); +} + +template <typename T> +void Box<T>::swap(Box &rhs) noexcept { + using std::swap; + swap(this->ptr, rhs.ptr); } template <typename T> Box<T> Box<T>::from_raw(T *raw) noexcept { - Box box; + Box box = uninit{}; box.ptr = raw; return box; } @@ -475,14 +803,25 @@ T *Box<T>::into_raw() noexcept { } template <typename T> -Box<T>::Box() noexcept {} -#endif // CXXBRIDGE05_RUST_BOX +Box<T>::Box(uninit) noexcept {} +#endif // CXXBRIDGE1_RUST_BOX -#ifndef CXXBRIDGE05_RUST_VEC -#define CXXBRIDGE05_RUST_VEC +#ifndef CXXBRIDGE1_RUST_VEC +#define CXXBRIDGE1_RUST_VEC template <typename T> -Vec<T>::Vec(Vec &&other) noexcept { - this->repr = other.repr; +Vec<T>::Vec(std::initializer_list<T> init) : Vec{} { + this->reserve_total(init.size()); + std::move(init.begin(), init.end(), std::back_inserter(*this)); +} + +template <typename T> +Vec<T>::Vec(const Vec &other) : Vec() { + this->reserve_total(other.size()); + std::copy(other.begin(), other.end(), std::back_inserter(*this)); +} + +template <typename T> +Vec<T>::Vec(Vec &&other) noexcept : repr(other.repr) { new (&other) Vec(); } @@ -492,18 +831,25 @@ Vec<T>::~Vec() noexcept { } template <typename T> -Vec<T> &Vec<T>::operator=(Vec &&other) noexcept { +Vec<T> &Vec<T>::operator=(Vec &&other) &noexcept { + this->drop(); + this->repr = other.repr; + new (&other) Vec(); + return *this; +} + +template <typename T> +Vec<T> &Vec<T>::operator=(const Vec &other) & { if (this != &other) { this->drop(); - this->repr = other.repr; - new (&other) Vec(); + new (this) Vec(other); } return *this; } template <typename T> bool Vec<T>::empty() const noexcept { - return size() == 0; + return this->size() == 0; } template <typename T> @@ -512,13 +858,41 @@ T *Vec<T>::data() noexcept { } template <typename T> -const T &Vec<T>::operator[](size_t n) const noexcept { +const T &Vec<T>::operator[](std::size_t n) const noexcept { + assert(n < this->size()); auto data = reinterpret_cast<const char *>(this->data()); - return *reinterpret_cast<const T *>(data + n * this->stride()); + return *reinterpret_cast<const T *>(data + n * size_of<T>()); +} + +template <typename T> +const T &Vec<T>::at(std::size_t n) const { + if (n >= this->size()) { + panic<std::out_of_range>("rust::Vec index out of range"); + } + return (*this)[n]; +} + +template <typename T> +const T &Vec<T>::front() const noexcept { + assert(!this->empty()); + return (*this)[0]; +} + +template <typename T> +const T &Vec<T>::back() const noexcept { + assert(!this->empty()); + return (*this)[this->size() - 1]; +} + +template <typename T> +T &Vec<T>::operator[](std::size_t n) noexcept { + assert(n < this->size()); + auto data = reinterpret_cast<char *>(this->data()); + return *reinterpret_cast<T *>(data + n * size_of<T>()); } template <typename T> -const T &Vec<T>::at(size_t n) const { +T &Vec<T>::at(std::size_t n) { if (n >= this->size()) { panic<std::out_of_range>("rust::Vec index out of range"); } @@ -526,17 +900,19 @@ const T &Vec<T>::at(size_t n) const { } template <typename T> -const T &Vec<T>::front() const { +T &Vec<T>::front() noexcept { + assert(!this->empty()); return (*this)[0]; } template <typename T> -const T &Vec<T>::back() const { +T &Vec<T>::back() noexcept { + assert(!this->empty()); return (*this)[this->size() - 1]; } template <typename T> -void Vec<T>::reserve(size_t new_cap) { +void Vec<T>::reserve(std::size_t new_cap) { this->reserve_total(new_cap); } @@ -552,73 +928,126 @@ void Vec<T>::push_back(T &&value) { template <typename T> template <typename... Args> -void Vec<T>::emplace_back(Args &&... 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())) + size * size_of<T>())) 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); +typename Vec<T>::iterator Vec<T>::begin() noexcept { + return Slice<T>(this->data(), this->size()).begin(); } template <typename T> -const T *Vec<T>::const_iterator::operator->() const noexcept { - return static_cast<const T *>(this->pos); +typename Vec<T>::iterator Vec<T>::end() noexcept { + return Slice<T>(this->data(), this->size()).end(); } template <typename T> -typename Vec<T>::const_iterator &Vec<T>::const_iterator::operator++() noexcept { - this->pos = static_cast<const uint8_t *>(this->pos) + this->stride; - return *this; +typename Vec<T>::const_iterator Vec<T>::begin() const noexcept { + return this->cbegin(); } template <typename T> -typename Vec<T>::const_iterator -Vec<T>::const_iterator::operator++(int) noexcept { - auto ret = const_iterator(*this); - this->pos = static_cast<const uint8_t *>(this->pos) + this->stride; - return ret; +typename Vec<T>::const_iterator Vec<T>::end() const noexcept { + return this->cend(); } template <typename T> -bool Vec<T>::const_iterator::operator==( - const const_iterator &other) const noexcept { - return this->pos == other.pos; +typename Vec<T>::const_iterator Vec<T>::cbegin() const noexcept { + return Slice<const T>(this->data(), this->size()).begin(); } template <typename T> -bool Vec<T>::const_iterator::operator!=( - const const_iterator &other) const noexcept { - return this->pos != other.pos; +typename Vec<T>::const_iterator Vec<T>::cend() const noexcept { + return Slice<const T>(this->data(), this->size()).end(); } template <typename T> -typename Vec<T>::const_iterator Vec<T>::begin() const noexcept { - const_iterator it; - it.pos = this->data(); - it.stride = this->stride(); - return it; +void Vec<T>::swap(Vec &rhs) noexcept { + using std::swap; + swap(this->repr, rhs.repr); } +// Internal API only intended for the cxxbridge code generator. template <typename T> -typename Vec<T>::const_iterator Vec<T>::end() const noexcept { - const_iterator it = this->begin(); - it.pos = static_cast<const uint8_t *>(it.pos) + it.stride * this->size(); - return it; +Vec<T>::Vec(unsafe_bitcopy_t, const Vec &bits) noexcept : repr(bits.repr) {} +#endif // CXXBRIDGE1_RUST_VEC + +#ifndef CXXBRIDGE1_IS_COMPLETE +#define CXXBRIDGE1_IS_COMPLETE +namespace detail { +namespace { +template <typename T, typename = std::size_t> +struct is_complete : std::false_type {}; +template <typename T> +struct is_complete<T, decltype(sizeof(T))> : std::true_type {}; +} // namespace +} // namespace detail +#endif // CXXBRIDGE1_IS_COMPLETE + +#ifndef CXXBRIDGE1_LAYOUT +#define CXXBRIDGE1_LAYOUT +class layout { + template <typename T> + friend std::size_t size_of(); + template <typename T> + friend std::size_t align_of(); + template <typename T> + static typename std::enable_if<std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_size_of() { + return T::layout::size(); + } + template <typename T> + static typename std::enable_if<!std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_size_of() { + return sizeof(T); + } + template <typename T> + static + typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type + size_of() { + return do_size_of<T>(); + } + template <typename T> + static typename std::enable_if<std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_align_of() { + return T::layout::align(); + } + template <typename T> + static typename std::enable_if<!std::is_base_of<Opaque, T>::value, + std::size_t>::type + do_align_of() { + return alignof(T); + } + template <typename T> + static + typename std::enable_if<detail::is_complete<T>::value, std::size_t>::type + align_of() { + return do_align_of<T>(); + } +}; + +template <typename T> +std::size_t size_of() { + return layout::size_of<T>(); } -// 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 // CXXBRIDGE05_RUST_VEC +std::size_t align_of() { + return layout::align_of<T>(); +} +#endif // CXXBRIDGE1_LAYOUT -#ifndef CXXBRIDGE05_RELOCATABLE -#define CXXBRIDGE05_RELOCATABLE +#ifndef CXXBRIDGE1_RELOCATABLE +#define CXXBRIDGE1_RELOCATABLE namespace detail { template <typename... Ts> struct make_void { @@ -652,7 +1081,7 @@ struct IsRelocatable std::integral_constant< bool, std::is_trivially_move_constructible<T>::value && std::is_trivially_destructible<T>::value>>::type {}; -#endif // CXXBRIDGE05_RELOCATABLE +#endif // CXXBRIDGE1_RELOCATABLE -} // namespace cxxbridge05 +} // namespace cxxbridge1 } // namespace rust diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 6b07a073..a3df1018 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "cxxbridge-macro" -version = "0.5.9" +version = "1.0.42" 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" +homepage = "https://cxx.rs" exclude = ["build.rs", "README.md"] keywords = ["ffi"] categories = ["development-tools::ffi"] @@ -16,10 +17,10 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0.4" -syn = { version = "1.0.20", features = ["full"] } +syn = { version = "1.0.68", features = ["full"] } [dev-dependencies] -cxx = { version = "0.5", path = ".." } +cxx = { version = "1.0", path = ".." } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/macro/src/derive.rs b/macro/src/derive.rs index 1abc5dab..2f770951 100644 --- a/macro/src/derive.rs +++ b/macro/src/derive.rs @@ -1,14 +1,281 @@ -use crate::syntax::Derive; -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use crate::syntax::{derive, Enum, Struct, Trait}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, quote_spanned, ToTokens}; -pub struct DeriveAttribute<'a>(pub &'a [Derive]); +pub use crate::syntax::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),*)])); +pub fn expand_struct(strct: &Struct, actual_derives: &mut Option<TokenStream>) -> TokenStream { + let mut expanded = TokenStream::new(); + let mut traits = Vec::new(); + + for derive in &strct.derives { + let span = derive.span; + match derive.what { + Trait::Copy => expanded.extend(struct_copy(strct, span)), + Trait::Clone => expanded.extend(struct_clone(strct, span)), + Trait::Debug => expanded.extend(struct_debug(strct, span)), + Trait::Default => expanded.extend(struct_default(strct, span)), + Trait::Eq => traits.push(quote_spanned!(span=> ::std::cmp::Eq)), + Trait::ExternType => unreachable!(), + Trait::Hash => traits.push(quote_spanned!(span=> ::std::hash::Hash)), + Trait::Ord => expanded.extend(struct_ord(strct, span)), + Trait::PartialEq => traits.push(quote_spanned!(span=> ::std::cmp::PartialEq)), + Trait::PartialOrd => expanded.extend(struct_partial_ord(strct, span)), + } + } + + if traits.is_empty() { + *actual_derives = None; + } else { + *actual_derives = Some(quote!(#[derive(#(#traits),*)])); + } + + expanded +} + +pub fn expand_enum(enm: &Enum, actual_derives: &mut Option<TokenStream>) -> TokenStream { + let mut expanded = TokenStream::new(); + let mut traits = Vec::new(); + let mut has_copy = false; + let mut has_clone = false; + let mut has_eq = false; + let mut has_partial_eq = false; + + for derive in &enm.derives { + let span = derive.span; + match derive.what { + Trait::Copy => { + expanded.extend(enum_copy(enm, span)); + has_copy = true; + } + Trait::Clone => { + expanded.extend(enum_clone(enm, span)); + has_clone = true; + } + Trait::Debug => expanded.extend(enum_debug(enm, span)), + Trait::Default => unreachable!(), + Trait::Eq => { + traits.push(quote_spanned!(span=> ::std::cmp::Eq)); + has_eq = true; + } + Trait::ExternType => unreachable!(), + Trait::Hash => traits.push(quote_spanned!(span=> ::std::hash::Hash)), + Trait::Ord => expanded.extend(enum_ord(enm, span)), + Trait::PartialEq => { + traits.push(quote_spanned!(span=> ::std::cmp::PartialEq)); + has_partial_eq = true; + } + Trait::PartialOrd => expanded.extend(enum_partial_ord(enm, span)), + } + } + + let span = enm.name.rust.span(); + if !has_copy { + expanded.extend(enum_copy(enm, span)); + } + if !has_clone { + expanded.extend(enum_clone(enm, span)); + } + if !has_eq { + // Required to be derived in order for the enum's "variants" to be + // usable in patterns. + traits.push(quote!(::std::cmp::Eq)); + } + if !has_partial_eq { + traits.push(quote!(::std::cmp::PartialEq)); + } + + *actual_derives = Some(quote!(#[derive(#(#traits),*)])); + + expanded +} + +fn struct_copy(strct: &Struct, span: Span) -> TokenStream { + let ident = &strct.name.rust; + let generics = &strct.generics; + + quote_spanned! {span=> + impl #generics ::std::marker::Copy for #ident #generics {} + } +} + +fn struct_clone(strct: &Struct, span: Span) -> TokenStream { + let ident = &strct.name.rust; + let generics = &strct.generics; + + let body = if derive::contains(&strct.derives, Trait::Copy) { + quote!(*self) + } else { + let fields = strct.fields.iter().map(|field| &field.name.rust); + let values = strct.fields.iter().map(|field| { + let ident = &field.name.rust; + let ty = field.ty.to_token_stream(); + let span = ty.into_iter().last().unwrap().span(); + quote_spanned!(span=> &self.#ident) + }); + quote_spanned!(span=> #ident { + #(#fields: ::std::clone::Clone::clone(#values),)* + }) + }; + + quote_spanned! {span=> + impl #generics ::std::clone::Clone for #ident #generics { + fn clone(&self) -> Self { + #body + } + } + } +} + +fn struct_debug(strct: &Struct, span: Span) -> TokenStream { + let ident = &strct.name.rust; + let generics = &strct.generics; + let struct_name = ident.to_string(); + let fields = strct.fields.iter().map(|field| &field.name.rust); + let field_names = fields.clone().map(Ident::to_string); + + quote_spanned! {span=> + impl #generics ::std::fmt::Debug for #ident #generics { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.debug_struct(#struct_name) + #(.field(#field_names, &self.#fields))* + .finish() + } + } + } +} + +fn struct_default(strct: &Struct, span: Span) -> TokenStream { + let ident = &strct.name.rust; + let generics = &strct.generics; + let fields = strct.fields.iter().map(|field| &field.name.rust); + + quote_spanned! {span=> + impl #generics ::std::default::Default for #ident #generics { + fn default() -> Self { + #ident { + #( + #fields: ::std::default::Default::default(), + )* + } + } + } + } +} + +fn struct_ord(strct: &Struct, span: Span) -> TokenStream { + let ident = &strct.name.rust; + let generics = &strct.generics; + let fields = strct.fields.iter().map(|field| &field.name.rust); + + quote_spanned! {span=> + impl #generics ::std::cmp::Ord for #ident #generics { + fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { + #( + match ::std::cmp::Ord::cmp(&self.#fields, &other.#fields) { + ::std::cmp::Ordering::Equal => {} + ordering => return ordering, + } + )* + ::std::cmp::Ordering::Equal + } + } + } +} + +fn struct_partial_ord(strct: &Struct, span: Span) -> TokenStream { + let ident = &strct.name.rust; + let generics = &strct.generics; + + let body = if derive::contains(&strct.derives, Trait::Ord) { + quote! { + ::std::option::Option::Some(::std::cmp::Ord::cmp(self, other)) + } + } else { + let fields = strct.fields.iter().map(|field| &field.name.rust); + quote! { + #( + match ::std::cmp::PartialOrd::partial_cmp(&self.#fields, &other.#fields) { + ::std::option::Option::Some(::std::cmp::Ordering::Equal) => {} + ordering => return ordering, + } + )* + ::std::option::Option::Some(::std::cmp::Ordering::Equal) + } + }; + + quote_spanned! {span=> + impl #generics ::std::cmp::PartialOrd for #ident #generics { + fn partial_cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> { + #body + } + } + } +} + +fn enum_copy(enm: &Enum, span: Span) -> TokenStream { + let ident = &enm.name.rust; + + quote_spanned! {span=> + impl ::std::marker::Copy for #ident {} + } +} + +fn enum_clone(enm: &Enum, span: Span) -> TokenStream { + let ident = &enm.name.rust; + + quote_spanned! {span=> + impl ::std::clone::Clone for #ident { + fn clone(&self) -> Self { + *self + } + } + } +} + +fn enum_debug(enm: &Enum, span: Span) -> TokenStream { + let ident = &enm.name.rust; + let variants = enm.variants.iter().map(|variant| { + let variant = &variant.name.rust; + let name = variant.to_string(); + quote_spanned! {span=> + #ident::#variant => formatter.write_str(#name), + } + }); + let fallback = format!("{}({{}})", ident); + + quote_spanned! {span=> + impl ::std::fmt::Debug for #ident { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match *self { + #(#variants)* + _ => ::std::write!(formatter, #fallback, self.repr), + } + } + } + } +} + +fn enum_ord(enm: &Enum, span: Span) -> TokenStream { + let ident = &enm.name.rust; + + quote_spanned! {span=> + impl ::std::cmp::Ord for #ident { + fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { + ::std::cmp::Ord::cmp(&self.repr, &other.repr) + } + } + } +} + +fn enum_partial_ord(enm: &Enum, span: Span) -> TokenStream { + let ident = &enm.name.rust; + + quote_spanned! {span=> + impl ::std::cmp::PartialOrd for #ident { + fn partial_cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> { + ::std::cmp::PartialOrd::partial_cmp(&self.repr, &other.repr) + } } } } diff --git a/macro/src/expand.rs b/macro/src/expand.rs index 903585ec..12e4fe08 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -1,19 +1,34 @@ -use crate::derive::DeriveAttribute; -use crate::syntax::atom::Atom::{self, *}; +use crate::syntax::atom::Atom::*; +use crate::syntax::attrs::{self, OtherAttrs}; use crate::syntax::file::Module; +use crate::syntax::instantiate::{ImplKey, NamedImplKey}; +use crate::syntax::qualified::QualifiedName; use crate::syntax::report::Errors; use crate::syntax::symbol::Symbol; use crate::syntax::{ - self, check, mangle, Api, Enum, ExternFn, ExternType, Impl, Pair, ResolvableName, Signature, - Struct, Type, TypeAlias, Types, + self, check, mangle, Api, Doc, Enum, ExternFn, ExternType, Impl, Lifetimes, Pair, Signature, + Struct, Trait, Type, TypeAlias, Types, }; +use crate::type_id::Crate; +use crate::{derive, generics}; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use std::mem; -use syn::{parse_quote, Result, Token}; +use syn::{parse_quote, punctuated, Lifetime, Result, Token}; pub fn bridge(mut ffi: Module) -> Result<TokenStream> { let ref mut errors = Errors::new(); + + let mut doc = Doc::new(); + let attrs = attrs::parse( + errors, + mem::take(&mut ffi.attrs), + attrs::Parser { + doc: Some(&mut doc), + ..Default::default() + }, + ); + let content = mem::take(&mut ffi.content); let trusted = ffi.unsafety.is_some(); let namespace = &ffi.namespace; @@ -23,34 +38,44 @@ pub fn bridge(mut ffi: Module) -> Result<TokenStream> { check::typecheck(errors, apis, types); errors.propagate()?; - Ok(expand(ffi, apis, types)) + Ok(expand(ffi, doc, attrs, apis, types)) } -fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream { +fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types) -> TokenStream { let mut expanded = TokenStream::new(); let mut hidden = TokenStream::new(); + let mut forbid = TokenStream::new(); for api in apis { if let Api::RustType(ety) = api { - expanded.extend(expand_rust_type(ety)); + expanded.extend(expand_rust_type_import(ety)); hidden.extend(expand_rust_type_assert_sized(ety)); } } for api in apis { match api { - Api::Include(_) | Api::RustType(_) | Api::Impl(_) => {} - Api::Struct(strct) => expanded.extend(expand_struct(strct)), + Api::Include(_) | Api::Impl(_) => {} + Api::Struct(strct) => { + expanded.extend(expand_struct(strct)); + hidden.extend(expand_struct_operators(strct)); + forbid.extend(expand_struct_forbid_drop(strct)); + } Api::Enum(enm) => expanded.extend(expand_enum(enm)), Api::CxxType(ety) => { let ident = &ety.name.rust; if !types.structs.contains_key(ident) && !types.enums.contains_key(ident) { expanded.extend(expand_cxx_type(ety)); + hidden.extend(expand_cxx_type_assert_pinned(ety)); } } Api::CxxFunction(efn) => { expanded.extend(expand_cxx_function_shim(efn, types)); } + Api::RustType(ety) => { + expanded.extend(expand_rust_type_impl(ety)); + hidden.extend(expand_rust_type_layout(ety)); + } Api::RustFunction(efn) => hidden.extend(expand_rust_function_shim(efn, types)), Api::TypeAlias(alias) => { expanded.extend(expand_type_alias(alias)); @@ -59,42 +84,33 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream { } } - 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.rust).is_none() { - hidden.extend(expand_rust_box(ident, types)); - } + for (impl_key, &explicit_impl) in &types.impls { + match *impl_key { + ImplKey::RustBox(ident) => { + hidden.extend(expand_rust_box(ident, types, explicit_impl)); } - } else if let Type::RustVec(ty) = ty { - if let Type::Ident(ident) = &ty.inner { - if Atom::from(&ident.rust).is_none() { - hidden.extend(expand_rust_vec(ident, types)); - } + ImplKey::RustVec(ident) => { + hidden.extend(expand_rust_vec(ident, types, explicit_impl)); } - } else if let Type::UniquePtr(ptr) = ty { - if let Type::Ident(ident) = &ptr.inner { - 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)); - } + ImplKey::UniquePtr(ident) => { + 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.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(ident, explicit_impl, types)); - } + ImplKey::SharedPtr(ident) => { + expanded.extend(expand_shared_ptr(ident, types, explicit_impl)); + } + ImplKey::WeakPtr(ident) => { + expanded.extend(expand_weak_ptr(ident, types, explicit_impl)); + } + ImplKey::CxxVector(ident) => { + expanded.extend(expand_cxx_vector(ident, explicit_impl, types)); } } } + if !forbid.is_empty() { + hidden.extend(expand_forbid(forbid)); + } + // Work around https://github.com/rust-lang/rust/issues/67851. if !hidden.is_empty() { expanded.extend(quote! { @@ -105,70 +121,209 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream { }); } - let attrs = ffi - .attrs - .into_iter() - .filter(|attr| attr.path.is_ident("doc")); let vis = &ffi.vis; + let mod_token = &ffi.mod_token; let ident = &ffi.ident; + let span = ffi.brace_token.span; + let expanded = quote_spanned!(span=> {#expanded}); quote! { - #(#attrs)* + #doc + #attrs #[deny(improper_ctypes)] - #[allow(non_snake_case)] - #vis mod #ident { - #expanded - } + #[allow(non_camel_case_types, non_snake_case, clippy::upper_case_acronyms)] + #vis #mod_token #ident #expanded } } fn expand_struct(strct: &Struct) -> TokenStream { let ident = &strct.name.rust; let doc = &strct.doc; - let derives = DeriveAttribute(&strct.derives); + let attrs = &strct.attrs; + let generics = &strct.generics; let type_id = type_id(&strct.name); let fields = strct.fields.iter().map(|field| { + let doc = &field.doc; + let attrs = &field.attrs; // 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) + let vis = field.visibility; + quote!(#doc #attrs #vis #field) }); + let mut derives = None; + let derived_traits = derive::expand_struct(strct, &mut derives); + + let span = ident.span(); + let visibility = strct.visibility; + let struct_token = strct.struct_token; + let struct_def = quote_spanned! {span=> + #visibility #struct_token #ident #generics { + #(#fields,)* + } + }; quote! { #doc + #attrs #derives #[repr(C)] - pub struct #ident { - #(#fields,)* - } + #struct_def - unsafe impl ::cxx::ExternType for #ident { + unsafe impl #generics ::cxx::ExternType for #ident #generics { + #[doc(hidden)] type Id = #type_id; type Kind = ::cxx::kind::Trivial; } + + #derived_traits + } +} + +fn expand_struct_operators(strct: &Struct) -> TokenStream { + let ident = &strct.name.rust; + let mut operators = TokenStream::new(); + + for derive in &strct.derives { + let span = derive.span; + match derive.what { + Trait::PartialEq => { + let link_name = mangle::operator(&strct.name, "eq"); + let local_name = format_ident!("__operator_eq_{}", strct.name.rust); + operators.extend(quote_spanned! {span=> + #[doc(hidden)] + #[export_name = #link_name] + extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool { + *lhs == *rhs + } + }); + + if !derive::contains(&strct.derives, Trait::Eq) { + let link_name = mangle::operator(&strct.name, "ne"); + let local_name = format_ident!("__operator_ne_{}", strct.name.rust); + operators.extend(quote_spanned! {span=> + #[doc(hidden)] + #[export_name = #link_name] + extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool { + *lhs != *rhs + } + }); + } + } + Trait::PartialOrd => { + let link_name = mangle::operator(&strct.name, "lt"); + let local_name = format_ident!("__operator_lt_{}", strct.name.rust); + operators.extend(quote_spanned! {span=> + #[doc(hidden)] + #[export_name = #link_name] + extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool { + *lhs < *rhs + } + }); + + let link_name = mangle::operator(&strct.name, "le"); + let local_name = format_ident!("__operator_le_{}", strct.name.rust); + operators.extend(quote_spanned! {span=> + #[doc(hidden)] + #[export_name = #link_name] + extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool { + *lhs <= *rhs + } + }); + + if !derive::contains(&strct.derives, Trait::Ord) { + let link_name = mangle::operator(&strct.name, "gt"); + let local_name = format_ident!("__operator_gt_{}", strct.name.rust); + operators.extend(quote_spanned! {span=> + #[doc(hidden)] + #[export_name = #link_name] + extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool { + *lhs > *rhs + } + }); + + let link_name = mangle::operator(&strct.name, "ge"); + let local_name = format_ident!("__operator_ge_{}", strct.name.rust); + operators.extend(quote_spanned! {span=> + #[doc(hidden)] + #[export_name = #link_name] + extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool { + *lhs >= *rhs + } + }); + } + } + Trait::Hash => { + let link_name = mangle::operator(&strct.name, "hash"); + let local_name = format_ident!("__operator_hash_{}", strct.name.rust); + operators.extend(quote_spanned! {span=> + #[doc(hidden)] + #[export_name = #link_name] + #[allow(clippy::cast_possible_truncation)] + extern "C" fn #local_name(this: &#ident) -> usize { + let mut hasher = ::std::collections::hash_map::DefaultHasher::new(); + ::std::hash::Hash::hash(this, &mut hasher); + ::std::hash::Hasher::finish(&hasher) as usize + } + }); + } + _ => {} + } + } + + operators +} + +fn expand_struct_forbid_drop(strct: &Struct) -> TokenStream { + let ident = &strct.name.rust; + let generics = &strct.generics; + let span = ident.span(); + let impl_token = Token![impl](strct.visibility.span); + + quote_spanned! {span=> + #impl_token #generics self::Drop for super::#ident #generics {} } } fn expand_enum(enm: &Enum) -> TokenStream { let ident = &enm.name.rust; let doc = &enm.doc; + let attrs = &enm.attrs; let repr = enm.repr; let type_id = type_id(&enm.name); let variants = enm.variants.iter().map(|variant| { - let variant_ident = &variant.ident; + let doc = &variant.doc; + let attrs = &variant.attrs; + let variant_ident = &variant.name.rust; let discriminant = &variant.discriminant; - Some(quote! { + let span = variant_ident.span(); + Some(quote_spanned! {span=> + #doc + #attrs pub const #variant_ident: Self = #ident { repr: #discriminant }; }) }); + let mut derives = None; + let derived_traits = derive::expand_enum(enm, &mut derives); + + let span = ident.span(); + let visibility = enm.visibility; + let struct_token = Token![struct](enm.enum_token.span); + let enum_repr = quote! { + #[allow(missing_docs)] + pub repr: #repr, + }; + let enum_def = quote_spanned! {span=> + #visibility #struct_token #ident { + #enum_repr + } + }; quote! { #doc - #[derive(Copy, Clone, PartialEq, Eq)] + #attrs + #derives #[repr(transparent)] - pub struct #ident { - pub repr: #repr, - } + #enum_def #[allow(non_upper_case_globals)] impl #ident { @@ -176,38 +331,95 @@ fn expand_enum(enm: &Enum) -> TokenStream { } unsafe impl ::cxx::ExternType for #ident { + #[doc(hidden)] type Id = #type_id; type Kind = ::cxx::kind::Trivial; } + + #derived_traits } } fn expand_cxx_type(ety: &ExternType) -> TokenStream { let ident = &ety.name.rust; let doc = &ety.doc; + let attrs = &ety.attrs; + let generics = &ety.generics; let type_id = type_id(&ety.name); + let lifetime_fields = ety.generics.lifetimes.iter().map(|lifetime| { + let field = format_ident!("_lifetime_{}", lifetime.ident); + quote!(#field: ::std::marker::PhantomData<&#lifetime ()>) + }); + let repr_fields = quote! { + _private: ::cxx::private::Opaque, + #(#lifetime_fields,)* + }; + + let span = ident.span(); + let visibility = &ety.visibility; + let struct_token = Token![struct](ety.type_token.span); + let extern_type_def = quote_spanned! {span=> + #visibility #struct_token #ident #generics { + #repr_fields + } + }; + quote! { #doc + #attrs #[repr(C)] - pub struct #ident { - _private: ::cxx::private::Opaque, - } + #extern_type_def - unsafe impl ::cxx::ExternType for #ident { + unsafe impl #generics ::cxx::ExternType for #ident #generics { + #[doc(hidden)] type Id = #type_id; type Kind = ::cxx::kind::Opaque; } } } +fn expand_cxx_type_assert_pinned(ety: &ExternType) -> TokenStream { + let ident = &ety.name.rust; + let infer = Token![_](ident.span()); + + quote! { + let _ = { + // Derived from https://github.com/nvzqz/static-assertions-rs. + trait __AmbiguousIfImpl<A> { + fn infer() {} + } + + impl<T> __AmbiguousIfImpl<()> for T + where + T: ?::std::marker::Sized + {} + + #[allow(dead_code)] + struct __Invalid; + + impl<T> __AmbiguousIfImpl<__Invalid> for T + where + T: ?::std::marker::Sized + ::std::marker::Unpin, + {} + + // If there is only one specialized trait impl, type inference with + // `_` can be resolved and this can compile. Fails to compile if + // user has added a manual Unpin impl for their opaque C++ type as + // then `__AmbiguousIfImpl<__Invalid>` also exists. + <#ident as __AmbiguousIfImpl<#infer>>::infer + }; + } +} + fn expand_cxx_function_decl(efn: &ExternFn, types: &Types) -> TokenStream { + let generics = &efn.generics; 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 ident = &arg.name.rust; let ty = expand_extern_type(&arg.ty, types, true); if arg.ty == RustString { quote!(#ident: *const #ty) @@ -236,18 +448,25 @@ fn expand_cxx_function_decl(efn: &ExternFn, types: &Types) -> TokenStream { let local_name = format_ident!("__{}", efn.name.rust); quote! { #[link_name = #link_name] - fn #local_name(#(#all_args,)* #outparam) #ret; + fn #local_name #generics(#(#all_args,)* #outparam) #ret; } } fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { let doc = &efn.doc; + let attrs = &efn.attrs; let decl = expand_cxx_function_decl(efn, types); let receiver = efn.receiver.iter().map(|receiver| { - let ampersand = receiver.ampersand; - let mutability = receiver.mutability; let var = receiver.var; - quote!(#ampersand #mutability #var) + if receiver.pinned { + let ty = receiver.ty_self(); + quote!(#var: #ty) + } else { + let ampersand = receiver.ampersand; + let lifetime = &receiver.lifetime; + let mutability = receiver.mutability; + quote!(#ampersand #lifetime #mutability #var) + } }); let args = efn.args.iter().map(|arg| quote!(#arg)); let all_args = receiver.chain(args); @@ -266,7 +485,7 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { .iter() .map(|receiver| receiver.var.to_token_stream()); let arg_vars = efn.args.iter().map(|arg| { - let var = &arg.ident; + let var = &arg.name.rust; match &arg.ty { Type::Ident(ident) if ident.rust == RustString => { quote!(#var.as_mut_ptr() as *const ::cxx::private::RustString) @@ -275,26 +494,44 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { 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.rust == RustString => match ty.mutability { - None => quote!(::cxx::private::RustString::from_ref(#var)), - Some(_) => quote!(::cxx::private::RustString::from_mut(#var)), + Type::Ident(ident) if ident.rust == RustString => match ty.mutable { + false => quote!(::cxx::private::RustString::from_ref(#var)), + true => 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(vec) if vec.inner == RustString => match ty.mutable { + false => quote!(::cxx::private::RustVec::from_ref_vec_string(#var)), + true => 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), + Type::RustVec(_) => match ty.mutable { + false => quote!(::cxx::private::RustVec::from_ref(#var)), + true => quote!(::cxx::private::RustVec::from_mut(#var)), }, + inner if types.is_considered_improper_ctype(inner) => { + let var = match ty.pinned { + false => quote!(#var), + true => quote!(::std::pin::Pin::into_inner_unchecked(#var)), + }; + match ty.mutable { + false => { + quote!(#var as *const #inner as *const ::std::ffi::c_void) + } + true => quote!(#var as *mut #inner as *mut ::std::ffi::c_void), + } + } _ => quote!(#var), }, + Type::Ptr(ty) => { + if types.is_considered_improper_ctype(&ty.inner) { + quote!(#var.cast()) + } else { + quote!(#var) + } + } Type::Str(_) => quote!(::cxx::private::RustStr::from(#var)), - Type::SliceRefU8(_) => quote!(::cxx::private::RustSliceU8::from(#var)), + Type::SliceRef(ty) => match ty.mutable { + false => quote!(::cxx::private::RustSlice::from_ref(#var)), + true => quote!(::cxx::private::RustSlice::from_mut(#var)), + }, ty if types.needs_indirect_abi(ty) => quote!(#var.as_mut_ptr()), _ => quote!(#var), } @@ -305,7 +542,7 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { .iter() .filter_map(|arg| { if let Type::Fn(f) = &arg.ty { - let var = &arg.ident; + let var = &arg.name; Some(expand_function_pointer_trampoline(efn, var, f, types)) } else { None @@ -317,7 +554,7 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { .iter() .filter(|arg| types.needs_indirect_abi(&arg.ty)) .map(|arg| { - let var = &arg.ident; + let var = &arg.name.rust; // These are arguments for which C++ has taken ownership of the data // behind the mut reference it received. quote! { @@ -368,26 +605,43 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { } 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::Ident(ident) if ident.rust == RustString => match ty.mutable { + false => quote!(#call.as_string()), + true => 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(vec) if vec.inner == RustString => match ty.mutable { + false => quote!(#call.as_vec_string()), + true => quote!(#call.as_mut_vec_string()), }, - Type::RustVec(_) => match ty.mutability { - None => quote!(#call.as_vec()), - Some(_) => quote!(#call.as_mut_vec()), + Type::RustVec(_) => match ty.mutable { + false => quote!(#call.as_vec()), + true => quote!(#call.as_mut_vec()), }, inner if types.is_considered_improper_ctype(inner) => { let mutability = ty.mutability; - quote!(&#mutability *#call.cast()) + let deref_mut = quote!(&#mutability *#call.cast()); + match ty.pinned { + false => deref_mut, + true => quote!(::std::pin::Pin::new_unchecked(#deref_mut)), + } } _ => call, }, + Type::Ptr(ty) => { + if types.is_considered_improper_ctype(&ty.inner) { + quote!(#call.cast()) + } else { + call + } + } Type::Str(_) => quote!(#call.as_str()), - Type::SliceRefU8(_) => quote!(#call.as_slice()), + Type::SliceRef(slice) => { + let inner = &slice.inner; + match slice.mutable { + false => quote!(#call.as_slice::<#inner>()), + true => quote!(#call.as_mut_slice::<#inner>()), + } + } _ => call, }, }; @@ -396,40 +650,74 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { } }; let mut dispatch = quote!(#setup #expr); + let visibility = efn.visibility; let unsafety = &efn.sig.unsafety; if unsafety.is_none() { dispatch = quote!(unsafe { #dispatch }); } + let fn_token = efn.sig.fn_token; let ident = &efn.name.rust; - let function_shim = quote! { - #doc - pub #unsafety fn #ident(#(#all_args,)*) #ret { - extern "C" { - #decl - } - #trampolines - #dispatch + let generics = &efn.generics; + let arg_list = quote_spanned!(efn.sig.paren_token.span=> (#(#all_args,)*)); + let fn_body = quote_spanned!(efn.semi_token.span=> { + extern "C" { + #decl } - }; + #trampolines + #dispatch + }); match &efn.receiver { - None => function_shim, + None => { + quote! { + #doc + #attrs + #visibility #unsafety #fn_token #ident #generics #arg_list #ret #fn_body + } + } Some(receiver) => { - let receiver_type = &receiver.ty; - quote!(impl #receiver_type { #function_shim }) + let elided_generics; + let receiver_ident = &receiver.ty.rust; + let resolve = types.resolve(&receiver.ty); + let receiver_generics = if receiver.ty.generics.lt_token.is_some() { + &receiver.ty.generics + } else { + elided_generics = Lifetimes { + lt_token: resolve.generics.lt_token, + lifetimes: resolve + .generics + .lifetimes + .pairs() + .map(|pair| { + let lifetime = Lifetime::new("'_", pair.value().apostrophe); + let punct = pair.punct().map(|&&comma| comma); + punctuated::Pair::new(lifetime, punct) + }) + .collect(), + gt_token: resolve.generics.gt_token, + }; + &elided_generics + }; + quote! { + impl #generics #receiver_ident #receiver_generics { + #doc + #attrs + #visibility #unsafety #fn_token #ident #arg_list #ret #fn_body + } + } } } } fn expand_function_pointer_trampoline( efn: &ExternFn, - var: &Ident, + var: &Pair, sig: &Signature, types: &Types, ) -> TokenStream { 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.name.rust, var); + let catch_unwind_label = format!("::{}::{}", efn.name.rust, var.rust); let shim = expand_rust_function_shim_impl( sig, types, @@ -438,6 +726,7 @@ fn expand_function_pointer_trampoline( catch_unwind_label, None, ); + let var = &var.rust; quote! { let #var = ::cxx::private::FatFunction { @@ -454,13 +743,43 @@ fn expand_function_pointer_trampoline( } } -fn expand_rust_type(ety: &ExternType) -> TokenStream { +fn expand_rust_type_import(ety: &ExternType) -> TokenStream { let ident = &ety.name.rust; - quote! { + let span = ident.span(); + + quote_spanned! {span=> use super::#ident; } } +fn expand_rust_type_impl(ety: &ExternType) -> TokenStream { + let ident = &ety.name.rust; + let generics = &ety.generics; + let span = ident.span(); + let unsafe_impl = quote_spanned!(ety.type_token.span=> unsafe impl); + + let mut impls = quote_spanned! {span=> + #[doc(hidden)] + #unsafe_impl #generics ::cxx::private::RustType for #ident #generics {} + }; + + for derive in &ety.derives { + if derive.what == Trait::ExternType { + let type_id = type_id(&ety.name); + let span = derive.span; + impls.extend(quote_spanned! {span=> + unsafe impl #generics ::cxx::ExternType for #ident #generics { + #[doc(hidden)] + type Id = #type_id; + type Kind = ::cxx::kind::Opaque; + } + }); + } + } + + impls +} + fn expand_rust_type_assert_sized(ety: &ExternType) -> TokenStream { // Rustc will render as follows if not sized: // @@ -472,21 +791,71 @@ fn expand_rust_type_assert_sized(ety: &ExternType) -> TokenStream { 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 + let unpin = quote_spanned! {ety.semi_token.span=> + #begin_span std::marker::Unpin }; quote_spanned! {ident.span()=> let _ = { - fn __AssertSized<T: ?#sized + #sized>() {} - __AssertSized::<#ident> + fn __AssertUnpin<T: ?::std::marker::Sized + #unpin>() {} + __AssertUnpin::<#ident> }; } } +fn expand_rust_type_layout(ety: &ExternType) -> TokenStream { + 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 + }; + + let link_sizeof = mangle::operator(&ety.name, "sizeof"); + let link_alignof = mangle::operator(&ety.name, "alignof"); + + let local_sizeof = format_ident!("__sizeof_{}", ety.name.rust); + let local_alignof = format_ident!("__alignof_{}", ety.name.rust); + + quote_spanned! {ident.span()=> + { + #[doc(hidden)] + fn __AssertSized<T: ?#sized + #sized>() -> ::std::alloc::Layout { + ::std::alloc::Layout::new::<T>() + } + #[doc(hidden)] + #[export_name = #link_sizeof] + extern "C" fn #local_sizeof() -> usize { + __AssertSized::<#ident>().size() + } + #[doc(hidden)] + #[export_name = #link_alignof] + extern "C" fn #local_alignof() -> usize { + __AssertSized::<#ident>().align() + } + } + } +} + +fn expand_forbid(impls: TokenStream) -> TokenStream { + quote! { + mod forbid { + pub trait Drop {} + #[allow(drop_bounds)] + impl<T: ?::std::marker::Sized + ::std::ops::Drop> self::Drop for T {} + #impls + } + } +} + 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 local_name = match &efn.receiver { + None => format_ident!("__{}", efn.name.rust), + Some(receiver) => format_ident!("__{}__{}", receiver.ty.rust, efn.name.rust), + }; + let catch_unwind_label = match &efn.receiver { + None => format!("::{}", efn.name.rust), + Some(receiver) => format!("::{}::{}", receiver.ty.rust, efn.name.rust), + }; let invoke = Some(&efn.name.rust); expand_rust_function_shim_impl( efn, @@ -506,6 +875,7 @@ fn expand_rust_function_shim_impl( catch_unwind_label: String, invoke: Option<&Ident>, ) -> TokenStream { + let generics = &sig.generics; let receiver_var = sig .receiver .as_ref() @@ -515,7 +885,7 @@ fn expand_rust_function_shim_impl( quote!(#receiver_var: #receiver_type) }); let args = sig.args.iter().map(|arg| { - let ident = &arg.ident; + let ident = &arg.name.rust; let ty = expand_extern_type(&arg.ty, types, false); if types.needs_indirect_abi(&arg.ty) { quote!(#ident: *mut #ty) @@ -526,7 +896,7 @@ fn expand_rust_function_shim_impl( let all_args = receiver.into_iter().chain(args); let arg_vars = sig.args.iter().map(|arg| { - let ident = &arg.ident; + let ident = &arg.name.rust; match &arg.ty { Type::Ident(i) if i.rust == RustString => { quote!(::std::mem::take((*#ident).as_mut_string())) @@ -541,22 +911,28 @@ fn expand_rust_function_shim_impl( } Type::UniquePtr(_) => quote!(::cxx::UniquePtr::from_raw(#ident)), Type::Ref(ty) => match &ty.inner { - Type::Ident(i) if i.rust == RustString => match ty.mutability { - None => quote!(#ident.as_string()), - Some(_) => quote!(#ident.as_mut_string()), + Type::Ident(i) if i.rust == RustString => match ty.mutable { + false => quote!(#ident.as_string()), + true => 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(vec) if vec.inner == RustString => match ty.mutable { + false => quote!(#ident.as_vec_string()), + true => quote!(#ident.as_mut_vec_string()), }, - Type::RustVec(_) => match ty.mutability { - None => quote!(#ident.as_vec()), - Some(_) => quote!(#ident.as_mut_vec()), + Type::RustVec(_) => match ty.mutable { + false => quote!(#ident.as_vec()), + true => quote!(#ident.as_mut_vec()), }, _ => quote!(#ident), }, Type::Str(_) => quote!(#ident.as_str()), - Type::SliceRefU8(_) => quote!(#ident.as_slice()), + Type::SliceRef(slice) => { + let inner = &slice.inner; + match slice.mutable { + false => quote!(#ident.as_slice::<#inner>()), + true => quote!(#ident.as_mut_slice::<#inner>()), + } + } ty if types.needs_indirect_abi(ty) => quote!(::std::ptr::read(#ident)), _ => quote!(#ident), } @@ -585,22 +961,25 @@ fn expand_rust_function_shim_impl( } 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::Ident(ident) if ident.rust == RustString => match ty.mutable { + false => Some(quote!(::cxx::private::RustString::from_ref)), + true => Some(quote!(::cxx::private::RustString::from_mut)), }, - 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(vec) if vec.inner == RustString => match ty.mutable { + false => Some(quote!(::cxx::private::RustVec::from_ref_vec_string)), + true => 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)), + Type::RustVec(_) => match ty.mutable { + false => Some(quote!(::cxx::private::RustVec::from_ref)), + true => Some(quote!(::cxx::private::RustVec::from_mut)), }, _ => None, }, Type::Str(_) => Some(quote!(::cxx::private::RustStr::from)), - Type::SliceRefU8(_) => Some(quote!(::cxx::private::RustSliceU8::from)), + Type::SliceRef(ty) => match ty.mutable { + false => Some(quote!(::cxx::private::RustSlice::from_ref)), + true => Some(quote!(::cxx::private::RustSlice::from_mut)), + }, _ => None, }); @@ -642,7 +1021,7 @@ fn expand_rust_function_shim_impl( quote! { #[doc(hidden)] #[export_name = #link_name] - unsafe extern "C" fn #local_name(#(#all_args,)* #outparam #pointer) #ret { + unsafe extern "C" fn #local_name #generics(#(#all_args,)* #outparam #pointer) #ret { let __fn = concat!(module_path!(), #catch_unwind_label); #wrap_super #expr @@ -658,6 +1037,7 @@ fn expand_rust_function_shim_super( invoke: &Ident, ) -> TokenStream { let unsafety = sig.unsafety; + let generics = &sig.generics; let receiver_var = sig .receiver @@ -682,20 +1062,20 @@ fn expand_rust_function_shim_super( expand_return_type(&sig.ret) }; - let arg_vars = sig.args.iter().map(|arg| &arg.ident); + let arg_vars = sig.args.iter().map(|arg| &arg.name.rust); 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; + let receiver_type = &receiver.ty.rust; quote_spanned!(span=> #receiver_type::#invoke) } }; quote_spanned! {span=> - #unsafety fn #local_name(#(#all_args,)*) #ret { + #unsafety fn #local_name #generics(#(#all_args,)*) #ret { #call(#(#vars,)*) } } @@ -703,11 +1083,19 @@ fn expand_rust_function_shim_super( fn expand_type_alias(alias: &TypeAlias) -> TokenStream { let doc = &alias.doc; + let attrs = &alias.attrs; + let visibility = alias.visibility; + let type_token = alias.type_token; let ident = &alias.name.rust; + let generics = &alias.generics; + let eq_token = alias.eq_token; let ty = &alias.ty; + let semi_token = alias.semi_token; + quote! { #doc - pub type #ident = #ty; + #attrs + #visibility #type_token #ident #generics #eq_token #ty #semi_token } } @@ -734,139 +1122,171 @@ fn expand_type_alias_verify(alias: &TypeAlias, types: &Types) -> TokenStream { } fn type_id(name: &Pair) -> TokenStream { - let path = name.to_fully_qualified(); - quote! { - ::cxx::type_id!(#path) - } + let namespace_segments = name.namespace.iter(); + let mut segments = Vec::with_capacity(namespace_segments.len() + 1); + segments.extend(namespace_segments.cloned()); + segments.push(Ident::new(&name.cxx.to_string(), Span::call_site())); + let qualified = QualifiedName { segments }; + crate::type_id::expand(Crate::Cxx, qualified) } -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); +fn expand_rust_box(key: NamedImplKey, types: &Types, explicit_impl: Option<&Impl>) -> TokenStream { + let ident = key.rust; + let resolve = types.resolve(ident); + let link_prefix = format!("cxxbridge1$box${}$", resolve.name.to_symbol()); + let link_alloc = format!("{}alloc", link_prefix); + let link_dealloc = format!("{}dealloc", link_prefix); let link_drop = format!("{}drop", link_prefix); - let local_prefix = format_ident!("{}__box_", &ident.rust); - let local_uninit = format_ident!("{}uninit", local_prefix); + let local_prefix = format_ident!("{}__box_", ident); + let local_alloc = format_ident!("{}alloc", local_prefix); + let local_dealloc = format_ident!("{}dealloc", local_prefix); let local_drop = format_ident!("{}drop", local_prefix); - let span = ident.span(); - quote_spanned! {span=> + let (impl_generics, ty_generics) = generics::split_for_impl(key, explicit_impl, resolve); + + let begin_span = explicit_impl.map_or(key.begin_span, |explicit| explicit.impl_token.span); + let end_span = explicit_impl.map_or(key.end_span, |explicit| explicit.brace_token.span); + let unsafe_token = format_ident!("unsafe", span = begin_span); + + quote_spanned! {end_span=> + #[doc(hidden)] + #unsafe_token impl #impl_generics ::cxx::private::ImplBox for #ident #ty_generics {} #[doc(hidden)] - #[export_name = #link_uninit] - unsafe extern "C" fn #local_uninit( - this: *mut ::std::boxed::Box<::std::mem::MaybeUninit<#ident>>, - ) { - ::std::ptr::write( - this, - ::std::boxed::Box::new(::std::mem::MaybeUninit::uninit()), - ); + #[export_name = #link_alloc] + unsafe extern "C" fn #local_alloc #impl_generics() -> *mut ::std::mem::MaybeUninit<#ident #ty_generics> { + ::std::boxed::Box::into_raw(::std::boxed::Box::new(::std::mem::MaybeUninit::uninit())) + } + #[doc(hidden)] + #[export_name = #link_dealloc] + unsafe extern "C" fn #local_dealloc #impl_generics(ptr: *mut ::std::mem::MaybeUninit<#ident #ty_generics>) { + ::std::boxed::Box::from_raw(ptr); } #[doc(hidden)] #[export_name = #link_drop] - unsafe extern "C" fn #local_drop(this: *mut ::std::boxed::Box<#ident>) { + unsafe extern "C" fn #local_drop #impl_generics(this: *mut ::std::boxed::Box<#ident #ty_generics>) { ::std::ptr::drop_in_place(this); } } } -fn expand_rust_vec(elem: &ResolvableName, types: &Types) -> TokenStream { - let link_prefix = format!("cxxbridge05$rust_vec${}$", elem.to_symbol(types)); +fn expand_rust_vec(key: NamedImplKey, types: &Types, explicit_impl: Option<&Impl>) -> TokenStream { + let elem = key.rust; + let resolve = types.resolve(elem); + let link_prefix = format!("cxxbridge1$rust_vec${}$", resolve.name.to_symbol()); let link_new = format!("{}new", link_prefix); let link_drop = format!("{}drop", link_prefix); let link_len = format!("{}len", link_prefix); + let link_capacity = format!("{}capacity", 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.rust); + let local_prefix = format_ident!("{}__vec_", elem); 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_capacity = format_ident!("{}capacity", 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(); - quote_spanned! {span=> + let (impl_generics, ty_generics) = generics::split_for_impl(key, explicit_impl, resolve); + + let begin_span = explicit_impl.map_or(key.begin_span, |explicit| explicit.impl_token.span); + let end_span = explicit_impl.map_or(key.end_span, |explicit| explicit.brace_token.span); + let unsafe_token = format_ident!("unsafe", span = begin_span); + + quote_spanned! {end_span=> + #[doc(hidden)] + #unsafe_token impl #impl_generics ::cxx::private::ImplVec for #elem #ty_generics {} #[doc(hidden)] #[export_name = #link_new] - unsafe extern "C" fn #local_new(this: *mut ::cxx::private::RustVec<#elem>) { + unsafe extern "C" fn #local_new #impl_generics(this: *mut ::cxx::private::RustVec<#elem #ty_generics>) { ::std::ptr::write(this, ::cxx::private::RustVec::new()); } #[doc(hidden)] #[export_name = #link_drop] - unsafe extern "C" fn #local_drop(this: *mut ::cxx::private::RustVec<#elem>) { + unsafe extern "C" fn #local_drop #impl_generics(this: *mut ::cxx::private::RustVec<#elem #ty_generics>) { ::std::ptr::drop_in_place(this); } #[doc(hidden)] #[export_name = #link_len] - unsafe extern "C" fn #local_len(this: *const ::cxx::private::RustVec<#elem>) -> usize { + unsafe extern "C" fn #local_len #impl_generics(this: *const ::cxx::private::RustVec<#elem #ty_generics>) -> usize { (*this).len() } #[doc(hidden)] + #[export_name = #link_capacity] + unsafe extern "C" fn #local_capacity #impl_generics(this: *const ::cxx::private::RustVec<#elem #ty_generics>) -> usize { + (*this).capacity() + } + #[doc(hidden)] #[export_name = #link_data] - unsafe extern "C" fn #local_data(this: *const ::cxx::private::RustVec<#elem>) -> *const #elem { + unsafe extern "C" fn #local_data #impl_generics(this: *const ::cxx::private::RustVec<#elem #ty_generics>) -> *const #elem #ty_generics { (*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) { + unsafe extern "C" fn #local_reserve_total #impl_generics(this: *mut ::cxx::private::RustVec<#elem #ty_generics>, 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) { + unsafe extern "C" fn #local_set_len #impl_generics(this: *mut ::cxx::private::RustVec<#elem #ty_generics>, len: usize) { (*this).set_len(len); } - #[doc(hidden)] - #[export_name = #link_stride] - unsafe extern "C" fn #local_stride() -> usize { - ::std::mem::size_of::<#elem>() - } } } fn expand_unique_ptr( - ident: &ResolvableName, + key: NamedImplKey, types: &Types, explicit_impl: Option<&Impl>, ) -> TokenStream { - let name = ident.rust.to_string(); - let prefix = format!("cxxbridge05$unique_ptr${}$", ident.to_symbol(types)); + let ident = key.rust; + let name = ident.to_string(); + let resolve = types.resolve(ident); + let prefix = format!("cxxbridge1$unique_ptr${}$", resolve.name.to_symbol()); let link_null = format!("{}null", prefix); - let link_new = format!("{}new", prefix); + let link_uninit = format!("{}uninit", prefix); let link_raw = format!("{}raw", prefix); let link_get = format!("{}get", prefix); let link_release = format!("{}release", prefix); let link_drop = format!("{}drop", prefix); - 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 (impl_generics, ty_generics) = generics::split_for_impl(key, explicit_impl, resolve); + + let can_construct_from_value = types.structs.contains_key(ident) + || types.enums.contains_key(ident) + || types.aliases.contains_key(ident); + let new_method = if can_construct_from_value { + Some(quote! { + #[doc(hidden)] + fn __new(value: Self) -> *mut ::std::ffi::c_void { + extern "C" { + #[link_name = #link_uninit] + fn __uninit(this: *mut *mut ::std::ffi::c_void) -> *mut ::std::ffi::c_void; } - }) - } else { - None - }; + let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>(); + unsafe { __uninit(&mut repr).cast::<#ident #ty_generics>().write(value) } + repr + } + }) + } else { + None + }; - 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 begin_span = explicit_impl.map_or(key.begin_span, |explicit| explicit.impl_token.span); + let end_span = explicit_impl.map_or(key.end_span, |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; + #unsafe_token impl #impl_generics ::cxx::private::UniquePtrTarget for #ident #ty_generics { + #[doc(hidden)] + fn __typename(f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.write_str(#name) + } + #[doc(hidden)] fn __null() -> *mut ::std::ffi::c_void { extern "C" { #[link_name = #link_null] @@ -877,29 +1297,33 @@ fn expand_unique_ptr( repr } #new_method + #[doc(hidden)] unsafe fn __raw(raw: *mut Self) -> *mut ::std::ffi::c_void { extern "C" { #[link_name = #link_raw] - fn __raw(this: *mut *mut ::std::ffi::c_void, raw: *mut #ident); + fn __raw(this: *mut *mut ::std::ffi::c_void, raw: *mut ::std::ffi::c_void); } let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>(); - __raw(&mut repr, raw); + __raw(&mut repr, raw.cast()); repr } + #[doc(hidden)] unsafe fn __get(repr: *mut ::std::ffi::c_void) -> *const Self { extern "C" { #[link_name = #link_get] - fn __get(this: *const *mut ::std::ffi::c_void) -> *const #ident; + fn __get(this: *const *mut ::std::ffi::c_void) -> *const ::std::ffi::c_void; } - __get(&repr) + __get(&repr).cast() } + #[doc(hidden)] unsafe fn __release(mut repr: *mut ::std::ffi::c_void) -> *mut Self { extern "C" { #[link_name = #link_release] - fn __release(this: *mut *mut ::std::ffi::c_void) -> *mut #ident; + fn __release(this: *mut *mut ::std::ffi::c_void) -> *mut ::std::ffi::c_void; } - __release(&mut repr) + __release(&mut repr).cast() } + #[doc(hidden)] unsafe fn __drop(mut repr: *mut ::std::ffi::c_void) { extern "C" { #[link_name = #link_drop] @@ -911,19 +1335,169 @@ fn expand_unique_ptr( } } +fn expand_shared_ptr( + key: NamedImplKey, + types: &Types, + explicit_impl: Option<&Impl>, +) -> TokenStream { + let ident = key.rust; + let name = ident.to_string(); + let resolve = types.resolve(ident); + let prefix = format!("cxxbridge1$shared_ptr${}$", resolve.name.to_symbol()); + let link_null = format!("{}null", prefix); + let link_uninit = format!("{}uninit", prefix); + let link_clone = format!("{}clone", prefix); + let link_get = format!("{}get", prefix); + let link_drop = format!("{}drop", prefix); + + let (impl_generics, ty_generics) = generics::split_for_impl(key, explicit_impl, resolve); + + let can_construct_from_value = types.structs.contains_key(ident) + || types.enums.contains_key(ident) + || types.aliases.contains_key(ident); + let new_method = if can_construct_from_value { + Some(quote! { + #[doc(hidden)] + unsafe fn __new(value: Self, new: *mut ::std::ffi::c_void) { + extern "C" { + #[link_name = #link_uninit] + fn __uninit(new: *mut ::std::ffi::c_void) -> *mut ::std::ffi::c_void; + } + __uninit(new).cast::<#ident #ty_generics>().write(value); + } + }) + } else { + None + }; + + let begin_span = explicit_impl.map_or(key.begin_span, |explicit| explicit.impl_token.span); + let end_span = explicit_impl.map_or(key.end_span, |explicit| explicit.brace_token.span); + let unsafe_token = format_ident!("unsafe", span = begin_span); + + quote_spanned! {end_span=> + #unsafe_token impl #impl_generics ::cxx::private::SharedPtrTarget for #ident #ty_generics { + #[doc(hidden)] + fn __typename(f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.write_str(#name) + } + #[doc(hidden)] + unsafe fn __null(new: *mut ::std::ffi::c_void) { + extern "C" { + #[link_name = #link_null] + fn __null(new: *mut ::std::ffi::c_void); + } + __null(new); + } + #new_method + #[doc(hidden)] + unsafe fn __clone(this: *const ::std::ffi::c_void, new: *mut ::std::ffi::c_void) { + extern "C" { + #[link_name = #link_clone] + fn __clone(this: *const ::std::ffi::c_void, new: *mut ::std::ffi::c_void); + } + __clone(this, new); + } + #[doc(hidden)] + unsafe fn __get(this: *const ::std::ffi::c_void) -> *const Self { + extern "C" { + #[link_name = #link_get] + fn __get(this: *const ::std::ffi::c_void) -> *const ::std::ffi::c_void; + } + __get(this).cast() + } + #[doc(hidden)] + unsafe fn __drop(this: *mut ::std::ffi::c_void) { + extern "C" { + #[link_name = #link_drop] + fn __drop(this: *mut ::std::ffi::c_void); + } + __drop(this); + } + } + } +} + +fn expand_weak_ptr(key: NamedImplKey, types: &Types, explicit_impl: Option<&Impl>) -> TokenStream { + let ident = key.rust; + let name = ident.to_string(); + let resolve = types.resolve(ident); + let prefix = format!("cxxbridge1$weak_ptr${}$", resolve.name.to_symbol()); + let link_null = format!("{}null", prefix); + let link_clone = format!("{}clone", prefix); + let link_downgrade = format!("{}downgrade", prefix); + let link_upgrade = format!("{}upgrade", prefix); + let link_drop = format!("{}drop", prefix); + + let (impl_generics, ty_generics) = generics::split_for_impl(key, explicit_impl, resolve); + + let begin_span = explicit_impl.map_or(key.begin_span, |explicit| explicit.impl_token.span); + let end_span = explicit_impl.map_or(key.end_span, |explicit| explicit.brace_token.span); + let unsafe_token = format_ident!("unsafe", span = begin_span); + + quote_spanned! {end_span=> + #unsafe_token impl #impl_generics ::cxx::private::WeakPtrTarget for #ident #ty_generics { + #[doc(hidden)] + fn __typename(f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.write_str(#name) + } + #[doc(hidden)] + unsafe fn __null(new: *mut ::std::ffi::c_void) { + extern "C" { + #[link_name = #link_null] + fn __null(new: *mut ::std::ffi::c_void); + } + __null(new); + } + #[doc(hidden)] + unsafe fn __clone(this: *const ::std::ffi::c_void, new: *mut ::std::ffi::c_void) { + extern "C" { + #[link_name = #link_clone] + fn __clone(this: *const ::std::ffi::c_void, new: *mut ::std::ffi::c_void); + } + __clone(this, new); + } + #[doc(hidden)] + unsafe fn __downgrade(shared: *const ::std::ffi::c_void, weak: *mut ::std::ffi::c_void) { + extern "C" { + #[link_name = #link_downgrade] + fn __downgrade(shared: *const ::std::ffi::c_void, weak: *mut ::std::ffi::c_void); + } + __downgrade(shared, weak); + } + #[doc(hidden)] + unsafe fn __upgrade(weak: *const ::std::ffi::c_void, shared: *mut ::std::ffi::c_void) { + extern "C" { + #[link_name = #link_upgrade] + fn __upgrade(weak: *const ::std::ffi::c_void, shared: *mut ::std::ffi::c_void); + } + __upgrade(weak, shared); + } + #[doc(hidden)] + unsafe fn __drop(this: *mut ::std::ffi::c_void) { + extern "C" { + #[link_name = #link_drop] + fn __drop(this: *mut ::std::ffi::c_void); + } + __drop(this); + } + } + } +} + fn expand_cxx_vector( - elem: &ResolvableName, + key: NamedImplKey, 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 elem = key.rust; + let name = elem.to_string(); + let resolve = types.resolve(elem); + let prefix = format!("cxxbridge1$std$vector${}$", resolve.name.to_symbol()); let link_size = format!("{}size", prefix); let link_get_unchecked = format!("{}get_unchecked", prefix); let unique_ptr_prefix = format!( - "cxxbridge05$unique_ptr$std$vector${}$", - elem.to_symbol(types) + "cxxbridge1$unique_ptr$std$vector${}$", + resolve.name.to_symbol(), ); let link_unique_ptr_null = format!("{}null", unique_ptr_prefix); let link_unique_ptr_raw = format!("{}raw", unique_ptr_prefix); @@ -931,28 +1505,35 @@ fn expand_cxx_vector( let link_unique_ptr_release = format!("{}release", unique_ptr_prefix); let link_unique_ptr_drop = format!("{}drop", unique_ptr_prefix); - 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 (impl_generics, ty_generics) = generics::split_for_impl(key, explicit_impl, resolve); + + let begin_span = explicit_impl.map_or(key.begin_span, |explicit| explicit.impl_token.span); + let end_span = explicit_impl.map_or(key.end_span, |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; + #unsafe_token impl #impl_generics ::cxx::private::VectorElement for #elem #ty_generics { + #[doc(hidden)] + fn __typename(f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + f.write_str(#name) + } + #[doc(hidden)] fn __vector_size(v: &::cxx::CxxVector<Self>) -> usize { extern "C" { #[link_name = #link_size] - fn __vector_size(_: &::cxx::CxxVector<#elem>) -> usize; + fn __vector_size #impl_generics(_: &::cxx::CxxVector<#elem #ty_generics>) -> usize; } unsafe { __vector_size(v) } } - unsafe fn __get_unchecked(v: &::cxx::CxxVector<Self>, pos: usize) -> *const Self { + #[doc(hidden)] + unsafe fn __get_unchecked(v: *mut ::cxx::CxxVector<Self>, pos: usize) -> *mut Self { extern "C" { #[link_name = #link_get_unchecked] - fn __get_unchecked(_: &::cxx::CxxVector<#elem>, _: usize) -> *const #elem; + fn __get_unchecked #impl_generics(_: *mut ::cxx::CxxVector<#elem #ty_generics>, _: usize) -> *mut #elem #ty_generics; } __get_unchecked(v, pos) } + #[doc(hidden)] fn __unique_ptr_null() -> *mut ::std::ffi::c_void { extern "C" { #[link_name = #link_unique_ptr_null] @@ -962,29 +1543,33 @@ fn expand_cxx_vector( unsafe { __unique_ptr_null(&mut repr) } repr } + #[doc(hidden)] unsafe fn __unique_ptr_raw(raw: *mut ::cxx::CxxVector<Self>) -> *mut ::std::ffi::c_void { extern "C" { #[link_name = #link_unique_ptr_raw] - fn __unique_ptr_raw(this: *mut *mut ::std::ffi::c_void, raw: *mut ::cxx::CxxVector<#elem>); + fn __unique_ptr_raw #impl_generics(this: *mut *mut ::std::ffi::c_void, raw: *mut ::cxx::CxxVector<#elem #ty_generics>); } let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>(); __unique_ptr_raw(&mut repr, raw); repr } + #[doc(hidden)] unsafe fn __unique_ptr_get(repr: *mut ::std::ffi::c_void) -> *const ::cxx::CxxVector<Self> { extern "C" { #[link_name = #link_unique_ptr_get] - fn __unique_ptr_get(this: *const *mut ::std::ffi::c_void) -> *const ::cxx::CxxVector<#elem>; + fn __unique_ptr_get #impl_generics(this: *const *mut ::std::ffi::c_void) -> *const ::cxx::CxxVector<#elem #ty_generics>; } __unique_ptr_get(&repr) } + #[doc(hidden)] unsafe fn __unique_ptr_release(mut repr: *mut ::std::ffi::c_void) -> *mut ::cxx::CxxVector<Self> { extern "C" { #[link_name = #link_unique_ptr_release] - fn __unique_ptr_release(this: *mut *mut ::std::ffi::c_void) -> *mut ::cxx::CxxVector<#elem>; + fn __unique_ptr_release #impl_generics(this: *mut *mut ::std::ffi::c_void) -> *mut ::cxx::CxxVector<#elem #ty_generics>; } __unique_ptr_release(&mut repr) } + #[doc(hidden)] unsafe fn __unique_ptr_drop(mut repr: *mut ::std::ffi::c_void) { extern "C" { #[link_name = #link_unique_ptr_drop] @@ -1030,15 +1615,24 @@ fn expand_extern_type(ty: &Type, types: &Types, proper: bool) -> TokenStream { 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), + inner if proper && types.is_considered_improper_ctype(inner) => match ty.mutable { + false => quote!(*const ::std::ffi::c_void), + true => quote!(*#mutability ::std::ffi::c_void), }, _ => quote!(#ty), } } + Type::Ptr(ty) => { + if proper && types.is_considered_improper_ctype(&ty.inner) { + let mutability = ty.mutability; + let constness = ty.constness; + quote!(*#mutability #constness ::std::ffi::c_void) + } else { + quote!(#ty) + } + } Type::Str(_) => quote!(::cxx::private::RustStr), - Type::SliceRefU8(_) => quote!(::cxx::private::RustSliceU8), + Type::SliceRef(_) => quote!(::cxx::private::RustSlice), _ => quote!(#ty), } } diff --git a/macro/src/generics.rs b/macro/src/generics.rs new file mode 100644 index 00000000..3de0330b --- /dev/null +++ b/macro/src/generics.rs @@ -0,0 +1,63 @@ +use crate::syntax::instantiate::NamedImplKey; +use crate::syntax::resolve::Resolution; +use crate::syntax::Impl; +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::Token; + +pub struct ImplGenerics<'a> { + explicit_impl: Option<&'a Impl>, + resolve: Resolution<'a>, +} + +pub struct TyGenerics<'a> { + key: NamedImplKey<'a>, + explicit_impl: Option<&'a Impl>, + resolve: Resolution<'a>, +} + +pub fn split_for_impl<'a>( + key: NamedImplKey<'a>, + explicit_impl: Option<&'a Impl>, + resolve: Resolution<'a>, +) -> (ImplGenerics<'a>, TyGenerics<'a>) { + let impl_generics = ImplGenerics { + explicit_impl, + resolve, + }; + let ty_generics = TyGenerics { + key, + explicit_impl, + resolve, + }; + (impl_generics, ty_generics) +} + +impl<'a> ToTokens for ImplGenerics<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some(imp) = self.explicit_impl { + imp.impl_generics.to_tokens(tokens); + } else { + self.resolve.generics.to_tokens(tokens); + } + } +} + +impl<'a> ToTokens for TyGenerics<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some(imp) = self.explicit_impl { + imp.ty_generics.to_tokens(tokens); + } else if !self.resolve.generics.lifetimes.is_empty() { + let span = self.key.rust.span(); + self.key + .lt_token + .unwrap_or_else(|| Token![<](span)) + .to_tokens(tokens); + self.resolve.generics.lifetimes.to_tokens(tokens); + self.key + .gt_token + .unwrap_or_else(|| Token![>](span)) + .to_tokens(tokens); + } + } +} diff --git a/macro/src/lib.rs b/macro/src/lib.rs index e55a08ac..f8e79034 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -1,22 +1,45 @@ #![allow( + clippy::cast_sign_loss, + clippy::default_trait_access, + clippy::doc_markdown, + clippy::enum_glob_use, + clippy::filter_map, clippy::inherent_to_string, + clippy::items_after_statements, clippy::large_enum_variant, + clippy::match_bool, + clippy::match_same_arms, + clippy::module_name_repetitions, + clippy::needless_pass_by_value, clippy::new_without_default, + clippy::nonminimal_bool, + clippy::option_if_let_else, clippy::or_fun_call, + clippy::redundant_else, + clippy::shadow_unrelated, + clippy::similar_names, + clippy::single_match, + clippy::single_match_else, + clippy::too_many_arguments, + clippy::too_many_lines, clippy::toplevel_ref_arg, - clippy::useless_let_if_seq + clippy::useless_let_if_seq, + // clippy bug: https://github.com/rust-lang/rust-clippy/issues/6983 + clippy::wrong_self_convention )] extern crate proc_macro; mod derive; mod expand; +mod generics; mod syntax; mod type_id; use crate::syntax::file::Module; use crate::syntax::namespace::Namespace; use crate::syntax::qualified::QualifiedName; +use crate::type_id::Crate; use proc_macro::TokenStream; use syn::parse::{Parse, ParseStream, Parser, Result}; use syn::parse_macro_input; @@ -27,7 +50,7 @@ use syn::parse_macro_input; /// is intended to be used. /// /// The only additional thing to note here is namespace support — if the -/// types and functions on the `extern "C"` side of our bridge are in a +/// types and functions on the `extern "C++"` side of our bridge are in a /// namespace, specify that namespace as an argument of the cxx::bridge /// attribute macro. /// @@ -54,16 +77,22 @@ pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream { .into() } +#[doc(hidden)] #[proc_macro] pub fn type_id(input: TokenStream) -> TokenStream { - struct TypeId(QualifiedName); + struct TypeId { + krate: Crate, + path: QualifiedName, + } impl Parse for TypeId { fn parse(input: ParseStream) -> Result<Self> { - QualifiedName::parse_quoted_or_unquoted(input).map(TypeId) + let krate = input.parse().map(Crate::DollarCrate)?; + let path = QualifiedName::parse_quoted_or_unquoted(input)?; + Ok(TypeId { krate, path }) } } let arg = parse_macro_input!(input as TypeId); - type_id::expand(arg.0).into() + type_id::expand(arg.krate, arg.path).into() } diff --git a/macro/src/type_id.rs b/macro/src/type_id.rs index 5c5d9ccc..74f0c1c9 100644 --- a/macro/src/type_id.rs +++ b/macro/src/type_id.rs @@ -1,9 +1,23 @@ use crate::syntax::qualified::QualifiedName; -use proc_macro2::TokenStream; -use quote::{format_ident, quote}; +use proc_macro2::{TokenStream, TokenTree}; +use quote::{format_ident, quote, ToTokens}; + +pub enum Crate { + Cxx, + DollarCrate(TokenTree), +} + +impl ToTokens for Crate { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Crate::Cxx => tokens.extend(quote!(::cxx)), + Crate::DollarCrate(krate) => krate.to_tokens(tokens), + } + } +} // "folly::File" => `(f, o, l, l, y, (), F, i, l, e)` -pub fn expand(arg: QualifiedName) -> TokenStream { +pub fn expand(krate: Crate, arg: QualifiedName) -> TokenStream { let mut ids = Vec::new(); for word in arg.segments { @@ -14,11 +28,11 @@ pub fn expand(arg: QualifiedName) -> TokenStream { ids.push(match ch { 'A'..='Z' | 'a'..='z' => { let t = format_ident!("{}", ch); - quote!(::cxx::#t) + quote!(#krate::#t) } '0'..='9' | '_' => { let t = format_ident!("_{}", ch); - quote!(::cxx::#t) + quote!(#krate::#t) } _ => quote!([(); #ch as _]), }); @@ -1,38 +1,60 @@ #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 { +void cxxbridge1$cxx_string$init(std::string *s, const std::uint8_t *ptr, + std::size_t len) noexcept { + new (s) std::string(reinterpret_cast<const char *>(ptr), len); +} + +void cxxbridge1$cxx_string$destroy(std::string *s) noexcept { + using std::string; + s->~string(); +} + +const char *cxxbridge1$cxx_string$data(const std::string &s) noexcept { return s.data(); } -size_t cxxbridge05$cxx_string$length(const std::string &s) noexcept { +std::size_t cxxbridge1$cxx_string$length(const std::string &s) noexcept { return s.length(); } +void cxxbridge1$cxx_string$push(std::string &s, const std::uint8_t *ptr, + std::size_t len) noexcept { + s.append(reinterpret_cast<const char *>(ptr), len); +} + // 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; +void cxxbridge1$string$new(rust::String *self) noexcept; +void cxxbridge1$string$clone(rust::String *self, + const rust::String &other) noexcept; +bool cxxbridge1$string$from(rust::String *self, const char *ptr, + std::size_t len) noexcept; +void cxxbridge1$string$drop(rust::String *self) noexcept; +const char *cxxbridge1$string$ptr(const rust::String *self) noexcept; +std::size_t cxxbridge1$string$len(const rust::String *self) noexcept; +void cxxbridge1$string$reserve_total(rust::String *self, size_t cap) noexcept; // rust::Str -bool cxxbridge05$str$valid(const char *ptr, size_t len) noexcept; +void cxxbridge1$str$new(rust::Str *self) noexcept; +void cxxbridge1$str$ref(rust::Str *self, const rust::String *string) noexcept; +bool cxxbridge1$str$from(rust::Str *self, const char *ptr, + std::size_t len) noexcept; +const char *cxxbridge1$str$ptr(const rust::Str *self) noexcept; +std::size_t cxxbridge1$str$len(const rust::Str *self) noexcept; + +// rust::Slice +void cxxbridge1$slice$new(void *self, const void *ptr, + std::size_t len) noexcept; +void *cxxbridge1$slice$ptr(const void *self) noexcept; +std::size_t cxxbridge1$slice$len(const void *self) noexcept; } // extern "C" namespace rust { -inline namespace cxxbridge05 { +inline namespace cxxbridge1 { template <typename Exception> void panic [[noreturn]] (const char *msg) { @@ -44,23 +66,22 @@ void panic [[noreturn]] (const char *msg) { #endif } -template void panic<std::out_of_range>[[noreturn]] (const char *msg); +template void panic<std::out_of_range> [[noreturn]] (const char *msg); -String::String() noexcept { cxxbridge05$string$new(this); } +String::String() noexcept { cxxbridge1$string$new(this); } String::String(const String &other) noexcept { - cxxbridge05$string$clone(this, other); + cxxbridge1$string$clone(this, other); } -String::String(String &&other) noexcept { - this->repr = other.repr; - cxxbridge05$string$new(&other); +String::String(String &&other) noexcept : repr(other.repr) { + cxxbridge1$string$new(&other); } -String::~String() noexcept { cxxbridge05$string$drop(this); } +String::~String() noexcept { cxxbridge1$string$drop(this); } -static void initString(String *self, const char *s, size_t len) { - if (!cxxbridge05$string$from(self, s, len)) { +static void initString(String *self, const char *s, std::size_t len) { + if (!cxxbridge1$string$from(self, s, len)) { panic<std::invalid_argument>("data for rust::String is not utf-8"); } } @@ -72,27 +93,25 @@ String::String(const char *s) { initString(this, s, std::strlen(s)); } -String::String(const char *s, size_t len) { +String::String(const char *s, std::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 { +String &String::operator=(const String &other) &noexcept { if (this != &other) { - cxxbridge05$string$drop(this); - cxxbridge05$string$clone(this, other); + cxxbridge1$string$drop(this); + cxxbridge1$string$clone(this, other); } return *this; } -String &String::operator=(String &&other) noexcept { - if (this != &other) { - cxxbridge05$string$drop(this); - this->repr = other.repr; - cxxbridge05$string$new(&other); - } +String &String::operator=(String &&other) &noexcept { + cxxbridge1$string$drop(this); + this->repr = other.repr; + cxxbridge1$string$new(&other); return *this; } @@ -101,12 +120,71 @@ String::operator std::string() const { } const char *String::data() const noexcept { - return cxxbridge05$string$ptr(this); + return cxxbridge1$string$ptr(this); +} + +std::size_t String::size() const noexcept { + return cxxbridge1$string$len(this); } -size_t String::size() const noexcept { return cxxbridge05$string$len(this); } +std::size_t String::length() const noexcept { + return cxxbridge1$string$len(this); +} + +const char *String::c_str() noexcept { + auto len = this->length(); + cxxbridge1$string$reserve_total(this, len + 1); + auto ptr = this->data(); + const_cast<char *>(ptr)[len] = '\0'; + return ptr; +} + +String::iterator String::begin() noexcept { + return const_cast<char *>(this->data()); +} + +String::iterator String::end() noexcept { + return const_cast<char *>(this->data()) + this->size(); +} + +String::const_iterator String::begin() const noexcept { return this->cbegin(); } + +String::const_iterator String::end() const noexcept { return this->cend(); } -size_t String::length() const noexcept { return cxxbridge05$string$len(this); } +String::const_iterator String::cbegin() const noexcept { return this->data(); } + +String::const_iterator String::cend() const noexcept { + return this->data() + this->size(); +} + +bool String::operator==(const String &rhs) const noexcept { + return rust::Str(*this) == rust::Str(rhs); +} + +bool String::operator!=(const String &rhs) const noexcept { + return rust::Str(*this) != rust::Str(rhs); +} + +bool String::operator<(const String &rhs) const noexcept { + return rust::Str(*this) < rust::Str(rhs); +} + +bool String::operator<=(const String &rhs) const noexcept { + return rust::Str(*this) <= rust::Str(rhs); +} + +bool String::operator>(const String &rhs) const noexcept { + return rust::Str(*this) > rust::Str(rhs); +} + +bool String::operator>=(const String &rhs) const noexcept { + return rust::Str(*this) >= rust::Str(rhs); +} + +void String::swap(String &rhs) noexcept { + using std::swap; + swap(this->repr, rhs.repr); +} String::String(unsafe_bitcopy_t, const String &bits) noexcept : repr(bits.repr) {} @@ -116,55 +194,178 @@ std::ostream &operator<<(std::ostream &os, const String &s) { return os; } -Str::Str() noexcept : ptr(reinterpret_cast<const char *>(1)), len(0) {} +Str::Str() noexcept { cxxbridge1$str$new(this); } + +Str::Str(const String &s) noexcept { cxxbridge1$str$ref(this, &s); } -static void initStr(const char *ptr, size_t len) { - if (!cxxbridge05$str$valid(ptr, len)) { +static void initStr(Str *self, const char *ptr, std::size_t len) { + if (!cxxbridge1$str$from(self, ptr, len)) { panic<std::invalid_argument>("data for rust::Str is not utf-8"); } } -Str::Str(const std::string &s) : ptr(s.data()), len(s.length()) { - initStr(this->ptr, this->len); -} +Str::Str(const std::string &s) { initStr(this, s.data(), s.length()); } -Str::Str(const char *s) : ptr(s), len(std::strlen(s)) { +Str::Str(const char *s) { assert(s != nullptr); - initStr(this->ptr, this->len); + initStr(this, s, std::strlen(s)); } -Str::Str(const char *s, size_t len) - : ptr(s == nullptr && len == 0 ? reinterpret_cast<const char *>(1) : s), - len(len) { +Str::Str(const char *s, std::size_t len) { assert(s != nullptr || len == 0); - initStr(this->ptr, this->len); + initStr(this, + s == nullptr && len == 0 ? reinterpret_cast<const char *>(1) : s, + len); } Str::operator std::string() const { return std::string(this->data(), this->size()); } +const char *Str::data() const noexcept { return cxxbridge1$str$ptr(this); } + +std::size_t Str::size() const noexcept { return cxxbridge1$str$len(this); } + +std::size_t Str::length() const noexcept { return this->size(); } + +Str::const_iterator Str::begin() const noexcept { return this->cbegin(); } + +Str::const_iterator Str::end() const noexcept { return this->cend(); } + +Str::const_iterator Str::cbegin() const noexcept { return this->data(); } + +Str::const_iterator Str::cend() const noexcept { + return this->data() + this->size(); +} + +bool Str::operator==(const Str &rhs) const noexcept { + return this->size() == rhs.size() && + std::equal(this->begin(), this->end(), rhs.begin()); +} + +bool Str::operator!=(const Str &rhs) const noexcept { return !(*this == rhs); } + +bool Str::operator<(const Str &rhs) const noexcept { + return std::lexicographical_compare(this->begin(), this->end(), rhs.begin(), + rhs.end()); +} + +bool Str::operator<=(const Str &rhs) const noexcept { + // std::mismatch(this->begin(), this->end(), rhs.begin(), rhs.end()), except + // without Undefined Behavior on C++11 if rhs is shorter than *this. + const_iterator liter = this->begin(), lend = this->end(), riter = rhs.begin(), + rend = rhs.end(); + while (liter != lend && riter != rend && *liter == *riter) { + ++liter, ++riter; + } + if (liter == lend) { + return true; // equal or *this is a prefix of rhs + } else if (riter == rend) { + return false; // rhs is a prefix of *this + } else { + return *liter <= *riter; + } +} + +bool Str::operator>(const Str &rhs) const noexcept { return rhs < *this; } + +bool Str::operator>=(const Str &rhs) const noexcept { return rhs <= *this; } + +void Str::swap(Str &rhs) noexcept { + using std::swap; + swap(this->repr, rhs.repr); +} + std::ostream &operator<<(std::ostream &os, const Str &s) { os.write(s.data(), s.size()); return os; } +void sliceInit(void *self, const void *ptr, std::size_t len) noexcept { + cxxbridge1$slice$new(self, ptr, len); +} + +void *slicePtr(const void *self) noexcept { return cxxbridge1$slice$ptr(self); } + +std::size_t sliceLen(const void *self) noexcept { + return cxxbridge1$slice$len(self); +} + +// Rust specifies that usize is ABI compatible with C's uintptr_t. +// https://rust-lang.github.io/unsafe-code-guidelines/layout/scalars.html#isize-and-usize +// However there is no direct Rust equivalent for size_t. C does not guarantee +// that size_t and uintptr_t are compatible. In practice though, on all +// platforms supported by Rust, they are identical for ABI purposes. See the +// libc crate which unconditionally defines libc::size_t = usize. We expect the +// same here and these assertions are just here to explicitly document that. +// *Note that no assumption is made about C++ name mangling of signatures +// containing these types, not here nor anywhere in CXX.* +static_assert(sizeof(std::size_t) == sizeof(std::uintptr_t), + "unsupported size_t size"); +static_assert(alignof(std::size_t) == alignof(std::uintptr_t), + "unsupported size_t alignment"); +static_assert(sizeof(rust::isize) == sizeof(std::intptr_t), + "unsupported ssize_t size"); +static_assert(alignof(rust::isize) == alignof(std::intptr_t), + "unsupported ssize_t alignment"); + 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 *cxxbridge05$error(const char *ptr, size_t len) { +static_assert( + std::is_trivially_copy_constructible<Slice<const std::uint8_t>>::value, + "trivial Slice(const Slice &)"); +static_assert( + std::is_trivially_move_constructible<Slice<const std::uint8_t>>::value, + "trivial Slice(Slice &&)"); +static_assert( + std::is_trivially_copy_assignable<Slice<const std::uint8_t>>::value, + "trivial Slice::operator=(const Slice &) for const slices"); +static_assert( + std::is_trivially_move_assignable<Slice<const std::uint8_t>>::value, + "trivial Slice::operator=(Slice &&)"); +static_assert(std::is_trivially_destructible<Slice<const std::uint8_t>>::value, + "trivial ~Slice()"); + +static_assert(std::is_trivially_copy_constructible<Slice<std::uint8_t>>::value, + "trivial Slice(const Slice &)"); +static_assert(std::is_trivially_move_constructible<Slice<std::uint8_t>>::value, + "trivial Slice(Slice &&)"); +static_assert(!std::is_copy_assignable<Slice<std::uint8_t>>::value, + "delete Slice::operator=(const Slice &) for mut slices"); +static_assert(std::is_trivially_move_assignable<Slice<std::uint8_t>>::value, + "trivial Slice::operator=(Slice &&)"); +static_assert(std::is_trivially_destructible<Slice<std::uint8_t>>::value, + "trivial ~Slice()"); + +static_assert(std::is_same<Vec<std::uint8_t>::const_iterator, + Vec<const std::uint8_t>::iterator>::value, + "Vec<T>::const_iterator == Vec<const T>::iterator"); +static_assert(std::is_same<Vec<const std::uint8_t>::const_iterator, + Vec<const std::uint8_t>::iterator>::value, + "Vec<const T>::const_iterator == Vec<const T>::iterator"); +static_assert(!std::is_same<Vec<std::uint8_t>::const_iterator, + Vec<std::uint8_t>::iterator>::value, + "Vec<T>::const_iterator != Vec<T>::iterator"); + +static const char *errorCopy(const char *ptr, std::size_t len) { char *copy = new char[len]; - std::strncpy(copy, ptr, len); + std::memcpy(copy, ptr, len); return copy; } + +extern "C" { +const char *cxxbridge1$error(const char *ptr, std::size_t len) noexcept { + return errorCopy(ptr, len); +} } // extern "C" Error::Error(const Error &other) - : std::exception(other), msg(cxxbridge05$error(other.msg, other.len)), + : std::exception(other), + msg(other.msg ? errorCopy(other.msg, other.len) : nullptr), len(other.len) {} Error::Error(Error &&other) noexcept @@ -175,165 +376,263 @@ Error::Error(Error &&other) noexcept Error::~Error() noexcept { delete[] this->msg; } -Error &Error::operator=(const Error &other) { +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; + if (other.msg) { + this->msg = errorCopy(other.msg, other.len); + this->len = other.len; + } } return *this; } -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; - } +Error &Error::operator=(Error &&other) &noexcept { + 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; } -} // namespace cxxbridge05 +namespace { +template <typename T> +union MaybeUninit { + T value; + MaybeUninit() {} + ~MaybeUninit() {} +}; +} // namespace + +namespace detail { +// On some platforms size_t is the same C++ type as one of the sized integer +// types; on others it is a distinct type. Only in the latter case do we need to +// define a specialized impl of rust::Vec<size_t>, because in the former case it +// would collide with one of the other specializations. +using usize_if_unique = + typename std::conditional<std::is_same<size_t, uint64_t>::value || + std::is_same<size_t, uint32_t>::value, + struct usize_ignore, size_t>::type; +using isize_if_unique = + typename std::conditional<std::is_same<rust::isize, int64_t>::value || + std::is_same<rust::isize, int32_t>::value, + struct isize_ignore, rust::isize>::type; +} // namespace detail + +} // namespace cxxbridge1 } // namespace rust extern "C" { -void cxxbridge05$unique_ptr$std$string$null( +void cxxbridge1$unique_ptr$std$string$null( std::unique_ptr<std::string> *ptr) noexcept { new (ptr) std::unique_ptr<std::string>(); } -void cxxbridge05$unique_ptr$std$string$raw(std::unique_ptr<std::string> *ptr, - std::string *raw) noexcept { +void cxxbridge1$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 *cxxbridge05$unique_ptr$std$string$get( +const std::string *cxxbridge1$unique_ptr$std$string$get( const std::unique_ptr<std::string> &ptr) noexcept { return ptr.get(); } -std::string *cxxbridge05$unique_ptr$std$string$release( +std::string *cxxbridge1$unique_ptr$std$string$release( std::unique_ptr<std::string> &ptr) noexcept { return ptr.release(); } -void cxxbridge05$unique_ptr$std$string$drop( +void cxxbridge1$unique_ptr$std$string$drop( std::unique_ptr<std::string> *ptr) noexcept { ptr->~unique_ptr(); } } // extern "C" +namespace { +const std::size_t kMaxExpectedWordsInString = 8; +static_assert(alignof(std::string) <= alignof(void *), + "unexpectedly large std::string alignment"); +static_assert(sizeof(std::string) <= kMaxExpectedWordsInString * sizeof(void *), + "unexpectedly large std::string size"); +} // namespace + #define STD_VECTOR_OPS(RUST_TYPE, CXX_TYPE) \ - size_t cxxbridge05$std$vector$##RUST_TYPE##$size( \ + std::size_t cxxbridge1$std$vector$##RUST_TYPE##$size( \ const std::vector<CXX_TYPE> &s) noexcept { \ return s.size(); \ } \ - const CXX_TYPE *cxxbridge05$std$vector$##RUST_TYPE##$get_unchecked( \ - const std::vector<CXX_TYPE> &s, size_t pos) noexcept { \ - return &s[pos]; \ + CXX_TYPE *cxxbridge1$std$vector$##RUST_TYPE##$get_unchecked( \ + std::vector<CXX_TYPE> *s, std::size_t pos) noexcept { \ + return &(*s)[pos]; \ } \ - void cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$null( \ + void cxxbridge1$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 cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$raw( \ + void cxxbridge1$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> \ - *cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$get( \ + *cxxbridge1$unique_ptr$std$vector$##RUST_TYPE##$get( \ const std::unique_ptr<std::vector<CXX_TYPE>> &ptr) noexcept { \ return ptr.get(); \ } \ std::vector<CXX_TYPE> \ - *cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$release( \ + *cxxbridge1$unique_ptr$std$vector$##RUST_TYPE##$release( \ std::unique_ptr<std::vector<CXX_TYPE>> &ptr) noexcept { \ return ptr.release(); \ } \ - void cxxbridge05$unique_ptr$std$vector$##RUST_TYPE##$drop( \ + void cxxbridge1$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 cxxbridge05$rust_vec$##RUST_TYPE##$new( \ + void cxxbridge1$rust_vec$##RUST_TYPE##$new( \ rust::Vec<CXX_TYPE> *ptr) noexcept; \ - void cxxbridge05$rust_vec$##RUST_TYPE##$drop( \ + void cxxbridge1$rust_vec$##RUST_TYPE##$drop( \ rust::Vec<CXX_TYPE> *ptr) noexcept; \ - size_t cxxbridge05$rust_vec$##RUST_TYPE##$len( \ + std::size_t cxxbridge1$rust_vec$##RUST_TYPE##$len( \ + const rust::Vec<CXX_TYPE> *ptr) noexcept; \ + std::size_t cxxbridge1$rust_vec$##RUST_TYPE##$capacity( \ const rust::Vec<CXX_TYPE> *ptr) noexcept; \ - const CXX_TYPE *cxxbridge05$rust_vec$##RUST_TYPE##$data( \ + const CXX_TYPE *cxxbridge1$rust_vec$##RUST_TYPE##$data( \ const rust::Vec<CXX_TYPE> *ptr) 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; + void cxxbridge1$rust_vec$##RUST_TYPE##$reserve_total( \ + rust::Vec<CXX_TYPE> *ptr, std::size_t cap) noexcept; \ + void cxxbridge1$rust_vec$##RUST_TYPE##$set_len(rust::Vec<CXX_TYPE> *ptr, \ + std::size_t len) noexcept; #define RUST_VEC_OPS(RUST_TYPE, CXX_TYPE) \ template <> \ Vec<CXX_TYPE>::Vec() noexcept { \ - cxxbridge05$rust_vec$##RUST_TYPE##$new(this); \ + cxxbridge1$rust_vec$##RUST_TYPE##$new(this); \ } \ template <> \ void Vec<CXX_TYPE>::drop() noexcept { \ - return cxxbridge05$rust_vec$##RUST_TYPE##$drop(this); \ + return cxxbridge1$rust_vec$##RUST_TYPE##$drop(this); \ } \ template <> \ - size_t Vec<CXX_TYPE>::size() const noexcept { \ - return cxxbridge05$rust_vec$##RUST_TYPE##$len(this); \ + std::size_t Vec<CXX_TYPE>::size() const noexcept { \ + return cxxbridge1$rust_vec$##RUST_TYPE##$len(this); \ } \ template <> \ - const CXX_TYPE *Vec<CXX_TYPE>::data() const noexcept { \ - return cxxbridge05$rust_vec$##RUST_TYPE##$data(this); \ + std::size_t Vec<CXX_TYPE>::capacity() const noexcept { \ + return cxxbridge1$rust_vec$##RUST_TYPE##$capacity(this); \ } \ template <> \ - void Vec<CXX_TYPE>::reserve_total(size_t cap) noexcept { \ - cxxbridge05$rust_vec$##RUST_TYPE##$reserve_total(this, cap); \ + const CXX_TYPE *Vec<CXX_TYPE>::data() const noexcept { \ + return cxxbridge1$rust_vec$##RUST_TYPE##$data(this); \ } \ template <> \ - void Vec<CXX_TYPE>::set_len(size_t len) noexcept { \ - cxxbridge05$rust_vec$##RUST_TYPE##$set_len(this, len); \ + void Vec<CXX_TYPE>::reserve_total(std::size_t cap) noexcept { \ + cxxbridge1$rust_vec$##RUST_TYPE##$reserve_total(this, cap); \ } \ template <> \ - size_t Vec<CXX_TYPE>::stride() noexcept { \ - return cxxbridge05$rust_vec$##RUST_TYPE##$stride(); \ + void Vec<CXX_TYPE>::set_len(std::size_t len) noexcept { \ + cxxbridge1$rust_vec$##RUST_TYPE##$set_len(this, len); \ + } + +#define SHARED_PTR_OPS(RUST_TYPE, CXX_TYPE) \ + static_assert(sizeof(std::shared_ptr<CXX_TYPE>) == 2 * sizeof(void *), ""); \ + static_assert(alignof(std::shared_ptr<CXX_TYPE>) == alignof(void *), ""); \ + void cxxbridge1$std$shared_ptr$##RUST_TYPE##$null( \ + std::shared_ptr<CXX_TYPE> *ptr) noexcept { \ + new (ptr) std::shared_ptr<CXX_TYPE>(); \ + } \ + CXX_TYPE *cxxbridge1$std$shared_ptr$##RUST_TYPE##$uninit( \ + std::shared_ptr<CXX_TYPE> *ptr) noexcept { \ + CXX_TYPE *uninit = \ + reinterpret_cast<CXX_TYPE *>(new rust::MaybeUninit<CXX_TYPE>); \ + new (ptr) std::shared_ptr<CXX_TYPE>(uninit); \ + return uninit; \ + } \ + void cxxbridge1$std$shared_ptr$##RUST_TYPE##$clone( \ + const std::shared_ptr<CXX_TYPE> &self, \ + std::shared_ptr<CXX_TYPE> *ptr) noexcept { \ + new (ptr) std::shared_ptr<CXX_TYPE>(self); \ + } \ + const CXX_TYPE *cxxbridge1$std$shared_ptr$##RUST_TYPE##$get( \ + const std::shared_ptr<CXX_TYPE> &self) noexcept { \ + return self.get(); \ + } \ + void cxxbridge1$std$shared_ptr$##RUST_TYPE##$drop( \ + const std::shared_ptr<CXX_TYPE> *self) noexcept { \ + self->~shared_ptr(); \ + } \ + static_assert(sizeof(std::weak_ptr<CXX_TYPE>) == 2 * sizeof(void *), ""); \ + static_assert(alignof(std::weak_ptr<CXX_TYPE>) == alignof(void *), ""); \ + void cxxbridge1$std$weak_ptr$##RUST_TYPE##$null( \ + std::weak_ptr<CXX_TYPE> *ptr) noexcept { \ + new (ptr) std::weak_ptr<CXX_TYPE>(); \ + } \ + void cxxbridge1$std$weak_ptr$##RUST_TYPE##$clone( \ + const std::weak_ptr<CXX_TYPE> &self, \ + std::weak_ptr<CXX_TYPE> *ptr) noexcept { \ + new (ptr) std::weak_ptr<CXX_TYPE>(self); \ + } \ + void cxxbridge1$std$weak_ptr$##RUST_TYPE##$downgrade( \ + const std::shared_ptr<CXX_TYPE> &shared, \ + std::weak_ptr<CXX_TYPE> *weak) noexcept { \ + new (weak) std::weak_ptr<CXX_TYPE>(shared); \ + } \ + void cxxbridge1$std$weak_ptr$##RUST_TYPE##$upgrade( \ + const std::weak_ptr<CXX_TYPE> &weak, \ + std::shared_ptr<CXX_TYPE> *shared) noexcept { \ + new (shared) std::shared_ptr<CXX_TYPE>(weak.lock()); \ + } \ + void cxxbridge1$std$weak_ptr$##RUST_TYPE##$drop( \ + const std::weak_ptr<CXX_TYPE> *self) noexcept { \ + self->~weak_ptr(); \ } // Usize and isize are the same type as one of the below. #define FOR_EACH_NUMERIC(MACRO) \ - MACRO(u8, uint8_t) \ - MACRO(u16, uint16_t) \ - MACRO(u32, uint32_t) \ - MACRO(u64, uint64_t) \ - MACRO(i8, int8_t) \ - MACRO(i16, int16_t) \ - MACRO(i32, int32_t) \ - MACRO(i64, int64_t) \ + MACRO(u8, std::uint8_t) \ + MACRO(u16, std::uint16_t) \ + MACRO(u32, std::uint32_t) \ + MACRO(u64, std::uint64_t) \ + MACRO(i8, std::int8_t) \ + MACRO(i16, std::int16_t) \ + MACRO(i32, std::int32_t) \ + MACRO(i64, std::int64_t) \ MACRO(f32, float) \ MACRO(f64, double) #define FOR_EACH_STD_VECTOR(MACRO) \ FOR_EACH_NUMERIC(MACRO) \ - MACRO(usize, size_t) \ + MACRO(usize, std::size_t) \ MACRO(isize, rust::isize) \ MACRO(string, std::string) #define FOR_EACH_RUST_VEC(MACRO) \ FOR_EACH_NUMERIC(MACRO) \ MACRO(bool, bool) \ - MACRO(string, rust::String) + MACRO(char, char) \ + MACRO(usize, rust::detail::usize_if_unique) \ + MACRO(isize, rust::detail::isize_if_unique) \ + MACRO(string, rust::String) \ + MACRO(str, rust::Str) + +#define FOR_EACH_SHARED_PTR(MACRO) \ + FOR_EACH_NUMERIC(MACRO) \ + MACRO(bool, bool) \ + MACRO(usize, std::size_t) \ + MACRO(isize, rust::isize) \ + MACRO(string, std::string) extern "C" { FOR_EACH_STD_VECTOR(STD_VECTOR_OPS) FOR_EACH_RUST_VEC(RUST_VEC_EXTERNS) +FOR_EACH_SHARED_PTR(SHARED_PTR_OPS) } // extern "C" namespace rust { -inline namespace cxxbridge05 { +inline namespace cxxbridge1 { FOR_EACH_RUST_VEC(RUST_VEC_OPS) -} // namespace cxxbridge05 +} // namespace cxxbridge1 } // namespace rust diff --git a/src/cxx_string.rs b/src/cxx_string.rs index 7b47feb7..dce7053d 100644 --- a/src/cxx_string.rs +++ b/src/cxx_string.rs @@ -1,14 +1,26 @@ +use crate::actually_private::Private; use alloc::borrow::Cow; use alloc::string::String; +use core::cmp::Ordering; use core::fmt::{self, Debug, Display}; +use core::hash::{Hash, Hasher}; +use core::marker::{PhantomData, PhantomPinned}; +use core::mem::MaybeUninit; +use core::pin::Pin; use core::slice; use core::str::{self, Utf8Error}; extern "C" { - #[link_name = "cxxbridge05$cxx_string$data"] - fn string_data(_: &CxxString) -> *const u8; - #[link_name = "cxxbridge05$cxx_string$length"] - fn string_length(_: &CxxString) -> usize; + #[link_name = "cxxbridge1$cxx_string$init"] + fn string_init(this: &mut MaybeUninit<CxxString>, ptr: *const u8, len: usize); + #[link_name = "cxxbridge1$cxx_string$destroy"] + fn string_destroy(this: &mut MaybeUninit<CxxString>); + #[link_name = "cxxbridge1$cxx_string$data"] + fn string_data(this: &CxxString) -> *const u8; + #[link_name = "cxxbridge1$cxx_string$length"] + fn string_length(this: &CxxString) -> usize; + #[link_name = "cxxbridge1$cxx_string$push"] + fn string_push(this: Pin<&mut CxxString>, ptr: *const u8, len: usize); } /// Binding to C++ `std::string`. @@ -24,9 +36,58 @@ extern "C" { #[repr(C)] pub struct CxxString { _private: [u8; 0], + _pinned: PhantomData<PhantomPinned>, +} + +/// Construct a C++ std::string on the Rust stack. +/// +/// # Syntax +/// +/// In statement position: +/// +/// ``` +/// # use cxx::let_cxx_string; +/// # let expression = ""; +/// let_cxx_string!(var = expression); +/// ``` +/// +/// The `expression` may have any type that implements `AsRef<[u8]>`. Commonly +/// it will be a string literal, but for example `&[u8]` and `String` would work +/// as well. +/// +/// The macro expands to something resembling `let $var: Pin<&mut CxxString> = +/// /*???*/;`. The resulting [`Pin`] can be deref'd to `&CxxString` as needed. +/// +/// # Example +/// +/// ``` +/// use cxx::{let_cxx_string, CxxString}; +/// +/// fn f(s: &CxxString) {/* ... */} +/// +/// fn main() { +/// let_cxx_string!(s = "example"); +/// f(&s); +/// } +/// ``` +#[macro_export] +macro_rules! let_cxx_string { + ($var:ident = $value:expr $(,)?) => { + let mut cxx_stack_string = $crate::private::StackString::new(); + #[allow(unused_mut, unused_unsafe)] + let mut $var = match $value { + let_cxx_string => unsafe { cxx_stack_string.init(let_cxx_string) }, + }; + }; } impl CxxString { + /// `CxxString` is not constructible via `new`. Instead, use the + /// [`let_cxx_string!`] macro. + pub fn new<T: Private>() -> Self { + unreachable!() + } + /// Returns the length of the string in bytes. /// /// Matches the behavior of C++ [std::string::size][size]. @@ -82,6 +143,16 @@ impl CxxString { pub fn to_string_lossy(&self) -> Cow<str> { String::from_utf8_lossy(self.as_bytes()) } + + /// Appends a given string slice onto the end of this C++ string. + pub fn push_str(self: Pin<&mut Self>, s: &str) { + self.push_bytes(s.as_bytes()); + } + + /// Appends arbitrary bytes onto the end of this C++ string. + pub fn push_bytes(self: Pin<&mut Self>, bytes: &[u8]) { + unsafe { string_push(self, bytes.as_ptr(), bytes.len()) } + } } impl Display for CxxString { @@ -97,7 +168,7 @@ impl Debug for CxxString { } impl PartialEq for CxxString { - fn eq(&self, other: &CxxString) -> bool { + fn eq(&self, other: &Self) -> bool { self.as_bytes() == other.as_bytes() } } @@ -113,3 +184,55 @@ impl PartialEq<str> for CxxString { self.as_bytes() == other.as_bytes() } } + +impl Eq for CxxString {} + +impl PartialOrd for CxxString { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + self.as_bytes().partial_cmp(other.as_bytes()) + } +} + +impl Ord for CxxString { + fn cmp(&self, other: &Self) -> Ordering { + self.as_bytes().cmp(other.as_bytes()) + } +} + +impl Hash for CxxString { + fn hash<H: Hasher>(&self, state: &mut H) { + self.as_bytes().hash(state); + } +} + +#[doc(hidden)] +#[repr(C)] +pub struct StackString { + // Static assertions in cxx.cc validate that this is large enough and + // aligned enough. + space: MaybeUninit<[usize; 8]>, +} + +impl StackString { + pub fn new() -> Self { + StackString { + space: MaybeUninit::uninit(), + } + } + + pub unsafe fn init(&mut self, value: impl AsRef<[u8]>) -> Pin<&mut CxxString> { + let value = value.as_ref(); + let this = &mut *self.space.as_mut_ptr().cast::<MaybeUninit<CxxString>>(); + string_init(this, value.as_ptr(), value.len()); + Pin::new_unchecked(&mut *this.as_mut_ptr()) + } +} + +impl Drop for StackString { + fn drop(&mut self) { + unsafe { + let this = &mut *self.space.as_mut_ptr().cast::<MaybeUninit<CxxString>>(); + string_destroy(this); + } + } +} diff --git a/src/cxx_vector.rs b/src/cxx_vector.rs index 5fb08071..16433417 100644 --- a/src/cxx_vector.rs +++ b/src/cxx_vector.rs @@ -1,8 +1,15 @@ -use crate::cxx_string::CxxString; +//! Less used details of `CxxVector` are exposed in this module. `CxxVector` +//! itself is exposed at the crate root. + +use crate::extern_type::ExternType; +use crate::kind::Trivial; +use crate::string::CxxString; use core::ffi::c_void; -use core::fmt::{self, Display}; -use core::marker::PhantomData; +use core::fmt::{self, Debug}; +use core::iter::FusedIterator; +use core::marker::{PhantomData, PhantomPinned}; use core::mem; +use core::pin::Pin; use core::ptr; use core::slice; @@ -17,6 +24,7 @@ use core::slice; #[repr(C, packed)] pub struct CxxVector<T> { _private: [T; 0], + _pinned: PhantomData<PhantomPinned>, } impl<T> CxxVector<T> @@ -51,6 +59,16 @@ where } } + /// Returns a pinned mutable reference to an element at the given position, + /// or `None` if out of bounds. + pub fn index_mut(self: Pin<&mut Self>, pos: usize) -> Option<Pin<&mut T>> { + if pos < self.len() { + Some(unsafe { self.index_unchecked_mut(pos) }) + } else { + None + } + } + /// Returns a reference to an element without doing bounds checking. /// /// This is generally not recommended, use with caution! Calling this method @@ -58,15 +76,36 @@ where /// reference is not used. /// /// Matches the behavior of C++ - /// [std::vector\<T\>::operator\[\]][operator_at]. + /// [std::vector\<T\>::operator\[\] const][operator_at]. /// /// [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) + let this = self as *const CxxVector<T> as *mut CxxVector<T>; + let ptr = T::__get_unchecked(this, pos) as *const T; + &*ptr + } + + /// Returns a pinned mutable reference to an element without doing bounds + /// checking. + /// + /// This is generally not recommended, use with caution! Calling this method + /// with an out-of-bounds index is undefined behavior even if the resulting + /// reference is not used. + /// + /// Matches the behavior of C++ + /// [std::vector\<T\>::operator\[\]][operator_at]. + /// + /// [operator_at]: https://en.cppreference.com/w/cpp/container/vector/operator_at + pub unsafe fn index_unchecked_mut(self: Pin<&mut Self>, pos: usize) -> Pin<&mut T> { + let ptr = T::__get_unchecked(self.get_unchecked_mut(), pos); + Pin::new_unchecked(&mut *ptr) } /// Returns a slice to the underlying contiguous array of elements. - pub fn as_slice(&self) -> &[T] { + pub fn as_slice(&self) -> &[T] + where + T: ExternType<Kind = Trivial>, + { let len = self.len(); if len == 0 { // The slice::from_raw_parts in the other branch requires a nonnull @@ -77,12 +116,41 @@ where // which upholds the invariants. &[] } else { - let ptr = unsafe { T::__get_unchecked(self, 0) }; + let this = self as *const CxxVector<T> as *mut CxxVector<T>; + let ptr = unsafe { T::__get_unchecked(this, 0) }; unsafe { slice::from_raw_parts(ptr, len) } } } + + /// Returns a slice to the underlying contiguous array of elements by + /// mutable reference. + pub fn as_mut_slice(self: Pin<&mut Self>) -> &mut [T] + where + T: ExternType<Kind = Trivial>, + { + let len = self.len(); + if len == 0 { + &mut [] + } else { + let ptr = unsafe { T::__get_unchecked(self.get_unchecked_mut(), 0) }; + unsafe { slice::from_raw_parts_mut(ptr, len) } + } + } + + /// Returns an iterator over elements of type `&T`. + pub fn iter(&self) -> Iter<T> { + Iter { v: self, index: 0 } + } + + /// Returns an iterator over elements of type `Pin<&mut T>`. + pub fn iter_mut(self: Pin<&mut Self>) -> IterMut<T> { + IterMut { v: self, index: 0 } + } } +/// Iterator over elements of a `CxxVector` by shared reference. +/// +/// The iterator element type is `&'a T`. pub struct Iter<'a, T> { v: &'a CxxVector<T>, index: usize, @@ -96,7 +164,7 @@ where type IntoIter = Iter<'a, T>; fn into_iter(self) -> Self::IntoIter { - Iter { v: self, index: 0 } + self.iter() } } @@ -107,44 +175,135 @@ where type Item = &'a T; fn next(&mut self) -> Option<Self::Item> { - let next = self.v.get(self.index); + let next = self.v.get(self.index)?; self.index += 1; - next + Some(next) + } + + fn size_hint(&self) -> (usize, Option<usize>) { + let len = self.len(); + (len, Some(len)) + } +} + +impl<'a, T> ExactSizeIterator for Iter<'a, T> +where + T: VectorElement, +{ + fn len(&self) -> usize { + self.v.len() - self.index } } -pub struct TypeName<T> { - element: PhantomData<T>, +impl<'a, T> FusedIterator for Iter<'a, T> where T: VectorElement {} + +/// Iterator over elements of a `CxxVector` by pinned mutable reference. +/// +/// The iterator element type is `Pin<&'a mut T>`. +pub struct IterMut<'a, T> { + v: Pin<&'a mut CxxVector<T>>, + index: usize, } -impl<T> TypeName<T> { - pub const fn new() -> Self { - TypeName { - element: PhantomData, +impl<'a, T> IntoIterator for Pin<&'a mut CxxVector<T>> +where + T: VectorElement, +{ + type Item = Pin<&'a mut T>; + type IntoIter = IterMut<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl<'a, T> Iterator for IterMut<'a, T> +where + T: VectorElement, +{ + type Item = Pin<&'a mut T>; + + fn next(&mut self) -> Option<Self::Item> { + let next = self.v.as_mut().index_mut(self.index)?; + self.index += 1; + // Extend lifetime to allow simultaneous holding of nonoverlapping + // elements, analogous to slice::split_first_mut. + unsafe { + let ptr = Pin::into_inner_unchecked(next) as *mut T; + Some(Pin::new_unchecked(&mut *ptr)) } } + + fn size_hint(&self) -> (usize, Option<usize>) { + let len = self.len(); + (len, Some(len)) + } } -impl<T> Display for TypeName<T> +impl<'a, T> ExactSizeIterator for IterMut<'a, T> where T: VectorElement, { + fn len(&self) -> usize { + self.v.len() - self.index + } +} + +impl<'a, T> FusedIterator for IterMut<'a, T> where T: VectorElement {} + +impl<T> Debug for CxxVector<T> +where + T: VectorElement + Debug, +{ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "CxxVector<{}>", T::__NAME) + formatter.debug_list().entries(self).finish() } } -// Methods are private; not intended to be implemented outside of cxxbridge -// codebase. -#[doc(hidden)] +/// Trait bound for types which may be used as the `T` inside of a +/// `CxxVector<T>` in generic code. +/// +/// This trait has no publicly callable or implementable methods. Implementing +/// it outside of the CXX codebase is not supported. +/// +/// # Example +/// +/// A bound `T: VectorElement` may be necessary when manipulating [`CxxVector`] +/// in generic code. +/// +/// ``` +/// use cxx::vector::{CxxVector, VectorElement}; +/// use std::fmt::Display; +/// +/// pub fn take_generic_vector<T>(vector: &CxxVector<T>) +/// where +/// T: VectorElement + Display, +/// { +/// println!("the vector elements are:"); +/// for element in vector { +/// println!(" • {}", element); +/// } +/// } +/// ``` +/// +/// Writing the same generic function without a `VectorElement` trait bound +/// would not compile. pub unsafe trait VectorElement: Sized { - const __NAME: &'static dyn Display; + #[doc(hidden)] + fn __typename(f: &mut fmt::Formatter) -> fmt::Result; + #[doc(hidden)] fn __vector_size(v: &CxxVector<Self>) -> usize; - unsafe fn __get_unchecked(v: &CxxVector<Self>, pos: usize) -> *const Self; + #[doc(hidden)] + unsafe fn __get_unchecked(v: *mut CxxVector<Self>, pos: usize) -> *mut Self; + #[doc(hidden)] fn __unique_ptr_null() -> *mut c_void; + #[doc(hidden)] unsafe fn __unique_ptr_raw(raw: *mut CxxVector<Self>) -> *mut c_void; + #[doc(hidden)] unsafe fn __unique_ptr_get(repr: *mut c_void) -> *const CxxVector<Self>; + #[doc(hidden)] unsafe fn __unique_ptr_release(repr: *mut c_void) -> *mut CxxVector<Self>; + #[doc(hidden)] unsafe fn __unique_ptr_drop(repr: *mut c_void); } @@ -153,29 +312,35 @@ macro_rules! impl_vector_element { const_assert_eq!(1, mem::align_of::<CxxVector<$ty>>()); unsafe impl VectorElement for $ty { - const __NAME: &'static dyn Display = &$name; + #[doc(hidden)] + fn __typename(f: &mut fmt::Formatter) -> fmt::Result { + f.write_str($name) + } + #[doc(hidden)] fn __vector_size(v: &CxxVector<$ty>) -> usize { extern "C" { attr! { - #[link_name = concat!("cxxbridge05$std$vector$", $segment, "$size")] + #[link_name = concat!("cxxbridge1$std$vector$", $segment, "$size")] fn __vector_size(_: &CxxVector<$ty>) -> usize; } } unsafe { __vector_size(v) } } - unsafe fn __get_unchecked(v: &CxxVector<$ty>, pos: usize) -> *const $ty { + #[doc(hidden)] + unsafe fn __get_unchecked(v: *mut CxxVector<$ty>, pos: usize) -> *mut $ty { extern "C" { attr! { - #[link_name = concat!("cxxbridge05$std$vector$", $segment, "$get_unchecked")] - fn __get_unchecked(_: &CxxVector<$ty>, _: usize) -> *const $ty; + #[link_name = concat!("cxxbridge1$std$vector$", $segment, "$get_unchecked")] + fn __get_unchecked(_: *mut CxxVector<$ty>, _: usize) -> *mut $ty; } } __get_unchecked(v, pos) } + #[doc(hidden)] fn __unique_ptr_null() -> *mut c_void { extern "C" { attr! { - #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$null")] + #[link_name = concat!("cxxbridge1$unique_ptr$std$vector$", $segment, "$null")] fn __unique_ptr_null(this: *mut *mut c_void); } } @@ -183,10 +348,11 @@ macro_rules! impl_vector_element { unsafe { __unique_ptr_null(&mut repr) } repr } + #[doc(hidden)] unsafe fn __unique_ptr_raw(raw: *mut CxxVector<Self>) -> *mut c_void { extern "C" { attr! { - #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$raw")] + #[link_name = concat!("cxxbridge1$unique_ptr$std$vector$", $segment, "$raw")] fn __unique_ptr_raw(this: *mut *mut c_void, raw: *mut CxxVector<$ty>); } } @@ -194,28 +360,31 @@ macro_rules! impl_vector_element { __unique_ptr_raw(&mut repr, raw); repr } + #[doc(hidden)] unsafe fn __unique_ptr_get(repr: *mut c_void) -> *const CxxVector<Self> { extern "C" { attr! { - #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$get")] + #[link_name = concat!("cxxbridge1$unique_ptr$std$vector$", $segment, "$get")] fn __unique_ptr_get(this: *const *mut c_void) -> *const CxxVector<$ty>; } } __unique_ptr_get(&repr) } + #[doc(hidden)] unsafe fn __unique_ptr_release(mut repr: *mut c_void) -> *mut CxxVector<Self> { extern "C" { attr! { - #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$release")] + #[link_name = concat!("cxxbridge1$unique_ptr$std$vector$", $segment, "$release")] fn __unique_ptr_release(this: *mut *mut c_void) -> *mut CxxVector<$ty>; } } __unique_ptr_release(&mut repr) } + #[doc(hidden)] unsafe fn __unique_ptr_drop(mut repr: *mut c_void) { extern "C" { attr! { - #[link_name = concat!("cxxbridge05$unique_ptr$std$vector$", $segment, "$drop")] + #[link_name = concat!("cxxbridge1$unique_ptr$std$vector$", $segment, "$drop")] fn __unique_ptr_drop(this: *mut *mut c_void); } } diff --git a/src/exception.rs b/src/exception.rs index 0ffca66a..f61e8fa9 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -1,7 +1,7 @@ use alloc::boxed::Box; use core::fmt::{self, Debug, Display}; -/// Exception thrown from an `extern "C"` function. +/// Exception thrown from an `extern "C++"` function. #[derive(Debug)] pub struct Exception { pub(crate) what: Box<str>, diff --git a/src/extern_type.rs b/src/extern_type.rs index f92ff40c..35057acb 100644 --- a/src/extern_type.rs +++ b/src/extern_type.rs @@ -1,4 +1,6 @@ use self::kind::{Kind, Opaque, Trivial}; +use crate::CxxString; +use alloc::string::String; /// A type for which the layout is determined by its C++ definition. /// @@ -30,7 +32,7 @@ use self::kind::{Kind, Opaque, Trivial}; /// # mod file1 { /// #[cxx::bridge(namespace = "example")] /// pub mod ffi { -/// extern "C" { +/// unsafe extern "C++" { /// type Demo; /// /// fn create_demo() -> UniquePtr<Demo>; @@ -41,7 +43,7 @@ use self::kind::{Kind, Opaque, Trivial}; /// // file2.rs /// #[cxx::bridge(namespace = "example")] /// pub mod ffi { -/// extern "C" { +/// unsafe extern "C++" { /// type Demo = crate::file1::ffi::Demo; /// /// fn take_ref_demo(demo: &Demo); @@ -80,7 +82,7 @@ use self::kind::{Kind, Opaque, Trivial}; /// /// #[cxx::bridge(namespace = "folly")] /// pub mod ffi { -/// extern "C" { +/// unsafe extern "C++" { /// include!("rust_cxx_bindings.h"); /// /// type StringPiece = crate::folly_sys::StringPiece; @@ -181,3 +183,36 @@ pub fn verify_extern_type<T: ExternType<Id = Id>, Id>() {} #[doc(hidden)] pub fn verify_extern_kind<T: ExternType<Kind = Kind>, Kind: self::Kind>() {} + +macro_rules! impl_extern_type { + ($([$kind:ident] $($ty:path = $cxxpath:literal)*)*) => { + $($( + unsafe impl ExternType for $ty { + #[doc(hidden)] + type Id = crate::type_id!($cxxpath); + type Kind = $kind; + } + )*)* + }; +} + +impl_extern_type! { + [Trivial] + bool = "bool" + u8 = "std::uint8_t" + u16 = "std::uint16_t" + u32 = "std::uint32_t" + u64 = "std::uint64_t" + usize = "size_t" + i8 = "std::int8_t" + i16 = "std::int16_t" + i32 = "std::int32_t" + i64 = "std::int64_t" + isize = "rust::isize" + f32 = "float" + f64 = "double" + String = "rust::String" + + [Opaque] + CxxString = "std::string" +} diff --git a/src/fmt.rs b/src/fmt.rs new file mode 100644 index 00000000..db14a7c0 --- /dev/null +++ b/src/fmt.rs @@ -0,0 +1,16 @@ +use std::fmt::{self, Display}; + +pub(crate) fn display(fmt: impl Fn(&mut fmt::Formatter) -> fmt::Result) -> impl Display { + DisplayInvoke(fmt) +} + +struct DisplayInvoke<T>(T); + +impl<T> Display for DisplayInvoke<T> +where + T: Fn(&mut fmt::Formatter) -> fmt::Result, +{ + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + (self.0)(formatter) + } +} @@ -18,11 +18,18 @@ //! //! <br> //! -//! *Compiler support: requires rustc 1.43+ and c++11 or newer*<br> +//! *Compiler support: requires rustc 1.48+ and c++11 or newer*<br> //! *[Release notes](https://github.com/dtolnay/cxx/releases)* //! //! <br> //! +//! # Guide +//! +//! Please see **<https://cxx.rs>** for a tutorial, reference material, and +//! example code. +//! +//! <br> +//! //! # Overview //! //! The idea is that we define the signatures of both sides of our FFI boundary @@ -86,7 +93,7 @@ //! fn next_chunk(buf: &mut MultiBuf) -> &[u8]; //! } //! -//! extern "C++" { +//! unsafe 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. @@ -156,20 +163,20 @@ //! - **Functions** — implemented in either language, callable from the //! other language. //! -//! Within the `extern "C"` part of the CXX bridge we list the types and -//! functions for which C++ is the source of truth, as well as the header(s) -//! that declare those APIs. In the future it's possible that this section could -//! be generated bindgen-style from the headers but for now we need the -//! signatures written out; static assertions will verify that they are -//! accurate. -//! -//! Within the `extern "Rust"` part, we list types and functions for which Rust -//! is the source of truth. These all implicitly refer to the `super` module, -//! the parent module of the CXX bridge. You can think of the two items listed -//! in the example above as being like `use super::ThingR` and `use -//! super::print_r` except re-exported to C++. The parent module will either -//! contain the definitions directly for simple things, or contain the relevant -//! `use` statements to bring them into scope from elsewhere. +//! Within the `extern "Rust"` part of the CXX bridge we list the types and +//! functions for which Rust is the source of truth. These all implicitly refer +//! to the `super` module, the parent module of the CXX bridge. You can think of +//! the two items listed in the example above as being like `use +//! super::MultiBuf` and `use super::next_chunk` except re-exported to C++. The +//! parent module will either contain the definitions directly for simple +//! things, or contain the relevant `use` statements to bring them into scope +//! from elsewhere. +//! +//! Within the `extern "C++"` part, we list types and functions for which C++ is +//! the source of truth, as well as the header(s) that declare those APIs. In +//! the future it's possible that this section could be generated bindgen-style +//! from the headers but for now we need the signatures written out; static +//! assertions will verify that they are accurate. //! //! Your function implementations themselves, whether in C++ or Rust, *do not* //! need to be defined as `extern "C"` ABI or no\_mangle. CXX will put in the @@ -235,7 +242,7 @@ //! # Cargo.toml //! //! [build-dependencies] -//! cxx-build = "0.5" +//! cxx-build = "1.0" //! ``` //! //! ```no_run @@ -324,12 +331,16 @@ //! <tr><th>name in Rust</th><th>name in C++</th><th>restrictions</th></tr> //! <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>&[T]</td><td>rust::Slice<const T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr> +//! <tr><td>&mut [T]</td><td>rust::Slice<T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr> //! <tr><td><a href="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="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="struct.SharedPtr.html">SharedPtr<T></a></td><td>std::shared_ptr<T></td><td><sup><i>cannot hold opaque Rust type</i></sup></td></tr> +//! <tr><td>[T; N]</td><td>std::array<T, N></td><td><sup><i>cannot hold opaque C++ 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="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>*mut T, *const T</td><td>T*, const T*</td><td><sup><i>fn with a raw pointer argument must be declared unsafe to call</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> @@ -350,65 +361,77 @@ //! <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> #![no_std] -#![doc(html_root_url = "https://docs.rs/cxx/0.5.9")] +#![doc(html_root_url = "https://docs.rs/cxx/1.0.42")] #![deny(improper_ctypes)] #![allow(non_camel_case_types)] #![allow( clippy::cognitive_complexity, clippy::declare_interior_mutable_const, + clippy::doc_markdown, + clippy::empty_enum, clippy::inherent_to_string, + clippy::items_after_statements, clippy::large_enum_variant, clippy::len_without_is_empty, + clippy::missing_errors_doc, clippy::missing_safety_doc, clippy::module_inception, + clippy::module_name_repetitions, + clippy::must_use_candidate, clippy::needless_doctest_main, clippy::new_without_default, clippy::or_fun_call, clippy::ptr_arg, clippy::toplevel_ref_arg, - clippy::useless_let_if_seq + clippy::useless_let_if_seq, + clippy::wrong_self_convention )] #[cfg(built_with_cargo)] extern crate link_cplusplus; extern crate alloc; +extern crate self as cxx; extern crate std; #[macro_use] mod macros; -mod cxx_string; mod cxx_vector; mod exception; mod extern_type; +mod fmt; mod function; +pub mod memory; mod opaque; mod result; -mod rust_sliceu8; +mod rust_slice; mod rust_str; mod rust_string; +mod rust_type; mod rust_vec; +mod shared_ptr; +#[path = "cxx_string.rs"] +mod string; mod symbols; +mod type_id; mod unique_ptr; mod unwind; +pub mod vector; +mod weak_ptr; -pub use crate::cxx_string::CxxString; pub use crate::cxx_vector::CxxVector; pub use crate::exception::Exception; pub use crate::extern_type::{kind, ExternType}; +pub use crate::shared_ptr::SharedPtr; +pub use crate::string::CxxString; pub use crate::unique_ptr::UniquePtr; +pub use crate::weak_ptr::WeakPtr; 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 @@ -431,12 +454,21 @@ pub mod private { pub use crate::function::FatFunction; pub use crate::opaque::Opaque; pub use crate::result::{r#try, Result}; - pub use crate::rust_sliceu8::RustSliceU8; + pub use crate::rust_slice::RustSlice; pub use crate::rust_str::RustStr; pub use crate::rust_string::RustString; + pub use crate::rust_type::{ImplBox, ImplVec, RustType}; pub use crate::rust_vec::RustVec; + pub use crate::shared_ptr::SharedPtrTarget; + pub use crate::string::StackString; pub use crate::unique_ptr::UniquePtrTarget; pub use crate::unwind::catch_unwind; + pub use crate::weak_ptr::WeakPtrTarget; + pub use cxxbridge_macro::type_id; +} + +mod actually_private { + pub trait Private {} } macro_rules! chars { diff --git a/src/memory.rs b/src/memory.rs new file mode 100644 index 00000000..441d3d89 --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,8 @@ +//! Less used details of `UniquePtr` and `SharedPtr`. +//! +//! The pointer types themselves are exposed at the crate root. + +pub use crate::shared_ptr::SharedPtrTarget; +pub use crate::unique_ptr::UniquePtrTarget; +#[doc(no_inline)] +pub use cxx::{SharedPtr, UniquePtr}; diff --git a/src/opaque.rs b/src/opaque.rs index bad57e76..3c8f5362 100644 --- a/src/opaque.rs +++ b/src/opaque.rs @@ -1,3 +1,4 @@ +use core::marker::{PhantomData, PhantomPinned}; use core::mem; // . size = 0 @@ -5,9 +6,11 @@ use core::mem; // . ffi-safe // . !Send // . !Sync +// . !Unpin #[repr(C, packed)] pub struct Opaque { _private: [*const u8; 0], + _pinned: PhantomData<PhantomPinned>, } const_assert_eq!(0, mem::size_of::<Opaque>()); diff --git a/src/result.rs b/src/result.rs index fcced769..f41639a1 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,16 +1,22 @@ use crate::exception::Exception; -use crate::rust_str::RustStr; use alloc::boxed::Box; use alloc::string::{String, ToString}; use core::fmt::Display; -use core::ptr; +use core::ptr::{self, NonNull}; use core::result::Result as StdResult; use core::slice; use core::str; #[repr(C)] +#[derive(Copy, Clone)] +struct PtrLen { + ptr: NonNull<u8>, + len: usize, +} + +#[repr(C)] pub union Result { - err: RustStr, + err: PtrLen, ok: *const u8, // null } @@ -34,14 +40,12 @@ unsafe fn to_c_error(msg: String) -> Result { let len = msg.len(); extern "C" { - #[link_name = "cxxbridge05$error"] - fn error(ptr: *const u8, len: usize) -> *const u8; + #[link_name = "cxxbridge1$error"] + fn error(ptr: *const u8, len: usize) -> NonNull<u8>; } let copy = error(ptr, len); - let slice = slice::from_raw_parts(copy, len); - let string = str::from_utf8_unchecked(slice); - let err = RustStr::from(string); + let err = PtrLen { ptr: copy, len }; Result { err } } diff --git a/src/rust_slice.rs b/src/rust_slice.rs new file mode 100644 index 00000000..b4b5f2cb --- /dev/null +++ b/src/rust_slice.rs @@ -0,0 +1,41 @@ +use core::mem; +use core::ptr::{self, NonNull}; +use core::slice; + +#[repr(C)] +pub struct RustSlice { + pub(crate) repr: NonNull<[()]>, +} + +impl RustSlice { + pub fn from_ref<T>(slice: &[T]) -> Self { + let ptr = ptr::slice_from_raw_parts::<()>(slice.as_ptr().cast(), slice.len()); + RustSlice { + repr: unsafe { NonNull::new_unchecked(ptr as *mut _) }, + } + } + + pub fn from_mut<T>(slice: &mut [T]) -> Self { + let ptr = ptr::slice_from_raw_parts_mut(slice.as_mut_ptr().cast(), slice.len()); + RustSlice { + repr: unsafe { NonNull::new_unchecked(ptr) }, + } + } + + pub unsafe fn as_slice<'a, T>(self) -> &'a [T] { + let ptr = self.repr.as_ptr(); + let len = self.repr.as_ref().len(); + slice::from_raw_parts(ptr.cast(), len) + } + + pub unsafe fn as_mut_slice<'a, T>(self) -> &'a mut [T] { + let ptr = self.repr.as_ptr(); + let len = self.repr.as_ref().len(); + slice::from_raw_parts_mut(ptr.cast(), len) + } +} + +const_assert_eq!( + mem::size_of::<Option<RustSlice>>(), + mem::size_of::<RustSlice>(), +); diff --git a/src/rust_sliceu8.rs b/src/rust_sliceu8.rs deleted file mode 100644 index 32f8798b..00000000 --- a/src/rust_sliceu8.rs +++ /dev/null @@ -1,29 +0,0 @@ -use core::mem; -use core::ptr::NonNull; -use core::slice; - -// Not necessarily ABI compatible with &[u8]. Codegen performs the translation. -#[repr(C)] -#[derive(Copy, Clone)] -pub struct RustSliceU8 { - pub(crate) ptr: NonNull<u8>, - pub(crate) len: usize, -} - -impl RustSliceU8 { - pub fn from(s: &[u8]) -> Self { - RustSliceU8 { - ptr: NonNull::from(s).cast::<u8>(), - len: s.len(), - } - } - - pub unsafe fn as_slice<'a>(self) -> &'a [u8] { - slice::from_raw_parts(self.ptr.as_ptr(), self.len) - } -} - -const_assert_eq!( - mem::size_of::<Option<RustSliceU8>>(), - mem::size_of::<RustSliceU8>(), -); diff --git a/src/rust_str.rs b/src/rust_str.rs index 38e5cabf..9bc26145 100644 --- a/src/rust_str.rs +++ b/src/rust_str.rs @@ -1,27 +1,20 @@ 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)] -#[derive(Copy, Clone)] pub struct RustStr { - pub(crate) ptr: NonNull<u8>, - pub(crate) len: usize, + repr: NonNull<str>, } impl RustStr { - pub fn from(s: &str) -> Self { - RustStr { - ptr: NonNull::from(s).cast::<u8>(), - len: s.len(), - } + pub fn from(repr: &str) -> Self { + let repr = NonNull::from(repr); + RustStr { repr } } pub unsafe fn as_str<'a>(self) -> &'a str { - let slice = slice::from_raw_parts(self.ptr.as_ptr(), self.len); - str::from_utf8_unchecked(slice) + &*self.repr.as_ptr() } } diff --git a/src/rust_type.rs b/src/rust_type.rs new file mode 100644 index 00000000..7bcf440d --- /dev/null +++ b/src/rust_type.rs @@ -0,0 +1,3 @@ +pub unsafe trait RustType {} +pub unsafe trait ImplBox {} +pub unsafe trait ImplVec {} diff --git a/src/rust_vec.rs b/src/rust_vec.rs index 8ddc4a72..126fdbf9 100644 --- a/src/rust_vec.rs +++ b/src/rust_vec.rs @@ -41,6 +41,10 @@ impl<T> RustVec<T> { self.repr.len() } + pub fn capacity(&self) -> usize { + self.repr.capacity() + } + pub fn as_ptr(&self) -> *const T { self.repr.as_ptr() } diff --git a/src/shared_ptr.rs b/src/shared_ptr.rs new file mode 100644 index 00000000..66b988ba --- /dev/null +++ b/src/shared_ptr.rs @@ -0,0 +1,285 @@ +use crate::fmt::display; +use crate::kind::Trivial; +use crate::string::CxxString; +use crate::weak_ptr::{WeakPtr, WeakPtrTarget}; +use crate::ExternType; +use core::ffi::c_void; +use core::fmt::{self, Debug, Display}; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::ops::Deref; + +/// Binding to C++ `std::shared_ptr<T>`. +#[repr(C)] +pub struct SharedPtr<T> +where + T: SharedPtrTarget, +{ + repr: [*mut c_void; 2], + ty: PhantomData<T>, +} + +impl<T> SharedPtr<T> +where + T: SharedPtrTarget, +{ + /// Makes a new SharedPtr wrapping a null pointer. + /// + /// Matches the behavior of default-constructing a std::shared\_ptr. + pub fn null() -> Self { + let mut shared_ptr = MaybeUninit::<SharedPtr<T>>::uninit(); + let new = shared_ptr.as_mut_ptr().cast(); + unsafe { + T::__null(new); + shared_ptr.assume_init() + } + } + + /// Allocates memory on the heap and makes a SharedPtr owner for it. + pub fn new(value: T) -> Self + where + T: ExternType<Kind = Trivial>, + { + let mut shared_ptr = MaybeUninit::<SharedPtr<T>>::uninit(); + let new = shared_ptr.as_mut_ptr().cast(); + unsafe { + T::__new(value, new); + shared_ptr.assume_init() + } + } + + /// Checks whether the SharedPtr does not own an object. + /// + /// This is the opposite of [std::shared_ptr\<T\>::operator bool](https://en.cppreference.com/w/cpp/memory/shared_ptr/operator_bool). + pub fn is_null(&self) -> bool { + let this = self as *const Self as *const c_void; + let ptr = unsafe { T::__get(this) }; + ptr.is_null() + } + + /// Returns a reference to the object owned by this SharedPtr if any, + /// otherwise None. + pub fn as_ref(&self) -> Option<&T> { + let this = self as *const Self as *const c_void; + unsafe { T::__get(this).as_ref() } + } + + /// Constructs new WeakPtr as a non-owning reference to the object managed + /// by `self`. If `self` manages no object, the WeakPtr manages no object + /// too. + /// + /// Matches the behavior of [std::weak_ptr\<T\>::weak_ptr(const std::shared_ptr\<T\> \&)](https://en.cppreference.com/w/cpp/memory/weak_ptr/weak_ptr). + pub fn downgrade(self: &SharedPtr<T>) -> WeakPtr<T> + where + T: WeakPtrTarget, + { + let this = self as *const Self as *const c_void; + let mut weak_ptr = MaybeUninit::<WeakPtr<T>>::uninit(); + let new = weak_ptr.as_mut_ptr().cast(); + unsafe { + T::__downgrade(this, new); + weak_ptr.assume_init() + } + } +} + +unsafe impl<T> Send for SharedPtr<T> where T: Send + Sync + SharedPtrTarget {} +unsafe impl<T> Sync for SharedPtr<T> where T: Send + Sync + SharedPtrTarget {} + +impl<T> Clone for SharedPtr<T> +where + T: SharedPtrTarget, +{ + fn clone(&self) -> Self { + let mut shared_ptr = MaybeUninit::<SharedPtr<T>>::uninit(); + let new = shared_ptr.as_mut_ptr().cast(); + let this = self as *const Self as *mut c_void; + unsafe { + T::__clone(this, new); + shared_ptr.assume_init() + } + } +} + +impl<T> Drop for SharedPtr<T> +where + T: SharedPtrTarget, +{ + fn drop(&mut self) { + let this = self as *mut Self as *mut c_void; + unsafe { T::__drop(this) } + } +} + +impl<T> Deref for SharedPtr<T> +where + T: SharedPtrTarget, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + match self.as_ref() { + Some(target) => target, + None => panic!( + "called deref on a null SharedPtr<{}>", + display(T::__typename), + ), + } + } +} + +impl<T> Debug for SharedPtr<T> +where + T: Debug + SharedPtrTarget, +{ + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self.as_ref() { + None => formatter.write_str("nullptr"), + Some(value) => Debug::fmt(value, formatter), + } + } +} + +impl<T> Display for SharedPtr<T> +where + T: Display + SharedPtrTarget, +{ + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self.as_ref() { + None => formatter.write_str("nullptr"), + Some(value) => Display::fmt(value, formatter), + } + } +} + +/// Trait bound for types which may be used as the `T` inside of a +/// `SharedPtr<T>` in generic code. +/// +/// This trait has no publicly callable or implementable methods. Implementing +/// it outside of the CXX codebase is not supported. +/// +/// # Example +/// +/// A bound `T: SharedPtrTarget` may be necessary when manipulating +/// [`SharedPtr`] in generic code. +/// +/// ``` +/// use cxx::memory::{SharedPtr, SharedPtrTarget}; +/// use std::fmt::Display; +/// +/// pub fn take_generic_ptr<T>(ptr: SharedPtr<T>) +/// where +/// T: SharedPtrTarget + Display, +/// { +/// println!("the shared_ptr points to: {}", *ptr); +/// } +/// ``` +/// +/// Writing the same generic function without a `SharedPtrTarget` trait bound +/// would not compile. +pub unsafe trait SharedPtrTarget { + #[doc(hidden)] + fn __typename(f: &mut fmt::Formatter) -> fmt::Result; + #[doc(hidden)] + unsafe fn __null(new: *mut c_void); + #[doc(hidden)] + unsafe fn __new(value: Self, new: *mut c_void) + where + Self: Sized, + { + // Opoaque C types do not get this method because they can never exist + // by value on the Rust side of the bridge. + let _ = value; + let _ = new; + unreachable!() + } + #[doc(hidden)] + unsafe fn __clone(this: *const c_void, new: *mut c_void); + #[doc(hidden)] + unsafe fn __get(this: *const c_void) -> *const Self; + #[doc(hidden)] + unsafe fn __drop(this: *mut c_void); +} + +macro_rules! impl_shared_ptr_target { + ($segment:expr, $name:expr, $ty:ty) => { + unsafe impl SharedPtrTarget for $ty { + #[doc(hidden)] + fn __typename(f: &mut fmt::Formatter) -> fmt::Result { + f.write_str($name) + } + #[doc(hidden)] + unsafe fn __null(new: *mut c_void) { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$shared_ptr$", $segment, "$null")] + fn __null(new: *mut c_void); + } + } + __null(new); + } + #[doc(hidden)] + unsafe fn __new(value: Self, new: *mut c_void) { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$shared_ptr$", $segment, "$uninit")] + fn __uninit(new: *mut c_void) -> *mut c_void; + } + } + __uninit(new).cast::<$ty>().write(value); + } + #[doc(hidden)] + unsafe fn __clone(this: *const c_void, new: *mut c_void) { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$shared_ptr$", $segment, "$clone")] + fn __clone(this: *const c_void, new: *mut c_void); + } + } + __clone(this, new); + } + #[doc(hidden)] + unsafe fn __get(this: *const c_void) -> *const Self { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$shared_ptr$", $segment, "$get")] + fn __get(this: *const c_void) -> *const c_void; + } + } + __get(this).cast() + } + #[doc(hidden)] + unsafe fn __drop(this: *mut c_void) { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$shared_ptr$", $segment, "$drop")] + fn __drop(this: *mut c_void); + } + } + __drop(this); + } + } + }; +} + +macro_rules! impl_shared_ptr_target_for_primitive { + ($ty:ident) => { + impl_shared_ptr_target!(stringify!($ty), stringify!($ty), $ty); + }; +} + +impl_shared_ptr_target_for_primitive!(bool); +impl_shared_ptr_target_for_primitive!(u8); +impl_shared_ptr_target_for_primitive!(u16); +impl_shared_ptr_target_for_primitive!(u32); +impl_shared_ptr_target_for_primitive!(u64); +impl_shared_ptr_target_for_primitive!(usize); +impl_shared_ptr_target_for_primitive!(i8); +impl_shared_ptr_target_for_primitive!(i16); +impl_shared_ptr_target_for_primitive!(i32); +impl_shared_ptr_target_for_primitive!(i64); +impl_shared_ptr_target_for_primitive!(isize); +impl_shared_ptr_target_for_primitive!(f32); +impl_shared_ptr_target_for_primitive!(f64); + +impl_shared_ptr_target!("string", "CxxString", CxxString); diff --git a/src/symbols/exception.rs b/src/symbols/exception.rs index b408f255..0c1bb876 100644 --- a/src/symbols/exception.rs +++ b/src/symbols/exception.rs @@ -2,7 +2,7 @@ use alloc::boxed::Box; use alloc::string::String; use core::slice; -#[export_name = "cxxbridge05$exception"] +#[export_name = "cxxbridge1$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/mod.rs b/src/symbols/mod.rs index a9d158db..e00bb550 100644 --- a/src/symbols/mod.rs +++ b/src/symbols/mod.rs @@ -1,4 +1,5 @@ mod exception; +mod rust_slice; mod rust_str; mod rust_string; mod rust_vec; diff --git a/src/symbols/rust_slice.rs b/src/symbols/rust_slice.rs new file mode 100644 index 00000000..055b4dee --- /dev/null +++ b/src/symbols/rust_slice.rs @@ -0,0 +1,22 @@ +use crate::rust_slice::RustSlice; +use core::mem::MaybeUninit; +use core::ptr::{self, NonNull}; + +#[export_name = "cxxbridge1$slice$new"] +unsafe extern "C" fn slice_new(this: &mut MaybeUninit<RustSlice>, ptr: *const (), len: usize) { + let ptr = ptr::slice_from_raw_parts(ptr, len); + let rust_slice = RustSlice { + repr: NonNull::new_unchecked(ptr as *mut _), + }; + ptr::write(this.as_mut_ptr(), rust_slice); +} + +#[export_name = "cxxbridge1$slice$ptr"] +unsafe extern "C" fn slice_ptr(this: &RustSlice) -> *const () { + this.repr.as_ptr().cast() +} + +#[export_name = "cxxbridge1$slice$len"] +unsafe extern "C" fn slice_len(this: &RustSlice) -> usize { + this.repr.as_ref().len() +} diff --git a/src/symbols/rust_str.rs b/src/symbols/rust_str.rs index b6553818..a9e84efb 100644 --- a/src/symbols/rust_str.rs +++ b/src/symbols/rust_str.rs @@ -1,8 +1,37 @@ +use alloc::string::String; +use core::mem::MaybeUninit; +use core::ptr; use core::slice; use core::str; -#[export_name = "cxxbridge05$str$valid"] -unsafe extern "C" fn str_valid(ptr: *const u8, len: usize) -> bool { +#[export_name = "cxxbridge1$str$new"] +unsafe extern "C" fn str_new(this: &mut MaybeUninit<&str>) { + ptr::write(this.as_mut_ptr(), ""); +} + +#[export_name = "cxxbridge1$str$ref"] +unsafe extern "C" fn str_ref<'a>(this: &mut MaybeUninit<&'a str>, string: &'a String) { + ptr::write(this.as_mut_ptr(), string.as_str()); +} + +#[export_name = "cxxbridge1$str$from"] +unsafe extern "C" fn str_from(this: &mut MaybeUninit<&str>, ptr: *const u8, len: usize) -> bool { let slice = slice::from_raw_parts(ptr, len); - str::from_utf8(slice).is_ok() + match str::from_utf8(slice) { + Ok(s) => { + ptr::write(this.as_mut_ptr(), s); + true + } + Err(_) => false, + } +} + +#[export_name = "cxxbridge1$str$ptr"] +unsafe extern "C" fn str_ptr(this: &&str) -> *const u8 { + this.as_ptr() +} + +#[export_name = "cxxbridge1$str$len"] +unsafe extern "C" fn str_len(this: &&str) -> usize { + this.len() } diff --git a/src/symbols/rust_string.rs b/src/symbols/rust_string.rs index e5ab9eaa..91fd78a3 100644 --- a/src/symbols/rust_string.rs +++ b/src/symbols/rust_string.rs @@ -5,17 +5,17 @@ use core::ptr; use core::slice; use core::str; -#[export_name = "cxxbridge05$string$new"] +#[export_name = "cxxbridge1$string$new"] unsafe extern "C" fn string_new(this: &mut MaybeUninit<String>) { ptr::write(this.as_mut_ptr(), String::new()); } -#[export_name = "cxxbridge05$string$clone"] +#[export_name = "cxxbridge1$string$clone"] unsafe extern "C" fn string_clone(this: &mut MaybeUninit<String>, other: &String) { ptr::write(this.as_mut_ptr(), other.clone()); } -#[export_name = "cxxbridge05$string$from"] +#[export_name = "cxxbridge1$string$from"] unsafe extern "C" fn string_from( this: &mut MaybeUninit<String>, ptr: *const u8, @@ -31,17 +31,22 @@ unsafe extern "C" fn string_from( } } -#[export_name = "cxxbridge05$string$drop"] +#[export_name = "cxxbridge1$string$drop"] unsafe extern "C" fn string_drop(this: &mut ManuallyDrop<String>) { ManuallyDrop::drop(this); } -#[export_name = "cxxbridge05$string$ptr"] +#[export_name = "cxxbridge1$string$ptr"] unsafe extern "C" fn string_ptr(this: &String) -> *const u8 { this.as_ptr() } -#[export_name = "cxxbridge05$string$len"] +#[export_name = "cxxbridge1$string$len"] unsafe extern "C" fn string_len(this: &String) -> usize { this.len() } + +#[export_name = "cxxbridge1$string$reserve_total"] +unsafe extern "C" fn string_reserve_total(this: &mut String, cap: usize) { + this.reserve(cap); +} diff --git a/src/symbols/rust_vec.rs b/src/symbols/rust_vec.rs index 90fbc542..a26a1569 100644 --- a/src/symbols/rust_vec.rs +++ b/src/symbols/rust_vec.rs @@ -3,6 +3,7 @@ use crate::rust_vec::RustVec; use alloc::vec::Vec; use core::mem; use core::ptr; +use std::os::raw::c_char; macro_rules! rust_vec_shims { ($segment:expr, $ty:ty) => { @@ -11,47 +12,47 @@ macro_rules! rust_vec_shims { const _: () = { attr! { - #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$new")] + #[export_name = concat!("cxxbridge1$rust_vec$", $segment, "$new")] unsafe extern "C" fn __new(this: *mut RustVec<$ty>) { ptr::write(this, RustVec { repr: Vec::new() }); } } attr! { - #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$drop")] + #[export_name = concat!("cxxbridge1$rust_vec$", $segment, "$drop")] unsafe extern "C" fn __drop(this: *mut RustVec<$ty>) { ptr::drop_in_place(this); } } attr! { - #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$len")] + #[export_name = concat!("cxxbridge1$rust_vec$", $segment, "$len")] unsafe extern "C" fn __len(this: *const RustVec<$ty>) -> usize { (*this).repr.len() } } attr! { - #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$data")] + #[export_name = concat!("cxxbridge1$rust_vec$", $segment, "$capacity")] + unsafe extern "C" fn __capacity(this: *const RustVec<$ty>) -> usize { + (*this).repr.capacity() + } + } + attr! { + #[export_name = concat!("cxxbridge1$rust_vec$", $segment, "$data")] unsafe extern "C" fn __data(this: *const RustVec<$ty>) -> *const $ty { (*this).repr.as_ptr() } } attr! { - #[export_name = concat!("cxxbridge05$rust_vec$", $segment, "$reserve_total")] + #[export_name = concat!("cxxbridge1$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")] + #[export_name = concat!("cxxbridge1$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>() - } - } }; }; } @@ -67,11 +68,15 @@ rust_vec_shims_for_primitive!(u8); rust_vec_shims_for_primitive!(u16); rust_vec_shims_for_primitive!(u32); rust_vec_shims_for_primitive!(u64); +rust_vec_shims_for_primitive!(usize); rust_vec_shims_for_primitive!(i8); rust_vec_shims_for_primitive!(i16); rust_vec_shims_for_primitive!(i32); rust_vec_shims_for_primitive!(i64); +rust_vec_shims_for_primitive!(isize); rust_vec_shims_for_primitive!(f32); rust_vec_shims_for_primitive!(f64); +rust_vec_shims!("char", c_char); rust_vec_shims!("string", RustString); +rust_vec_shims!("str", &str); diff --git a/src/type_id.rs b/src/type_id.rs new file mode 100644 index 00000000..bd2b4ea6 --- /dev/null +++ b/src/type_id.rs @@ -0,0 +1,9 @@ +/// For use in impls of the `ExternType` trait. See [`ExternType`]. +/// +/// [`ExternType`]: trait.ExternType.html +#[macro_export] +macro_rules! type_id { + ($($path:tt)*) => { + $crate::private::type_id! { $crate $($path)* } + }; +} diff --git a/src/unique_ptr.rs b/src/unique_ptr.rs index 3a5fc669..836f4677 100644 --- a/src/unique_ptr.rs +++ b/src/unique_ptr.rs @@ -1,12 +1,14 @@ -use crate::cxx_string::CxxString; -use crate::cxx_vector::{self, CxxVector, VectorElement}; +use crate::cxx_vector::{CxxVector, VectorElement}; +use crate::fmt::display; use crate::kind::Trivial; +use crate::string::CxxString; 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::pin::Pin; use core::ptr; /// Binding to C++ `std::unique_ptr<T, std::default_delete<T>>`. @@ -58,10 +60,29 @@ where unsafe { T::__get(self.repr).as_ref() } } - /// Returns a mutable reference to the object owned by this UniquePtr if - /// any, otherwise None. - pub fn as_mut(&mut self) -> Option<&mut T> { - unsafe { (T::__get(self.repr) as *mut T).as_mut() } + /// Returns a mutable pinned reference to the object owned by this UniquePtr + /// if any, otherwise None. + pub fn as_mut(&mut self) -> Option<Pin<&mut T>> { + unsafe { + let mut_reference = (T::__get(self.repr) as *mut T).as_mut()?; + Some(Pin::new_unchecked(mut_reference)) + } + } + + /// Returns a mutable pinned reference to the object owned by this + /// UniquePtr. + /// + /// # Panics + /// + /// Panics if the UniquePtr holds a null pointer. + pub fn pin_mut(&mut self) -> Pin<&mut T> { + match self.as_mut() { + Some(target) => target, + None => panic!( + "called pin_mut on a null UniquePtr<{}>", + display(T::__typename), + ), + } } /// Consumes the UniquePtr, releasing its ownership of the heap-allocated T. @@ -110,19 +131,25 @@ where fn deref(&self) -> &Self::Target { match self.as_ref() { Some(target) => target, - None => panic!("called deref on a null UniquePtr<{}>", T::__NAME), + None => panic!( + "called deref on a null UniquePtr<{}>", + display(T::__typename), + ), } } } impl<T> DerefMut for UniquePtr<T> where - T: UniquePtrTarget, + T: UniquePtrTarget + Unpin, { fn deref_mut(&mut self) -> &mut Self::Target { match self.as_mut() { - Some(target) => target, - None => panic!("called deref_mut on a null UniquePtr<{}>", T::__NAME), + Some(target) => Pin::into_inner(target), + None => panic!( + "called deref_mut on a null UniquePtr<{}>", + display(T::__typename), + ), } } } @@ -151,11 +178,34 @@ where } } -// Methods are private; not intended to be implemented outside of cxxbridge -// codebase. +/// Trait bound for types which may be used as the `T` inside of a +/// `UniquePtr<T>` in generic code. +/// +/// This trait has no publicly callable or implementable methods. Implementing +/// it outside of the CXX codebase is not supported. +/// +/// # Example +/// +/// A bound `T: UniquePtrTarget` may be necessary when manipulating +/// [`UniquePtr`] in generic code. +/// +/// ``` +/// use cxx::memory::{UniquePtr, UniquePtrTarget}; +/// use std::fmt::Display; +/// +/// pub fn take_generic_ptr<T>(ptr: UniquePtr<T>) +/// where +/// T: UniquePtrTarget + Display, +/// { +/// println!("the unique_ptr points to: {}", *ptr); +/// } +/// ``` +/// +/// Writing the same generic function without a `UniquePtrTarget` trait bound +/// would not compile. pub unsafe trait UniquePtrTarget { #[doc(hidden)] - const __NAME: &'static dyn Display; + fn __typename(f: &mut fmt::Formatter) -> fmt::Result; #[doc(hidden)] fn __null() -> *mut c_void; #[doc(hidden)] @@ -179,20 +229,24 @@ pub unsafe trait UniquePtrTarget { } extern "C" { - #[link_name = "cxxbridge05$unique_ptr$std$string$null"] + #[link_name = "cxxbridge1$unique_ptr$std$string$null"] fn unique_ptr_std_string_null(this: *mut *mut c_void); - #[link_name = "cxxbridge05$unique_ptr$std$string$raw"] + #[link_name = "cxxbridge1$unique_ptr$std$string$raw"] fn unique_ptr_std_string_raw(this: *mut *mut c_void, raw: *mut CxxString); - #[link_name = "cxxbridge05$unique_ptr$std$string$get"] + #[link_name = "cxxbridge1$unique_ptr$std$string$get"] fn unique_ptr_std_string_get(this: *const *mut c_void) -> *const CxxString; - #[link_name = "cxxbridge05$unique_ptr$std$string$release"] + #[link_name = "cxxbridge1$unique_ptr$std$string$release"] fn unique_ptr_std_string_release(this: *mut *mut c_void) -> *mut CxxString; - #[link_name = "cxxbridge05$unique_ptr$std$string$drop"] + #[link_name = "cxxbridge1$unique_ptr$std$string$drop"] fn unique_ptr_std_string_drop(this: *mut *mut c_void); } unsafe impl UniquePtrTarget for CxxString { - const __NAME: &'static dyn Display = &"CxxString"; + #[doc(hidden)] + fn __typename(f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("CxxString") + } + #[doc(hidden)] fn __null() -> *mut c_void { let mut repr = ptr::null_mut::<c_void>(); unsafe { @@ -200,17 +254,21 @@ unsafe impl UniquePtrTarget for CxxString { } repr } + #[doc(hidden)] unsafe fn __raw(raw: *mut Self) -> *mut c_void { let mut repr = ptr::null_mut::<c_void>(); unique_ptr_std_string_raw(&mut repr, raw); repr } + #[doc(hidden)] unsafe fn __get(repr: *mut c_void) -> *const Self { unique_ptr_std_string_get(&repr) } + #[doc(hidden)] unsafe fn __release(mut repr: *mut c_void) -> *mut Self { unique_ptr_std_string_release(&mut repr) } + #[doc(hidden)] unsafe fn __drop(mut repr: *mut c_void) { unique_ptr_std_string_drop(&mut repr); } @@ -218,21 +276,29 @@ unsafe impl UniquePtrTarget for CxxString { unsafe impl<T> UniquePtrTarget for CxxVector<T> where - T: VectorElement + 'static, + T: VectorElement, { - const __NAME: &'static dyn Display = &cxx_vector::TypeName::<T>::new(); + #[doc(hidden)] + fn __typename(f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "CxxVector<{}>", display(T::__typename)) + } + #[doc(hidden)] fn __null() -> *mut c_void { T::__unique_ptr_null() } + #[doc(hidden)] unsafe fn __raw(raw: *mut Self) -> *mut c_void { T::__unique_ptr_raw(raw) } + #[doc(hidden)] unsafe fn __get(repr: *mut c_void) -> *const Self { T::__unique_ptr_get(repr) } + #[doc(hidden)] unsafe fn __release(repr: *mut c_void) -> *mut Self { T::__unique_ptr_release(repr) } + #[doc(hidden)] unsafe fn __drop(repr: *mut c_void) { T::__unique_ptr_drop(repr); } diff --git a/src/vector.rs b/src/vector.rs new file mode 100644 index 00000000..4afd4879 --- /dev/null +++ b/src/vector.rs @@ -0,0 +1,9 @@ +//! Less used details of `CxxVector`. +//! +//! `CxxVector` itself is exposed at the crate root. + +pub use crate::cxx_vector::{Iter, IterMut, VectorElement}; +#[doc(inline)] +pub use crate::Vector; +#[doc(no_inline)] +pub use cxx::CxxVector; diff --git a/src/weak_ptr.rs b/src/weak_ptr.rs new file mode 100644 index 00000000..8291d59b --- /dev/null +++ b/src/weak_ptr.rs @@ -0,0 +1,192 @@ +use crate::shared_ptr::{SharedPtr, SharedPtrTarget}; +use crate::string::CxxString; +use core::ffi::c_void; +use core::fmt::{self, Debug}; +use core::marker::PhantomData; +use core::mem::MaybeUninit; + +/// Binding to C++ `std::weak_ptr<T>`. +/// +/// The typical way to construct a WeakPtr from Rust is by [downgrading] from a +/// SharedPtr. +/// +/// [downgrading]: crate::SharedPtr::downgrade +#[repr(C)] +pub struct WeakPtr<T> +where + T: WeakPtrTarget, +{ + repr: [*mut c_void; 2], + ty: PhantomData<T>, +} + +impl<T> WeakPtr<T> +where + T: WeakPtrTarget, +{ + /// Makes a new WeakPtr wrapping a null pointer. + /// + /// Matches the behavior of default-constructing a std::weak\_ptr. + pub fn null() -> Self { + let mut weak_ptr = MaybeUninit::<WeakPtr<T>>::uninit(); + let new = weak_ptr.as_mut_ptr().cast(); + unsafe { + T::__null(new); + weak_ptr.assume_init() + } + } + + /// Upgrades a non-owning reference into an owning reference if possible, + /// otherwise to a null reference. + /// + /// Matches the behavior of [std::weak_ptr\<T\>::lock](https://en.cppreference.com/w/cpp/memory/weak_ptr/lock). + pub fn upgrade(&self) -> SharedPtr<T> + where + T: SharedPtrTarget, + { + let this = self as *const Self as *const c_void; + let mut shared_ptr = MaybeUninit::<SharedPtr<T>>::uninit(); + let new = shared_ptr.as_mut_ptr().cast(); + unsafe { + T::__upgrade(this, new); + shared_ptr.assume_init() + } + } +} + +unsafe impl<T> Send for WeakPtr<T> where T: Send + Sync + WeakPtrTarget {} +unsafe impl<T> Sync for WeakPtr<T> where T: Send + Sync + WeakPtrTarget {} + +impl<T> Clone for WeakPtr<T> +where + T: WeakPtrTarget, +{ + fn clone(&self) -> Self { + let mut weak_ptr = MaybeUninit::<WeakPtr<T>>::uninit(); + let new = weak_ptr.as_mut_ptr().cast(); + let this = self as *const Self as *mut c_void; + unsafe { + T::__clone(this, new); + weak_ptr.assume_init() + } + } +} + +impl<T> Drop for WeakPtr<T> +where + T: WeakPtrTarget, +{ + fn drop(&mut self) { + let this = self as *mut Self as *mut c_void; + unsafe { T::__drop(this) } + } +} + +impl<T> Debug for WeakPtr<T> +where + T: Debug + WeakPtrTarget + SharedPtrTarget, +{ + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(&self.upgrade(), formatter) + } +} + +// Methods are private; not intended to be implemented outside of cxxbridge +// codebase. +pub unsafe trait WeakPtrTarget { + #[doc(hidden)] + fn __typename(f: &mut fmt::Formatter) -> fmt::Result; + #[doc(hidden)] + unsafe fn __null(new: *mut c_void); + #[doc(hidden)] + unsafe fn __clone(this: *const c_void, new: *mut c_void); + #[doc(hidden)] + unsafe fn __downgrade(shared: *const c_void, new: *mut c_void); + #[doc(hidden)] + unsafe fn __upgrade(weak: *const c_void, shared: *mut c_void); + #[doc(hidden)] + unsafe fn __drop(this: *mut c_void); +} + +macro_rules! impl_weak_ptr_target { + ($segment:expr, $name:expr, $ty:ty) => { + unsafe impl WeakPtrTarget for $ty { + #[doc(hidden)] + fn __typename(f: &mut fmt::Formatter) -> fmt::Result { + f.write_str($name) + } + #[doc(hidden)] + unsafe fn __null(new: *mut c_void) { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$weak_ptr$", $segment, "$null")] + fn __null(new: *mut c_void); + } + } + __null(new); + } + #[doc(hidden)] + unsafe fn __clone(this: *const c_void, new: *mut c_void) { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$weak_ptr$", $segment, "$clone")] + fn __clone(this: *const c_void, new: *mut c_void); + } + } + __clone(this, new); + } + #[doc(hidden)] + unsafe fn __downgrade(shared: *const c_void, weak: *mut c_void) { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$weak_ptr$", $segment, "$downgrade")] + fn __downgrade(shared: *const c_void, weak: *mut c_void); + } + } + __downgrade(shared, weak); + } + #[doc(hidden)] + unsafe fn __upgrade(weak: *const c_void, shared: *mut c_void) { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$weak_ptr$", $segment, "$upgrade")] + fn __upgrade(weak: *const c_void, shared: *mut c_void); + } + } + __upgrade(weak, shared); + } + #[doc(hidden)] + unsafe fn __drop(this: *mut c_void) { + extern "C" { + attr! { + #[link_name = concat!("cxxbridge1$std$weak_ptr$", $segment, "$drop")] + fn __drop(this: *mut c_void); + } + } + __drop(this); + } + } + }; +} + +macro_rules! impl_weak_ptr_target_for_primitive { + ($ty:ident) => { + impl_weak_ptr_target!(stringify!($ty), stringify!($ty), $ty); + }; +} + +impl_weak_ptr_target_for_primitive!(bool); +impl_weak_ptr_target_for_primitive!(u8); +impl_weak_ptr_target_for_primitive!(u16); +impl_weak_ptr_target_for_primitive!(u32); +impl_weak_ptr_target_for_primitive!(u64); +impl_weak_ptr_target_for_primitive!(usize); +impl_weak_ptr_target_for_primitive!(i8); +impl_weak_ptr_target_for_primitive!(i16); +impl_weak_ptr_target_for_primitive!(i32); +impl_weak_ptr_target_for_primitive!(i64); +impl_weak_ptr_target_for_primitive!(isize); +impl_weak_ptr_target_for_primitive!(f32); +impl_weak_ptr_target_for_primitive!(f64); + +impl_weak_ptr_target!("string", "CxxString", CxxString); diff --git a/syntax/atom.rs b/syntax/atom.rs index 7d0ef6b6..d4ad78f1 100644 --- a/syntax/atom.rs +++ b/syntax/atom.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Display}; #[derive(Copy, Clone, PartialEq)] pub enum Atom { Bool, + Char, // C char, not Rust char U8, U16, U32, @@ -30,6 +31,7 @@ impl Atom { use self::Atom::*; match s { "bool" => Some(Bool), + "c_char" => Some(Char), "u8" => Some(U8), "u16" => Some(U16), "u32" => Some(U32), @@ -60,6 +62,7 @@ impl AsRef<str> for Atom { use self::Atom::*; match self { Bool => "bool", + Char => "c_char", U8 => "u8", U16 => "u16", U32 => "u32", diff --git a/syntax/attrs.rs b/syntax/attrs.rs index af73b7a5..4808f2e1 100644 --- a/syntax/attrs.rs +++ b/syntax/attrs.rs @@ -1,22 +1,47 @@ use crate::syntax::namespace::Namespace; use crate::syntax::report::Errors; use crate::syntax::Atom::{self, *}; -use crate::syntax::{Derive, Doc}; -use proc_macro2::Ident; +use crate::syntax::{Derive, Doc, ForeignName}; +use proc_macro2::{Ident, TokenStream}; +use quote::ToTokens; use syn::parse::{ParseStream, Parser as _}; use syn::{Attribute, Error, LitStr, Path, Result, Token}; +// Intended usage: +// +// let mut doc = Doc::new(); +// let mut cxx_name = None; +// let mut rust_name = None; +// /* ... */ +// let attrs = attrs::parse( +// cx, +// item.attrs, +// attrs::Parser { +// doc: Some(&mut doc), +// cxx_name: Some(&mut cxx_name), +// rust_name: Some(&mut rust_name), +// /* ... */ +// ..Default::default() +// }, +// ); +// #[derive(Default)] pub struct Parser<'a> { pub doc: Option<&'a mut Doc>, pub derives: Option<&'a mut Vec<Derive>>, pub repr: Option<&'a mut Option<Atom>>, - pub cxx_name: Option<&'a mut Option<Ident>>, - pub rust_name: Option<&'a mut Option<Ident>>, pub namespace: Option<&'a mut Namespace>, + pub cxx_name: Option<&'a mut Option<ForeignName>>, + pub rust_name: Option<&'a mut Option<Ident>>, + + // Suppress clippy needless_update lint ("struct update has no effect, all + // the fields in the struct have already been specified") when preemptively + // writing `..Default::default()`. + pub(crate) _more: (), } -pub(super) fn parse(cx: &mut Errors, attrs: &[Attribute], mut parser: Parser) { +pub fn parse(cx: &mut Errors, attrs: Vec<Attribute>, mut parser: Parser) -> OtherAttrs { + let mut passthrough_attrs = Vec::new(); for attr in attrs { if attr.path.is_ident("doc") { match parse_doc_attribute.parse2(attr.tokens.clone()) { @@ -26,17 +51,23 @@ pub(super) fn parse(cx: &mut Errors, attrs: &[Attribute], mut parser: Parser) { continue; } } - Err(err) => return cx.push(err), + Err(err) => { + cx.push(err); + break; + } } } else if attr.path.is_ident("derive") { - match attr.parse_args_with(parse_derive_attribute) { + match attr.parse_args_with(|attr: ParseStream| parse_derive_attribute(cx, attr)) { Ok(attr) => { if let Some(derives) = &mut parser.derives { derives.extend(attr); continue; } } - Err(err) => return cx.push(err), + Err(err) => { + cx.push(err); + break; + } } } else if attr.path.is_ident("repr") { match attr.parse_args_with(parse_repr_attribute) { @@ -46,41 +77,74 @@ pub(super) fn parse(cx: &mut Errors, attrs: &[Attribute], mut parser: Parser) { continue; } } - Err(err) => return cx.push(err), + Err(err) => { + cx.push(err); + break; + } + } + } else if attr.path.is_ident("namespace") { + match parse_namespace_attribute.parse2(attr.tokens.clone()) { + Ok(attr) => { + if let Some(namespace) = &mut parser.namespace { + **namespace = attr; + continue; + } + } + Err(err) => { + cx.push(err); + break; + } } } else if attr.path.is_ident("cxx_name") { - match parse_function_alias_attribute.parse2(attr.tokens.clone()) { + match parse_cxx_name_attribute.parse2(attr.tokens.clone()) { Ok(attr) => { if let Some(cxx_name) = &mut parser.cxx_name { **cxx_name = Some(attr); continue; } } - Err(err) => return cx.push(err), + Err(err) => { + cx.push(err); + break; + } } } else if attr.path.is_ident("rust_name") { - match parse_function_alias_attribute.parse2(attr.tokens.clone()) { + match parse_rust_name_attribute.parse2(attr.tokens.clone()) { Ok(attr) => { if let Some(rust_name) = &mut parser.rust_name { **rust_name = Some(attr); continue; } } - Err(err) => 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) => { + cx.push(err); + break; } - Err(err) => return cx.push(err), + } + } else if attr.path.is_ident("allow") + || attr.path.is_ident("warn") + || attr.path.is_ident("deny") + || attr.path.is_ident("forbid") + || attr.path.is_ident("deprecated") + || attr.path.is_ident("must_use") + { + // https://doc.rust-lang.org/reference/attributes/diagnostics.html + passthrough_attrs.push(attr); + continue; + } else if attr.path.segments.len() > 1 { + let tool = &attr.path.segments.first().unwrap().ident; + if tool == "rustfmt" { + // Skip, rustfmt only needs to find it in the pre-expansion source file. + continue; + } else if tool == "clippy" { + passthrough_attrs.push(attr); + continue; } } - return cx.error(attr, "unsupported attribute"); + cx.error(attr, "unsupported attribute"); + break; } + OtherAttrs(passthrough_attrs) } fn parse_doc_attribute(input: ParseStream) -> Result<LitStr> { @@ -89,19 +153,20 @@ fn parse_doc_attribute(input: ParseStream) -> Result<LitStr> { Ok(lit) } -fn parse_derive_attribute(input: ParseStream) -> Result<Vec<Derive>> { - input - .parse_terminated::<Path, Token![,]>(Path::parse_mod_style)? - .into_iter() - .map(|path| { - if let Some(ident) = path.get_ident() { - if let Some(derive) = Derive::from(ident) { - return Ok(derive); - } +fn parse_derive_attribute(cx: &mut Errors, input: ParseStream) -> Result<Vec<Derive>> { + let paths = input.parse_terminated::<Path, Token![,]>(Path::parse_mod_style)?; + + let mut derives = Vec::new(); + for path in paths { + if let Some(ident) = path.get_ident() { + if let Some(derive) = Derive::from(ident) { + derives.push(derive); + continue; } - Err(Error::new_spanned(path, "unsupported derive")) - }) - .collect() + } + cx.error(path, "unsupported derive"); + } + Ok(derives) } fn parse_repr_attribute(input: ParseStream) -> Result<Atom> { @@ -121,7 +186,24 @@ fn parse_repr_attribute(input: ParseStream) -> Result<Atom> { )) } -fn parse_function_alias_attribute(input: ParseStream) -> Result<Ident> { +fn parse_namespace_attribute(input: ParseStream) -> Result<Namespace> { + input.parse::<Token![=]>()?; + let namespace = input.parse::<Namespace>()?; + Ok(namespace) +} + +fn parse_cxx_name_attribute(input: ParseStream) -> Result<ForeignName> { + input.parse::<Token![=]>()?; + if input.peek(LitStr) { + let lit: LitStr = input.parse()?; + ForeignName::parse(&lit.value(), lit.span()) + } else { + let ident: Ident = input.parse()?; + ForeignName::parse(&ident.to_string(), ident.span()) + } +} + +fn parse_rust_name_attribute(input: ParseStream) -> Result<Ident> { input.parse::<Token![=]>()?; if input.peek(LitStr) { let lit: LitStr = input.parse()?; @@ -131,8 +213,18 @@ fn parse_function_alias_attribute(input: ParseStream) -> Result<Ident> { } } -fn parse_namespace_attribute(input: ParseStream) -> Result<Namespace> { - input.parse::<Token![=]>()?; - let namespace = input.parse::<Namespace>()?; - Ok(namespace) +pub struct OtherAttrs(Vec<Attribute>); + +impl OtherAttrs { + pub fn none() -> Self { + OtherAttrs(Vec::new()) + } +} + +impl ToTokens for OtherAttrs { + fn to_tokens(&self, tokens: &mut TokenStream) { + for attr in &self.0 { + attr.to_tokens(tokens); + } + } } diff --git a/syntax/check.rs b/syntax/check.rs index e8a64486..bab2dec5 100644 --- a/syntax/check.rs +++ b/syntax/check.rs @@ -1,9 +1,9 @@ use crate::syntax::atom::Atom::{self, *}; use crate::syntax::report::Errors; -use crate::syntax::types::TrivialReason; +use crate::syntax::visit::{self, Visit}; use crate::syntax::{ - error, ident, Api, Enum, ExternFn, ExternType, Impl, Lang, Receiver, Ref, Slice, Struct, Ty1, - Type, Types, + error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, NamedType, Ptr, + Receiver, Ref, Signature, SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types, }; use proc_macro2::{Delimiter, Group, Ident, TokenStream}; use quote::{quote, ToTokens}; @@ -28,25 +28,31 @@ fn do_typecheck(cx: &mut Check) { for ty in cx.types { match ty { - Type::Ident(ident) => check_type_ident(cx, &ident.rust), + Type::Ident(ident) => check_type_ident(cx, ident), Type::RustBox(ptr) => check_type_box(cx, ptr), Type::RustVec(ty) => check_type_rust_vec(cx, ty), Type::UniquePtr(ptr) => check_type_unique_ptr(cx, ptr), + Type::SharedPtr(ptr) => check_type_shared_ptr(cx, ptr), + Type::WeakPtr(ptr) => check_type_weak_ptr(cx, ptr), Type::CxxVector(ptr) => check_type_cxx_vector(cx, ptr), Type::Ref(ty) => check_type_ref(cx, ty), - Type::Slice(ty) => check_type_slice(cx, ty), - _ => {} + Type::Ptr(ty) => check_type_ptr(cx, ty), + Type::Array(array) => check_type_array(cx, array), + Type::Fn(ty) => check_type_fn(cx, ty), + Type::SliceRef(ty) => check_type_slice_ref(cx, ty), + Type::Str(_) | Type::Void(_) => {} } } for api in cx.apis { match api { + Api::Include(_) => {} Api::Struct(strct) => check_api_struct(cx, strct), Api::Enum(enm) => check_api_enum(cx, enm), Api::CxxType(ety) | Api::RustType(ety) => check_api_type(cx, ety), Api::CxxFunction(efn) | Api::RustFunction(efn) => check_api_fn(cx, efn), + Api::TypeAlias(alias) => check_api_type_alias(cx, alias), Api::Impl(imp) => check_api_impl(cx, imp), - _ => {} } } } @@ -57,7 +63,8 @@ impl Check<'_> { } } -fn check_type_ident(cx: &mut Check, ident: &Ident) { +fn check_type_ident(cx: &mut Check, name: &NamedType) { + let ident = &name.rust; if Atom::from(ident).is_none() && !cx.types.structs.contains_key(ident) && !cx.types.enums.contains_key(ident) @@ -72,6 +79,7 @@ fn check_type_ident(cx: &mut Check, ident: &Ident) { fn check_type_box(cx: &mut Check, ptr: &Ty1) { if let Type::Ident(ident) = &ptr.inner { if cx.types.cxx.contains(&ident.rust) + && !cx.types.aliases.contains_key(&ident.rust) && !cx.types.structs.contains_key(&ident.rust) && !cx.types.enums.contains_key(&ident.rust) { @@ -87,22 +95,27 @@ fn check_type_box(cx: &mut Check, ptr: &Ty1) { } fn check_type_rust_vec(cx: &mut Check, ty: &Ty1) { - if let Type::Ident(ident) = &ty.inner { - 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 &ty.inner { + Type::Ident(ident) => { + if cx.types.cxx.contains(&ident.rust) + && !cx.types.aliases.contains_key(&ident.rust) + && !cx.types.structs.contains_key(&ident.rust) + && !cx.types.enums.contains_key(&ident.rust) + { + cx.error(ty, "Rust Vec containing C++ type is not supported yet"); + return; + } - match Atom::from(&ident.rust) { - None | Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize) | Some(I8) - | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32) | Some(F64) - | Some(RustString) => return, - Some(Bool) => { /* todo */ } - Some(CxxString) => {} + match Atom::from(&ident.rust) { + None | Some(Char) | Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize) + | Some(I8) | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32) + | Some(F64) | Some(RustString) => return, + Some(Bool) => { /* todo */ } + Some(CxxString) => {} + } } + Type::Str(_) => return, + _ => {} } cx.error(ty, "unsupported element type of Vec"); @@ -112,6 +125,7 @@ fn check_type_unique_ptr(cx: &mut Check, ptr: &Ty1) { if let Type::Ident(ident) = &ptr.inner { if cx.types.rust.contains(&ident.rust) { cx.error(ptr, "unique_ptr of a Rust type is not supported yet"); + return; } match Atom::from(&ident.rust) { @@ -125,6 +139,48 @@ fn check_type_unique_ptr(cx: &mut Check, ptr: &Ty1) { cx.error(ptr, "unsupported unique_ptr target type"); } +fn check_type_shared_ptr(cx: &mut Check, ptr: &Ty1) { + if let Type::Ident(ident) = &ptr.inner { + if cx.types.rust.contains(&ident.rust) { + cx.error(ptr, "shared_ptr of a Rust type is not supported yet"); + return; + } + + match Atom::from(&ident.rust) { + None | Some(Bool) | Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize) + | Some(I8) | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32) + | Some(F64) | Some(CxxString) => return, + Some(Char) | Some(RustString) => {} + } + } else if let Type::CxxVector(_) = &ptr.inner { + cx.error(ptr, "std::shared_ptr<std::vector> is not supported yet"); + return; + } + + cx.error(ptr, "unsupported shared_ptr target type"); +} + +fn check_type_weak_ptr(cx: &mut Check, ptr: &Ty1) { + if let Type::Ident(ident) = &ptr.inner { + if cx.types.rust.contains(&ident.rust) { + cx.error(ptr, "weak_ptr of a Rust type is not supported yet"); + return; + } + + match Atom::from(&ident.rust) { + None | Some(Bool) | Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize) + | Some(I8) | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32) + | Some(F64) | Some(CxxString) => return, + Some(Char) | Some(RustString) => {} + } + } else if let Type::CxxVector(_) = &ptr.inner { + cx.error(ptr, "std::weak_ptr<std::vector> is not supported yet"); + return; + } + + cx.error(ptr, "unsupported weak_ptr target type"); +} + fn check_type_cxx_vector(cx: &mut Check, ptr: &Ty1) { if let Type::Ident(ident) = &ptr.inner { if cx.types.rust.contains(&ident.rust) { @@ -132,22 +188,38 @@ fn check_type_cxx_vector(cx: &mut Check, ptr: &Ty1) { ptr, "C++ vector containing a Rust type is not supported yet", ); + return; } match Atom::from(&ident.rust) { None | Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize) | Some(I8) | Some(I16) | Some(I32) | Some(I64) | Some(Isize) | Some(F32) | Some(F64) | Some(CxxString) => return, + Some(Char) => { /* todo */ } Some(Bool) | Some(RustString) => {} } } - cx.error(ptr, "unsupported vector target type"); + cx.error(ptr, "unsupported vector element type"); } fn check_type_ref(cx: &mut Check, ty: &Ref) { - if ty.lifetime.is_some() { - cx.error(ty, "references with explicit lifetimes are not supported"); + if ty.mutable && !ty.pinned { + if let Some(requires_pin) = match &ty.inner { + Type::Ident(ident) if ident.rust == CxxString || is_opaque_cxx(cx, &ident.rust) => { + Some(ident.rust.to_string()) + } + Type::CxxVector(_) => Some("CxxVector<...>".to_owned()), + _ => None, + } { + cx.error( + ty, + format!( + "mutable reference to C++ type requires a pin -- use Pin<&mut {}>", + requires_pin, + ), + ); + } } match ty.inner { @@ -162,8 +234,61 @@ fn check_type_ref(cx: &mut Check, ty: &Ref) { cx.error(ty, "unsupported reference type"); } -fn check_type_slice(cx: &mut Check, ty: &Slice) { - cx.error(ty, "only &[u8] is supported so far, not other slice types"); +fn check_type_ptr(cx: &mut Check, ty: &Ptr) { + match ty.inner { + Type::Fn(_) | Type::Void(_) => {} + Type::Ref(_) => { + cx.error(ty, "C++ does not allow pointer to reference as a type"); + return; + } + _ => return, + } + + cx.error(ty, "unsupported pointer type"); +} + +fn check_type_slice_ref(cx: &mut Check, ty: &SliceRef) { + let supported = !is_unsized(cx, &ty.inner) + || match &ty.inner { + Type::Ident(ident) => cx.types.rust.contains(&ident.rust), + _ => false, + }; + + if !supported { + let mutable = if ty.mutable { "mut " } else { "" }; + let mut msg = format!("unsupported &{}[T] element type", mutable); + if let Type::Ident(ident) = &ty.inner { + if is_opaque_cxx(cx, &ident.rust) { + msg += ": opaque C++ type is not supported yet"; + } + } + cx.error(ty, msg); + } +} + +fn check_type_array(cx: &mut Check, ty: &Array) { + let supported = !is_unsized(cx, &ty.inner); + + if !supported { + cx.error(ty, "unsupported array element type"); + } +} + +fn check_type_fn(cx: &mut Check, ty: &Signature) { + if ty.throws { + cx.error(ty, "function pointer returning Result is not supported yet"); + } + + for arg in &ty.args { + if let Type::Ptr(_) = arg.ty { + if ty.unsafety.is_none() { + cx.error( + arg, + "pointer argument requires that the function pointer be marked unsafe", + ); + } + } + } } fn check_api_struct(cx: &mut Check, strct: &Struct) { @@ -182,17 +307,23 @@ fn check_api_struct(cx: &mut Check, strct: &Struct) { } } - for field in &strct.fields { - if is_unsized(cx, &field.ty) { - let desc = describe(cx, &field.ty); - let msg = format!("using {} by value is not supported", desc); - cx.error(field, msg); + for derive in &strct.derives { + if derive.what == Trait::ExternType { + let msg = format!("derive({}) on shared struct is not supported", derive); + cx.error(derive, msg); } + } + + for field in &strct.fields { if let Type::Fn(_) = field.ty { cx.error( field, "function pointers in a struct field are not implemented yet", ); + } else if is_unsized(cx, &field.ty) { + let desc = describe(cx, &field.ty); + let msg = format!("using {} by value is not supported", desc); + cx.error(field, msg); } } } @@ -200,63 +331,110 @@ fn check_api_struct(cx: &mut Check, strct: &Struct) { fn check_api_enum(cx: &mut Check, enm: &Enum) { check_reserved_name(cx, &enm.name.rust); - if enm.variants.is_empty() { + if enm.variants.is_empty() && !enm.explicit_repr { let span = span_for_enum_error(enm); - cx.error(span, "enums without any variants are not supported"); + cx.error( + span, + "explicit #[repr(...)] is required for enum without any variants", + ); + } + + for derive in &enm.derives { + if derive.what == Trait::Default || derive.what == Trait::ExternType { + let msg = format!("derive({}) on shared enum is not supported", derive); + cx.error(derive, msg); + } } } fn check_api_type(cx: &mut Check, ety: &ExternType) { check_reserved_name(cx, &ety.name.rust); - 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), + for derive in &ety.derives { + if derive.what == Trait::ExternType && ety.lang == Lang::Rust { + continue; + } + let lang = match ety.lang { + Lang::Rust => "Rust", + Lang::Cxx => "C++", }; let msg = format!( + "derive({}) on opaque {} type is not supported yet", + derive, lang, + ); + cx.error(derive, msg); + } + + if !ety.bounds.is_empty() { + let bounds = &ety.bounds; + let span = quote!(#(#bounds)*); + cx.error(span, "extern type bounds are not implemented yet"); + } + + if let Some(reasons) = cx.types.required_trivial.get(&ety.name.rust) { + let msg = format!( "needs a cxx::ExternType impl in order to be used as {}", - what, + trivial::as_what(&ety.name, reasons), ); cx.error(ety, msg); } } fn check_api_fn(cx: &mut Check, efn: &ExternFn) { + match efn.lang { + Lang::Cxx => { + if !efn.generics.params.is_empty() && !efn.trusted { + let ref span = span_for_generics_error(efn); + cx.error(span, "extern C++ function with lifetimes must be declared in `unsafe extern \"C++\"` block"); + } + } + Lang::Rust => { + if !efn.generics.params.is_empty() && efn.unsafety.is_none() { + let ref span = span_for_generics_error(efn); + let message = format!( + "must be `unsafe fn {}` in order to expose explicit lifetimes to C++", + efn.name.rust, + ); + cx.error(span, message); + } + } + } + if let Some(receiver) = &efn.receiver { let ref span = span_for_receiver_error(receiver); - if receiver.ty.is_self() { - let mutability = match receiver.mutability { - Some(_) => "mut ", - None => "", + if receiver.ty.rust == "Self" { + let mutability = match receiver.mutable { + true => "mut ", + false => "", }; let msg = format!( - "unnamed receiver type is only allowed if the surrounding \ - extern block contains exactly one extern type; \ - use `self: &{mutability}TheType`", + "unnamed receiver type is only allowed if the surrounding extern block contains exactly one extern type; use `self: &{mutability}TheType`", mutability = mutability, ); cx.error(span, msg); + } else if cx.types.enums.contains_key(&receiver.ty.rust) { + cx.error( + span, + "unsupported receiver type; C++ does not allow member functions on enums", + ); } else if !cx.types.structs.contains_key(&receiver.ty.rust) && !cx.types.cxx.contains(&receiver.ty.rust) && !cx.types.rust.contains(&receiver.ty.rust) { cx.error(span, "unrecognized receiver type"); - } - - if receiver.lifetime.is_some() { - cx.error(span, "references with explicit lifetimes are not supported"); + } else if receiver.mutable && !receiver.pinned && is_opaque_cxx(cx, &receiver.ty.rust) { + cx.error( + span, + format!( + "mutable reference to opaque C++ type requires a pin -- use `self: Pin<&mut {}>`", + receiver.ty.rust, + ), + ); } } for arg in &efn.args { - if is_unsized(cx, &arg.ty) { - let desc = describe(cx, &arg.ty); - let msg = format!("passing {} by value is not supported", desc); - cx.error(arg, msg); - } if let Type::Fn(_) = arg.ty { if efn.lang == Lang::Rust { cx.error( @@ -264,89 +442,141 @@ fn check_api_fn(cx: &mut Check, efn: &ExternFn) { "passing a function pointer from C++ to Rust is not implemented yet", ); } + } else if let Type::Ptr(_) = arg.ty { + if efn.sig.unsafety.is_none() { + cx.error( + arg, + "pointer argument requires that the function be marked unsafe", + ); + } + } else if is_unsized(cx, &arg.ty) { + let desc = describe(cx, &arg.ty); + let msg = format!("passing {} by value is not supported", desc); + cx.error(arg, msg); } } if let Some(ty) = &efn.ret { - if is_unsized(cx, ty) { + if let Type::Fn(_) = ty { + cx.error(ty, "returning a function pointer is not implemented yet"); + } else if is_unsized(cx, ty) { let desc = describe(cx, ty); let msg = format!("returning {} by value is not supported", desc); cx.error(ty, msg); } - if let Type::Fn(_) = ty { - cx.error(ty, "returning a function pointer is not implemented yet"); - } } if efn.lang == Lang::Cxx { check_mut_return_restriction(cx, efn); } +} - check_multiple_arg_lifetimes(cx, efn); +fn check_api_type_alias(cx: &mut Check, alias: &TypeAlias) { + for derive in &alias.derives { + let msg = format!("derive({}) on extern type alias is not supported", derive); + cx.error(derive, msg); + } } fn check_api_impl(cx: &mut Check, imp: &Impl) { - 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; + let ty = &imp.ty; + + if let Some(negative) = imp.negative_token { + let span = quote!(#negative #ty); + cx.error(span, "negative impl is not supported yet"); + return; + } + + match ty { + Type::RustBox(ty) + | Type::RustVec(ty) + | Type::UniquePtr(ty) + | Type::SharedPtr(ty) + | Type::WeakPtr(ty) + | Type::CxxVector(ty) => { + if let Type::Ident(inner) = &ty.inner { + if Atom::from(&inner.rust).is_none() { + return; + } } } + _ => {} } cx.error(imp, "unsupported Self type of explicit impl"); } fn check_mut_return_restriction(cx: &mut Check, efn: &ExternFn) { + if efn.sig.unsafety.is_some() { + // Unrestricted as long as the function is made unsafe-to-call. + return; + } + match &efn.ret { - Some(Type::Ref(ty)) if ty.mutability.is_some() => {} + Some(Type::Ref(ty)) if ty.mutable => {} + Some(Type::SliceRef(slice)) if slice.mutable => {} _ => return, } - for arg in &efn.args { - if let Type::Ref(ty) = &arg.ty { - if ty.mutability.is_some() { - return; - } + if let Some(receiver) = &efn.receiver { + if receiver.mutable { + return; + } + let resolve = match cx.types.try_resolve(&receiver.ty) { + Some(resolve) => resolve, + None => return, + }; + if !resolve.generics.lifetimes.is_empty() { + return; } } - cx.error( - efn, - "&mut return type is not allowed unless there is a &mut argument", - ); -} - -fn check_multiple_arg_lifetimes(cx: &mut Check, efn: &ExternFn) { - match &efn.ret { - Some(Type::Ref(_)) => {} - _ => return, + struct FindLifetimeMut<'a> { + cx: &'a Check<'a>, + found: bool, } - let mut reference_args = 0; - for arg in &efn.args { - if let Type::Ref(_) = &arg.ty { - reference_args += 1; + impl<'t, 'a> Visit<'t> for FindLifetimeMut<'a> { + fn visit_type(&mut self, ty: &'t Type) { + self.found |= match ty { + Type::Ref(ty) => ty.mutable, + Type::SliceRef(slice) => slice.mutable, + Type::Ident(ident) if Atom::from(&ident.rust).is_none() => { + match self.cx.types.try_resolve(ident) { + Some(resolve) => !resolve.generics.lifetimes.is_empty(), + None => true, + } + } + _ => false, + }; + visit::visit_type(self, ty); } } - if efn.receiver.is_some() { - reference_args += 1; + let mut visitor = FindLifetimeMut { cx, found: false }; + + for arg in &efn.args { + visitor.visit_type(&arg.ty); } - if reference_args != 1 { - cx.error( - efn, - "functions that return a reference must take exactly one input reference", - ); + if visitor.found { + return; } + + cx.error( + efn, + "&mut return type is not allowed unless there is a &mut argument", + ); } fn check_reserved_name(cx: &mut Check, ident: &Ident) { if ident == "Box" || ident == "UniquePtr" + || ident == "SharedPtr" + || ident == "WeakPtr" || ident == "Vec" || ident == "CxxVector" + || ident == "str" || Atom::from(ident).is_some() { cx.error(ident, "reserved name"); @@ -354,18 +584,30 @@ fn check_reserved_name(cx: &mut Check, ident: &Ident) { } fn is_unsized(cx: &mut Check, ty: &Type) -> bool { - let ident = match ty { - Type::Ident(ident) => &ident.rust, - Type::CxxVector(_) | Type::Slice(_) | Type::Void(_) => return true, - _ => return false, - }; - 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) + match ty { + Type::Ident(ident) => { + let ident = &ident.rust; + ident == CxxString || is_opaque_cxx(cx, ident) || cx.types.rust.contains(ident) + } + Type::Array(array) => is_unsized(cx, &array.inner), + Type::CxxVector(_) | Type::Fn(_) | Type::Void(_) => true, + Type::RustBox(_) + | Type::RustVec(_) + | Type::UniquePtr(_) + | Type::SharedPtr(_) + | Type::WeakPtr(_) + | Type::Ref(_) + | Type::Ptr(_) + | Type::Str(_) + | Type::SliceRef(_) => false, + } +} + +fn is_opaque_cxx(cx: &mut Check, ty: &Ident) -> bool { + cx.types.cxx.contains(ty) + && !cx.types.structs.contains_key(ty) + && !cx.types.enums.contains_key(ty) + && !(cx.types.aliases.contains_key(ty) && cx.types.required_trivial.contains_key(ty)) } fn span_for_struct_error(strct: &Struct) -> TokenStream { @@ -395,6 +637,13 @@ fn span_for_receiver_error(receiver: &Receiver) -> TokenStream { } } +fn span_for_generics_error(efn: &ExternFn) -> TokenStream { + let unsafety = efn.unsafety; + let fn_token = efn.fn_token; + let generics = &efn.generics; + quote!(#unsafety #fn_token #generics) +} + fn describe(cx: &mut Check, ty: &Type) -> String { match ty { Type::Ident(ident) => { @@ -410,6 +659,8 @@ fn describe(cx: &mut Check, ty: &Type) -> String { "opaque Rust type".to_owned() } else if Atom::from(&ident.rust) == Some(CxxString) { "C++ string".to_owned() + } else if Atom::from(&ident.rust) == Some(Char) { + "C char".to_owned() } else { ident.rust.to_string() } @@ -417,12 +668,15 @@ fn describe(cx: &mut Check, ty: &Type) -> String { Type::RustBox(_) => "Box".to_owned(), Type::RustVec(_) => "Vec".to_owned(), Type::UniquePtr(_) => "unique_ptr".to_owned(), + Type::SharedPtr(_) => "shared_ptr".to_owned(), + Type::WeakPtr(_) => "weak_ptr".to_owned(), Type::Ref(_) => "reference".to_owned(), + Type::Ptr(_) => "raw pointer".to_owned(), Type::Str(_) => "&str".to_owned(), Type::CxxVector(_) => "C++ vector".to_owned(), - Type::Slice(_) => "slice".to_owned(), - Type::SliceRefU8(_) => "&[u8]".to_owned(), + Type::SliceRef(_) => "slice".to_owned(), Type::Fn(_) => "function pointer".to_owned(), Type::Void(_) => "()".to_owned(), + Type::Array(_) => "array".to_owned(), } } diff --git a/syntax/derive.rs b/syntax/derive.rs index 435aa209..96b3eea7 100644 --- a/syntax/derive.rs +++ b/syntax/derive.rs @@ -1,26 +1,75 @@ -use proc_macro2::Ident; +use proc_macro2::{Ident, Span}; +use std::fmt::{self, Display}; + +#[derive(Copy, Clone)] +pub struct Derive { + pub what: Trait, + pub span: Span, +} #[derive(Copy, Clone, PartialEq)] -pub enum Derive { +pub enum Trait { Clone, Copy, + Debug, + Default, + Eq, + ExternType, + Hash, + Ord, + PartialEq, + PartialOrd, } impl Derive { pub fn from(ident: &Ident) -> Option<Self> { - match ident.to_string().as_str() { - "Clone" => Some(Derive::Clone), - "Copy" => Some(Derive::Copy), - _ => None, - } + let what = match ident.to_string().as_str() { + "Clone" => Trait::Clone, + "Copy" => Trait::Copy, + "Debug" => Trait::Debug, + "Default" => Trait::Default, + "Eq" => Trait::Eq, + "ExternType" => Trait::ExternType, + "Hash" => Trait::Hash, + "Ord" => Trait::Ord, + "PartialEq" => Trait::PartialEq, + "PartialOrd" => Trait::PartialOrd, + _ => return None, + }; + let span = ident.span(); + Some(Derive { what, span }) + } +} + +impl PartialEq<Trait> for Derive { + fn eq(&self, other: &Trait) -> bool { + self.what == *other } } -impl AsRef<str> for Derive { +impl AsRef<str> for Trait { fn as_ref(&self) -> &str { match self { - Derive::Clone => "Clone", - Derive::Copy => "Copy", + Trait::Clone => "Clone", + Trait::Copy => "Copy", + Trait::Debug => "Debug", + Trait::Default => "Default", + Trait::Eq => "Eq", + Trait::ExternType => "ExternType", + Trait::Hash => "Hash", + Trait::Ord => "Ord", + Trait::PartialEq => "PartialEq", + Trait::PartialOrd => "PartialOrd", } } } + +impl Display for Derive { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.what.as_ref()) + } +} + +pub fn contains(derives: &[Derive], query: Trait) -> bool { + derives.iter().any(|derive| derive.what == query) +} diff --git a/syntax/discriminant.rs b/syntax/discriminant.rs index 6a04d881..80f7c0be 100644 --- a/syntax/discriminant.rs +++ b/syntax/discriminant.rs @@ -16,10 +16,16 @@ pub struct DiscriminantSet { #[derive(Copy, Clone, Eq, PartialEq)] pub struct Discriminant { - negative: bool, + sign: Sign, magnitude: u64, } +#[derive(Copy, Clone, Eq, PartialEq)] +enum Sign { + Negative, + Positive, +} + impl DiscriminantSet { pub fn new(repr: Option<Atom>) -> Self { DiscriminantSet { @@ -59,21 +65,23 @@ impl DiscriminantSet { pub fn insert_next(&mut self) -> Result<Discriminant> { let discriminant = match self.previous { None => Discriminant::zero(), - Some(mut discriminant) if discriminant.negative => { - discriminant.magnitude -= 1; - if discriminant.magnitude == 0 { - discriminant.negative = false; + Some(mut discriminant) => match discriminant.sign { + Sign::Negative => { + discriminant.magnitude -= 1; + if discriminant.magnitude == 0 { + discriminant.sign = Sign::Positive; + } + discriminant } - discriminant - } - Some(mut discriminant) => { - if discriminant.magnitude == u64::MAX { - let msg = format!("discriminant overflow on value after {}", u64::MAX); - return Err(Error::new(Span::call_site(), msg)); + Sign::Positive => { + if discriminant.magnitude == u64::MAX { + let msg = format!("discriminant overflow on value after {}", u64::MAX); + return Err(Error::new(Span::call_site(), msg)); + } + discriminant.magnitude += 1; + discriminant } - discriminant.magnitude += 1; - discriminant - } + }, }; insert(self, discriminant) } @@ -109,7 +117,10 @@ fn expr_to_discriminant(expr: &Expr) -> Result<(Discriminant, Option<Atom>)> { Expr::Unary(unary) => { if let UnOp::Neg(_) = unary.op { let (mut discriminant, repr) = expr_to_discriminant(&unary.expr)?; - discriminant.negative ^= true; + discriminant.sign = match discriminant.sign { + Sign::Positive => Sign::Negative, + Sign::Negative => Sign::Positive, + }; return Ok((discriminant, repr)); } } @@ -133,33 +144,33 @@ fn insert(set: &mut DiscriminantSet, discriminant: Discriminant) -> Result<Discr } } } - if set.values.insert(discriminant) { - set.previous = Some(discriminant); - Ok(discriminant) - } else { - let msg = format!("discriminant value `{}` already exists", discriminant); - Err(Error::new(Span::call_site(), msg)) - } + set.values.insert(discriminant); + set.previous = Some(discriminant); + Ok(discriminant) } impl Discriminant { const fn zero() -> Self { Discriminant { - negative: false, + sign: Sign::Positive, magnitude: 0, } } const fn pos(u: u64) -> Self { Discriminant { - negative: false, + sign: Sign::Positive, magnitude: u, } } const fn neg(i: i64) -> Self { Discriminant { - negative: i < 0, + sign: if i < 0 { + Sign::Negative + } else { + Sign::Positive + }, // This is `i.abs() as u64` but without overflow on MIN. Uses the // fact that MIN.wrapping_abs() wraps back to MIN whose binary // representation is 1<<63, and thus the `as u64` conversion @@ -172,7 +183,7 @@ impl Discriminant { impl Display for Discriminant { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.negative { + if self.sign == Sign::Negative { f.write_str("-")?; } Display::fmt(&self.magnitude, f) @@ -181,7 +192,7 @@ impl Display for Discriminant { impl ToTokens for Discriminant { fn to_tokens(&self, tokens: &mut TokenStream) { - if self.negative { + if self.sign == Sign::Negative { Token![-](Span::call_site()).to_tokens(tokens); } Literal::u64_unsuffixed(self.magnitude).to_tokens(tokens); @@ -192,15 +203,14 @@ impl FromStr for Discriminant { type Err = Error; fn from_str(mut s: &str) -> Result<Self> { - let negative = s.starts_with('-'); - if negative { + let sign = if s.starts_with('-') { s = &s[1..]; - } + Sign::Negative + } else { + Sign::Positive + }; match s.parse::<u64>() { - Ok(magnitude) => Ok(Discriminant { - negative, - magnitude, - }), + Ok(magnitude) => Ok(Discriminant { sign, magnitude }), Err(_) => Err(Error::new( Span::call_site(), "discriminant value outside of supported range", @@ -211,11 +221,12 @@ impl FromStr for Discriminant { impl Ord for Discriminant { fn cmp(&self, other: &Self) -> Ordering { - match (self.negative, other.negative) { - (true, true) => self.magnitude.cmp(&other.magnitude).reverse(), - (true, false) => Ordering::Less, // negative < positive - (false, true) => Ordering::Greater, // positive > negative - (false, false) => self.magnitude.cmp(&other.magnitude), + use self::Sign::{Negative, Positive}; + match (self.sign, other.sign) { + (Negative, Negative) => self.magnitude.cmp(&other.magnitude).reverse(), + (Negative, Positive) => Ordering::Less, // negative < positive + (Positive, Negative) => Ordering::Greater, // positive > negative + (Positive, Positive) => self.magnitude.cmp(&other.magnitude), } } } diff --git a/syntax/error.rs b/syntax/error.rs index d0ae0210..a6723293 100644 --- a/syntax/error.rs +++ b/syntax/error.rs @@ -29,7 +29,7 @@ pub static ERRORS: &[Error] = &[ pub static BOX_CXX_TYPE: Error = Error { msg: "Box of a C++ type is not supported yet", label: None, - note: Some("hint: use UniquePtr<>"), + note: Some("hint: use UniquePtr<> or SharedPtr<>"), }; pub static CXXBRIDGE_RESERVED: Error = Error { @@ -47,7 +47,7 @@ pub static CXX_STRING_BY_VALUE: Error = Error { pub static CXX_TYPE_BY_VALUE: Error = Error { msg: "C++ type by value is not supported", label: None, - note: Some("hint: wrap it in a UniquePtr<>"), + note: Some("hint: wrap it in a UniquePtr<> or SharedPtr<>"), }; pub static DISCRIMINANT_OVERFLOW: Error = Error { diff --git a/syntax/ident.rs b/syntax/ident.rs index aaf78326..bb2281e7 100644 --- a/syntax/ident.rs +++ b/syntax/ident.rs @@ -1,22 +1,27 @@ use crate::syntax::check::Check; use crate::syntax::{error, Api, Pair}; -use proc_macro2::Ident; -fn check(cx: &mut Check, ident: &Ident) { - let s = ident.to_string(); - if s.starts_with("cxxbridge") { - cx.error(ident, error::CXXBRIDGE_RESERVED.msg); +fn check(cx: &mut Check, name: &Pair) { + for segment in &name.namespace { + check_cxx_ident(cx, &segment.to_string()); } - if s.contains("__") { - cx.error(ident, error::DOUBLE_UNDERSCORE.msg); + check_cxx_ident(cx, &name.cxx.to_string()); + check_rust_ident(cx, &name.rust.to_string()); + + fn check_cxx_ident(cx: &mut Check, ident: &str) { + if ident.starts_with("cxxbridge") { + cx.error(ident, error::CXXBRIDGE_RESERVED.msg); + } + if ident.contains("__") { + cx.error(ident, error::DOUBLE_UNDERSCORE.msg); + } } -} -fn check_ident(cx: &mut Check, name: &Pair) { - for segment in &name.namespace { - check(cx, segment); + fn check_rust_ident(cx: &mut Check, ident: &str) { + if ident.starts_with("cxxbridge") { + cx.error(ident, error::CXXBRIDGE_RESERVED.msg); + } } - check(cx, &name.cxx); } pub(crate) fn check_all(cx: &mut Check, apis: &[Api]) { @@ -24,28 +29,28 @@ pub(crate) fn check_all(cx: &mut Check, apis: &[Api]) { match api { Api::Include(_) | Api::Impl(_) => {} Api::Struct(strct) => { - check_ident(cx, &strct.name); + check(cx, &strct.name); for field in &strct.fields { - check(cx, &field.ident); + check(cx, &field.name); } } Api::Enum(enm) => { - check_ident(cx, &enm.name); + check(cx, &enm.name); for variant in &enm.variants { - check(cx, &variant.ident); + check(cx, &variant.name); } } Api::CxxType(ety) | Api::RustType(ety) => { - check_ident(cx, &ety.name); + check(cx, &ety.name); } Api::CxxFunction(efn) | Api::RustFunction(efn) => { - check(cx, &efn.name.rust); + check(cx, &efn.name); for arg in &efn.args { - check(cx, &arg.ident); + check(cx, &arg.name); } } Api::TypeAlias(alias) => { - check_ident(cx, &alias.name); + check(cx, &alias.name); } } } diff --git a/syntax/impls.rs b/syntax/impls.rs index a4b393ae..8b0743b6 100644 --- a/syntax/impls.rs +++ b/syntax/impls.rs @@ -1,11 +1,12 @@ -use crate::syntax::{ExternFn, Impl, Include, Receiver, Ref, Signature, Slice, Ty1, Type}; -use std::borrow::Borrow; +use crate::syntax::{ + Array, ExternFn, Include, Lifetimes, Ptr, Receiver, Ref, Signature, SliceRef, Ty1, Type, Var, +}; use std::hash::{Hash, Hasher}; use std::mem; use std::ops::{Deref, DerefMut}; impl PartialEq for Include { - fn eq(&self, other: &Include) -> bool { + fn eq(&self, other: &Self) -> bool { let Include { path, kind, @@ -43,13 +44,16 @@ impl Hash for Type { Type::Ident(t) => t.hash(state), Type::RustBox(t) => t.hash(state), Type::UniquePtr(t) => t.hash(state), + Type::SharedPtr(t) => t.hash(state), + Type::WeakPtr(t) => t.hash(state), Type::Ref(t) => t.hash(state), + Type::Ptr(t) => t.hash(state), Type::Str(t) => t.hash(state), Type::RustVec(t) => t.hash(state), Type::CxxVector(t) => t.hash(state), Type::Fn(t) => t.hash(state), - Type::Slice(t) => t.hash(state), - Type::SliceRefU8(t) => t.hash(state), + Type::SliceRef(t) => t.hash(state), + Type::Array(t) => t.hash(state), Type::Void(_) => {} } } @@ -58,28 +62,61 @@ impl Hash for Type { impl Eq for Type {} impl PartialEq for Type { - fn eq(&self, other: &Type) -> bool { + fn eq(&self, other: &Self) -> bool { match (self, other) { (Type::Ident(lhs), Type::Ident(rhs)) => lhs == rhs, (Type::RustBox(lhs), Type::RustBox(rhs)) => lhs == rhs, (Type::UniquePtr(lhs), Type::UniquePtr(rhs)) => lhs == rhs, + (Type::SharedPtr(lhs), Type::SharedPtr(rhs)) => lhs == rhs, + (Type::WeakPtr(lhs), Type::WeakPtr(rhs)) => lhs == rhs, (Type::Ref(lhs), Type::Ref(rhs)) => lhs == rhs, (Type::Str(lhs), Type::Str(rhs)) => lhs == rhs, (Type::RustVec(lhs), Type::RustVec(rhs)) => lhs == rhs, (Type::CxxVector(lhs), Type::CxxVector(rhs)) => lhs == rhs, (Type::Fn(lhs), Type::Fn(rhs)) => lhs == rhs, - (Type::Slice(lhs), Type::Slice(rhs)) => lhs == rhs, - (Type::SliceRefU8(lhs), Type::SliceRefU8(rhs)) => lhs == rhs, + (Type::SliceRef(lhs), Type::SliceRef(rhs)) => lhs == rhs, (Type::Void(_), Type::Void(_)) => true, (_, _) => false, } } } +impl Eq for Lifetimes {} + +impl PartialEq for Lifetimes { + fn eq(&self, other: &Self) -> bool { + let Lifetimes { + lt_token: _, + lifetimes, + gt_token: _, + } = self; + let Lifetimes { + lt_token: _, + lifetimes: lifetimes2, + gt_token: _, + } = other; + lifetimes.iter().eq(lifetimes2) + } +} + +impl Hash for Lifetimes { + fn hash<H: Hasher>(&self, state: &mut H) { + let Lifetimes { + lt_token: _, + lifetimes, + gt_token: _, + } = self; + lifetimes.len().hash(state); + for lifetime in lifetimes { + lifetime.hash(state); + } + } +} + impl Eq for Ty1 {} impl PartialEq for Ty1 { - fn eq(&self, other: &Ty1) -> bool { + fn eq(&self, other: &Self) -> bool { let Ty1 { name, langle: _, @@ -112,64 +149,167 @@ impl Hash for Ty1 { impl Eq for Ref {} impl PartialEq for Ref { - fn eq(&self, other: &Ref) -> bool { + fn eq(&self, other: &Self) -> bool { let Ref { + pinned, ampersand: _, lifetime, - mutability, + mutable, inner, + pin_tokens: _, + mutability: _, } = self; let Ref { + pinned: pinned2, ampersand: _, lifetime: lifetime2, - mutability: mutability2, + mutable: mutable2, inner: inner2, + pin_tokens: _, + mutability: _, } = other; - lifetime == lifetime2 && mutability.is_some() == mutability2.is_some() && inner == inner2 + pinned == pinned2 && lifetime == lifetime2 && mutable == mutable2 && inner == inner2 } } impl Hash for Ref { fn hash<H: Hasher>(&self, state: &mut H) { let Ref { + pinned, + ampersand: _, + lifetime, + mutable, + inner, + pin_tokens: _, + mutability: _, + } = self; + pinned.hash(state); + lifetime.hash(state); + mutable.hash(state); + inner.hash(state); + } +} + +impl Eq for Ptr {} + +impl PartialEq for Ptr { + fn eq(&self, other: &Ptr) -> bool { + let Ptr { + star: _, + mutable, + inner, + mutability: _, + constness: _, + } = self; + let Ptr { + star: _, + mutable: mutable2, + inner: inner2, + mutability: _, + constness: _, + } = other; + mutable == mutable2 && inner == inner2 + } +} + +impl Hash for Ptr { + fn hash<H: Hasher>(&self, state: &mut H) { + let Ptr { + star: _, + mutable, + inner, + mutability: _, + constness: _, + } = self; + mutable.hash(state); + inner.hash(state); + } +} + +impl Eq for SliceRef {} + +impl PartialEq for SliceRef { + fn eq(&self, other: &Self) -> bool { + let SliceRef { + ampersand: _, + lifetime, + mutable, + bracket: _, + inner, + mutability: _, + } = self; + let SliceRef { + ampersand: _, + lifetime: lifetime2, + mutable: mutable2, + bracket: _, + inner: inner2, + mutability: _, + } = other; + lifetime == lifetime2 && mutable == mutable2 && inner == inner2 + } +} + +impl Hash for SliceRef { + fn hash<H: Hasher>(&self, state: &mut H) { + let SliceRef { ampersand: _, lifetime, - mutability, + mutable, + bracket: _, inner, + mutability: _, } = self; lifetime.hash(state); - mutability.is_some().hash(state); + mutable.hash(state); inner.hash(state); } } -impl Eq for Slice {} +impl Eq for Array {} -impl PartialEq for Slice { - fn eq(&self, other: &Slice) -> bool { - let Slice { bracket: _, inner } = self; - let Slice { +impl PartialEq for Array { + fn eq(&self, other: &Self) -> bool { + let Array { + bracket: _, + inner, + semi_token: _, + len, + len_token: _, + } = self; + let Array { bracket: _, inner: inner2, + semi_token: _, + len: len2, + len_token: _, } = other; - inner == inner2 + inner == inner2 && len == len2 } } -impl Hash for Slice { +impl Hash for Array { fn hash<H: Hasher>(&self, state: &mut H) { - let Slice { bracket: _, inner } = self; + let Array { + bracket: _, + inner, + semi_token: _, + len, + len_token: _, + } = self; inner.hash(state); + len.hash(state); } } impl Eq for Signature {} impl PartialEq for Signature { - fn eq(&self, other: &Signature) -> bool { + fn eq(&self, other: &Self) -> bool { let Signature { unsafety, fn_token: _, + generics: _, receiver, args, ret, @@ -180,6 +320,7 @@ impl PartialEq for Signature { let Signature { unsafety: unsafety2, fn_token: _, + generics: _, receiver: receiver2, args: args2, ret: ret2, @@ -192,7 +333,23 @@ impl PartialEq for Signature { && ret == ret2 && throws == throws2 && args.len() == args2.len() - && args.iter().zip(args2).all(|(arg, arg2)| arg == arg2) + && args.iter().zip(args2).all(|(arg, arg2)| { + let Var { + doc: _, + attrs: _, + visibility: _, + name: _, + ty, + } = arg; + let Var { + doc: _, + attrs: _, + visibility: _, + name: _, + ty: ty2, + } = arg2; + ty == ty2 + }) } } @@ -201,6 +358,7 @@ impl Hash for Signature { let Signature { unsafety, fn_token: _, + generics: _, receiver, args, ret, @@ -211,7 +369,14 @@ impl Hash for Signature { unsafety.is_some().hash(state); receiver.hash(state); for arg in args { - arg.hash(state); + let Var { + doc: _, + attrs: _, + visibility: _, + name: _, + ty, + } = arg; + ty.hash(state); } ret.hash(state); throws.hash(state); @@ -221,74 +386,49 @@ impl Hash for Signature { impl Eq for Receiver {} impl PartialEq for Receiver { - fn eq(&self, other: &Receiver) -> bool { + fn eq(&self, other: &Self) -> bool { let Receiver { + pinned, ampersand: _, lifetime, - mutability, + mutable, var: _, ty, shorthand: _, + pin_tokens: _, + mutability: _, } = self; let Receiver { + pinned: pinned2, ampersand: _, lifetime: lifetime2, - mutability: mutability2, + mutable: mutable2, var: _, ty: ty2, shorthand: _, + pin_tokens: _, + mutability: _, } = other; - lifetime == lifetime2 && mutability.is_some() == mutability2.is_some() && ty == ty2 + pinned == pinned2 && lifetime == lifetime2 && mutable == mutable2 && ty == ty2 } } impl Hash for Receiver { fn hash<H: Hasher>(&self, state: &mut H) { let Receiver { + pinned, ampersand: _, lifetime, - mutability, + mutable, var: _, ty, shorthand: _, + pin_tokens: _, + mutability: _, } = self; + pinned.hash(state); lifetime.hash(state); - mutability.is_some().hash(state); - ty.hash(state); - } -} - -impl Hash for Impl { - fn hash<H: Hasher>(&self, state: &mut H) { - let Impl { - impl_token: _, - ty, - brace_token: _, - } = self; + mutable.hash(state); 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 index 6fd31629..f19eb86a 100644 --- a/syntax/improper.rs +++ b/syntax/improper.rs @@ -19,7 +19,7 @@ impl<'a> Types<'a> { } else if let Some(strct) = self.structs.get(ident) { Depends(&strct.name.rust) // iterate to fixed-point } else { - Definite(self.rust.contains(ident)) + Definite(self.rust.contains(ident) || self.aliases.contains_key(ident)) } } Type::RustBox(_) @@ -27,10 +27,13 @@ impl<'a> Types<'a> { | Type::Str(_) | Type::Fn(_) | Type::Void(_) - | Type::Slice(_) - | Type::SliceRefU8(_) => Definite(true), - Type::UniquePtr(_) | Type::CxxVector(_) => Definite(false), + | Type::SliceRef(_) => Definite(true), + Type::UniquePtr(_) | Type::SharedPtr(_) | Type::WeakPtr(_) | Type::CxxVector(_) => { + Definite(false) + } Type::Ref(ty) => self.determine_improper_ctype(&ty.inner), + Type::Ptr(ty) => self.determine_improper_ctype(&ty.inner), + Type::Array(ty) => self.determine_improper_ctype(&ty.inner), } } } diff --git a/syntax/instantiate.rs b/syntax/instantiate.rs new file mode 100644 index 00000000..b6cbf24b --- /dev/null +++ b/syntax/instantiate.rs @@ -0,0 +1,80 @@ +use crate::syntax::{NamedType, Ty1, Type}; +use proc_macro2::{Ident, Span}; +use std::hash::{Hash, Hasher}; +use syn::Token; + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub enum ImplKey<'a> { + RustBox(NamedImplKey<'a>), + RustVec(NamedImplKey<'a>), + UniquePtr(NamedImplKey<'a>), + SharedPtr(NamedImplKey<'a>), + WeakPtr(NamedImplKey<'a>), + CxxVector(NamedImplKey<'a>), +} + +#[derive(Copy, Clone)] +pub struct NamedImplKey<'a> { + pub begin_span: Span, + pub rust: &'a Ident, + pub lt_token: Option<Token![<]>, + pub gt_token: Option<Token![>]>, + pub end_span: Span, +} + +impl Type { + pub(crate) fn impl_key(&self) -> Option<ImplKey> { + if let Type::RustBox(ty) = self { + if let Type::Ident(ident) = &ty.inner { + return Some(ImplKey::RustBox(NamedImplKey::new(ty, ident))); + } + } else if let Type::RustVec(ty) = self { + if let Type::Ident(ident) = &ty.inner { + return Some(ImplKey::RustVec(NamedImplKey::new(ty, ident))); + } + } else if let Type::UniquePtr(ty) = self { + if let Type::Ident(ident) = &ty.inner { + return Some(ImplKey::UniquePtr(NamedImplKey::new(ty, ident))); + } + } else if let Type::SharedPtr(ty) = self { + if let Type::Ident(ident) = &ty.inner { + return Some(ImplKey::SharedPtr(NamedImplKey::new(ty, ident))); + } + } else if let Type::WeakPtr(ty) = self { + if let Type::Ident(ident) = &ty.inner { + return Some(ImplKey::WeakPtr(NamedImplKey::new(ty, ident))); + } + } else if let Type::CxxVector(ty) = self { + if let Type::Ident(ident) = &ty.inner { + return Some(ImplKey::CxxVector(NamedImplKey::new(ty, ident))); + } + } + None + } +} + +impl<'a> PartialEq for NamedImplKey<'a> { + fn eq(&self, other: &Self) -> bool { + PartialEq::eq(self.rust, other.rust) + } +} + +impl<'a> Eq for NamedImplKey<'a> {} + +impl<'a> Hash for NamedImplKey<'a> { + fn hash<H: Hasher>(&self, hasher: &mut H) { + self.rust.hash(hasher); + } +} + +impl<'a> NamedImplKey<'a> { + fn new(outer: &Ty1, inner: &'a NamedType) -> Self { + NamedImplKey { + begin_span: outer.name.span(), + rust: &inner.rust, + lt_token: inner.generics.lt_token, + gt_token: inner.generics.gt_token, + end_span: outer.rangle.span, + } + } +} diff --git a/syntax/mangle.rs b/syntax/mangle.rs index cc5115a9..287b4434 100644 --- a/syntax/mangle.rs +++ b/syntax/mangle.rs @@ -1,12 +1,86 @@ +// Mangled symbol arrangements: +// +// (a) One-off internal symbol. +// pattern: {CXXBRIDGE} $ {NAME} +// examples: +// - cxxbridge1$exception +// defining characteristics: +// - 2 segments +// - starts with cxxbridge +// +// (b) Behavior on a builtin binding without generic parameter. +// pattern: {CXXBRIDGE} $ {TYPE} $ {NAME} +// examples: +// - cxxbridge1$string$len +// defining characteristics: +// - 3 segments +// - starts with cxxbridge +// +// (c) Behavior on a builtin binding with generic parameter. +// pattern: {CXXBRIDGE} $ {TYPE} $ {PARAM...} $ {NAME} +// examples: +// - cxxbridge1$box$org$rust$Struct$alloc +// - cxxbridge1$unique_ptr$std$vector$u8$drop +// defining characteristics: +// - 4+ segments +// - starts with cxxbridge +// +// (d) User-defined extern function. +// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {NAME} +// examples: +// - cxxbridge1$new_client +// - org$rust$cxxbridge1$new_client +// defining characteristics: +// - cxxbridge is second from end +// FIXME: conflict with (a) if they collide with one of our one-off symbol names in the global namespace +// +// (e) User-defined extern member function. +// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {TYPE} $ {NAME} +// examples: +// - org$cxxbridge1$Struct$get +// defining characteristics: +// - cxxbridge is third from end +// FIXME: conflict with (b) if e.g. user binds a type in global namespace that collides with our builtin type names +// +// (f) Operator overload. +// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {TYPE} $ operator $ {NAME} +// examples: +// - org$rust$cxxbridge1$Struct$operator$eq +// defining characteristics: +// - second segment from end is `operator` (not possible in type or namespace names) +// +// (g) Closure trampoline. +// pattern: {NAMESPACE...} $ {CXXBRIDGE} $ {TYPE?} $ {NAME} $ {ARGUMENT} $ {DIRECTION} +// examples: +// - org$rust$cxxbridge1$Struct$invoke$f$0 +// defining characteristics: +// - last symbol is `0` (C half) or `1` (Rust half) which are not legal identifiers on their own +// +// +// Mangled preprocessor variable arrangements: +// +// (A) One-off internal variable. +// pattern: {CXXBRIDGE} _ {NAME} +// examples: +// - CXXBRIDGE1_PANIC +// - CXXBRIDGE1_RUST_STRING +// defining characteristics: +// - NAME does not begin with STRUCT or ENUM +// +// (B) Guard around user-defined type. +// pattern: {CXXBRIDGE} _ {STRUCT or ENUM} _ {NAMESPACE...} $ {TYPE} +// examples: +// - CXXBRIDGE1_STRUCT_org$rust$Struct +// - CXXBRIDGE1_ENUM_Enabled + use crate::syntax::symbol::{self, Symbol}; -use crate::syntax::{ExternFn, Types}; -use proc_macro2::Ident; +use crate::syntax::{ExternFn, Pair, Types}; -const CXXBRIDGE: &str = "cxxbridge05"; +const CXXBRIDGE: &str = "cxxbridge1"; macro_rules! join { - ($($segment:expr),*) => { - symbol::join(&[$(&$segment),*]) + ($($segment:expr),+ $(,)?) => { + symbol::join(&[$(&$segment),+]) }; } @@ -17,20 +91,30 @@ pub fn extern_fn(efn: &ExternFn, types: &Types) -> Symbol { join!( efn.name.namespace, CXXBRIDGE, - receiver_ident.cxx, - efn.name.rust + receiver_ident.name.cxx, + efn.name.rust, ) } None => join!(efn.name.namespace, CXXBRIDGE, efn.name.rust), } } +pub fn operator(receiver: &Pair, operator: &'static str) -> Symbol { + join!( + receiver.namespace, + CXXBRIDGE, + receiver.cxx, + "operator", + operator, + ) +} + // The C half of a function pointer trampoline. -pub fn c_trampoline(efn: &ExternFn, var: &Ident, types: &Types) -> Symbol { - join!(extern_fn(efn, types), var, 0) +pub fn c_trampoline(efn: &ExternFn, var: &Pair, types: &Types) -> Symbol { + join!(extern_fn(efn, types), var.rust, 0) } // The Rust half of a function pointer trampoline. -pub fn r_trampoline(efn: &ExternFn, var: &Ident, types: &Types) -> Symbol { - join!(extern_fn(efn, types), var, 1) +pub fn r_trampoline(efn: &ExternFn, var: &Pair, types: &Types) -> Symbol { + join!(extern_fn(efn, types), var.rust, 1) } diff --git a/syntax/map.rs b/syntax/map.rs new file mode 100644 index 00000000..4873409d --- /dev/null +++ b/syntax/map.rs @@ -0,0 +1,179 @@ +use std::borrow::Borrow; +use std::hash::Hash; +use std::ops::Index; +use std::slice; + +pub use self::ordered::OrderedMap; +pub use self::unordered::UnorderedMap; +pub use std::collections::hash_map::Entry; + +mod ordered { + use super::{Entry, Iter, UnorderedMap}; + use std::borrow::Borrow; + use std::hash::Hash; + use std::mem; + + pub struct OrderedMap<K, V> { + map: UnorderedMap<K, usize>, + vec: Vec<(K, V)>, + } + + impl<K, V> OrderedMap<K, V> { + pub fn new() -> Self { + OrderedMap { + map: UnorderedMap::new(), + vec: Vec::new(), + } + } + + pub fn iter(&self) -> Iter<K, V> { + Iter(self.vec.iter()) + } + + pub fn keys(&self) -> impl Iterator<Item = &K> { + self.vec.iter().map(|(k, _v)| k) + } + } + + impl<K, V> OrderedMap<K, V> + where + K: Copy + Hash + Eq, + { + pub fn insert(&mut self, key: K, value: V) -> Option<V> { + match self.map.entry(key) { + Entry::Occupied(entry) => { + let i = &mut self.vec[*entry.get()]; + Some(mem::replace(&mut i.1, value)) + } + Entry::Vacant(entry) => { + entry.insert(self.vec.len()); + self.vec.push((key, value)); + None + } + } + } + + pub fn contains_key<Q>(&self, key: &Q) -> bool + where + K: Borrow<Q>, + Q: ?Sized + Hash + Eq, + { + self.map.contains_key(key) + } + + pub fn get<Q>(&self, key: &Q) -> Option<&V> + where + K: Borrow<Q>, + Q: ?Sized + Hash + Eq, + { + let i = *self.map.get(key)?; + Some(&self.vec[i].1) + } + } + + impl<'a, K, V> IntoIterator for &'a OrderedMap<K, V> { + type Item = (&'a K, &'a V); + type IntoIter = Iter<'a, K, V>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } + } +} + +mod unordered { + use crate::syntax::set::UnorderedSet; + use std::borrow::Borrow; + use std::collections::hash_map::{Entry, HashMap}; + use std::hash::Hash; + + // Wrapper prohibits accidentally introducing iteration over the map, which + // could lead to nondeterministic generated code. + pub struct UnorderedMap<K, V>(HashMap<K, V>); + + impl<K, V> UnorderedMap<K, V> { + pub fn new() -> Self { + UnorderedMap(HashMap::new()) + } + } + + impl<K, V> UnorderedMap<K, V> + where + K: Hash + Eq, + { + pub fn insert(&mut self, key: K, value: V) -> Option<V> { + self.0.insert(key, value) + } + + pub fn contains_key<Q>(&self, key: &Q) -> bool + where + K: Borrow<Q>, + Q: ?Sized + Hash + Eq, + { + self.0.contains_key(key) + } + + pub fn get<Q>(&self, key: &Q) -> Option<&V> + where + K: Borrow<Q>, + Q: ?Sized + Hash + Eq, + { + self.0.get(key) + } + + pub fn entry(&mut self, key: K) -> Entry<K, V> { + self.0.entry(key) + } + + pub fn remove<Q>(&mut self, key: &Q) -> Option<V> + where + K: Borrow<Q>, + Q: ?Sized + Hash + Eq, + { + self.0.remove(key) + } + + pub fn keys(&self) -> UnorderedSet<K> + where + K: Copy, + { + let mut set = UnorderedSet::new(); + for key in self.0.keys() { + set.insert(*key); + } + set + } + } +} + +pub struct Iter<'a, K, V>(slice::Iter<'a, (K, V)>); + +impl<'a, K, V> Iterator for Iter<'a, K, V> { + type Item = (&'a K, &'a V); + + fn next(&mut self) -> Option<Self::Item> { + let (k, v) = self.0.next()?; + Some((k, v)) + } + + fn size_hint(&self) -> (usize, Option<usize>) { + self.0.size_hint() + } +} + +impl<K, V> Default for UnorderedMap<K, V> { + fn default() -> Self { + UnorderedMap::new() + } +} + +impl<Q, K, V> Index<&Q> for UnorderedMap<K, V> +where + K: Borrow<Q> + Hash + Eq, + Q: ?Sized + Hash + Eq, +{ + type Output = V; + + fn index(&self, key: &Q) -> &V { + self.get(key).unwrap() + } +} diff --git a/syntax/mod.rs b/syntax/mod.rs index 33ed31cf..3d562931 100644 --- a/syntax/mod.rs +++ b/syntax/mod.rs @@ -1,9 +1,9 @@ // Functionality that is shared between the cxxbridge macro and the cmd. pub mod atom; -mod attrs; +pub mod attrs; pub mod check; -mod derive; +pub mod derive; mod discriminant; mod doc; pub mod error; @@ -11,18 +11,25 @@ pub mod file; pub mod ident; mod impls; mod improper; +pub mod instantiate; pub mod mangle; +pub mod map; mod names; pub mod namespace; mod parse; +mod pod; pub mod qualified; pub mod report; +pub mod resolve; pub mod set; pub mod symbol; mod tokens; mod toposort; +pub mod trivial; pub mod types; +mod visit; +use self::attrs::OtherAttrs; use self::discriminant::Discriminant; use self::namespace::Namespace; use self::parse::kw; @@ -30,11 +37,12 @@ use self::symbol::Symbol; use proc_macro2::{Ident, Span}; use syn::punctuated::Punctuated; use syn::token::{Brace, Bracket, Paren}; -use syn::{Expr, Lifetime, Token, Type as RustType}; +use syn::{Expr, Generics, Lifetime, LitInt, Token, Type as RustType}; pub use self::atom::Atom; -pub use self::derive::Derive; +pub use self::derive::{Derive, Trait}; pub use self::doc::Doc; +pub use self::names::ForeignName; pub use self::parse::parse_items; pub use self::types::Types; @@ -67,9 +75,16 @@ pub enum IncludeKind { } pub struct ExternType { + pub lang: Lang, pub doc: Doc, + pub derives: Vec<Derive>, + pub attrs: OtherAttrs, + pub visibility: Token![pub], pub type_token: Token![type], pub name: Pair, + pub generics: Lifetimes, + pub colon_token: Option<Token![:]>, + pub bounds: Vec<Derive>, pub semi_token: Token![;], pub trusted: bool, } @@ -77,33 +92,49 @@ pub struct ExternType { pub struct Struct { pub doc: Doc, pub derives: Vec<Derive>, + pub attrs: OtherAttrs, + pub visibility: Token![pub], pub struct_token: Token![struct], pub name: Pair, + pub generics: Lifetimes, pub brace_token: Brace, pub fields: Vec<Var>, } pub struct Enum { pub doc: Doc, + pub derives: Vec<Derive>, + pub attrs: OtherAttrs, + pub visibility: Token![pub], pub enum_token: Token![enum], pub name: Pair, + pub generics: Lifetimes, pub brace_token: Brace, pub variants: Vec<Variant>, pub repr: Atom, + pub repr_type: Type, + pub explicit_repr: bool, } pub struct ExternFn { pub lang: Lang, pub doc: Doc, + pub attrs: OtherAttrs, + pub visibility: Token![pub], pub name: Pair, pub sig: Signature, pub semi_token: Token![;], + pub trusted: bool, } pub struct TypeAlias { pub doc: Doc, + pub derives: Vec<Derive>, + pub attrs: OtherAttrs, + pub visibility: Token![pub], pub type_token: Token![type], pub name: Pair, + pub generics: Lifetimes, pub eq_token: Token![=], pub ty: RustType, pub semi_token: Token![;], @@ -111,13 +142,25 @@ pub struct TypeAlias { pub struct Impl { pub impl_token: Token![impl], + pub impl_generics: Lifetimes, + pub negative: bool, pub ty: Type, + pub ty_generics: Lifetimes, pub brace_token: Brace, + pub negative_token: Option<Token![!]>, +} + +#[derive(Clone, Default)] +pub struct Lifetimes { + pub lt_token: Option<Token![<]>, + pub lifetimes: Punctuated<Lifetime, Token![,]>, + pub gt_token: Option<Token![>]>, } pub struct Signature { pub unsafety: Option<Token![unsafe]>, pub fn_token: Token![fn], + pub generics: Generics, pub receiver: Option<Receiver>, pub args: Punctuated<Var, Token![,]>, pub ret: Option<Type>, @@ -126,39 +169,49 @@ pub struct Signature { pub throws_tokens: Option<(kw::Result, Token![<], Token![>])>, } -#[derive(Eq, PartialEq, Hash)] pub struct Var { - pub ident: Ident, + pub doc: Doc, + pub attrs: OtherAttrs, + pub visibility: Token![pub], + pub name: Pair, pub ty: Type, } pub struct Receiver { + pub pinned: bool, pub ampersand: Token![&], pub lifetime: Option<Lifetime>, - pub mutability: Option<Token![mut]>, + pub mutable: bool, pub var: Token![self], - pub ty: ResolvableName, + pub ty: NamedType, pub shorthand: bool, + pub pin_tokens: Option<(kw::Pin, Token![<], Token![>])>, + pub mutability: Option<Token![mut]>, } pub struct Variant { - pub ident: Ident, + pub doc: Doc, + pub attrs: OtherAttrs, + pub name: Pair, pub discriminant: Discriminant, pub expr: Option<Expr>, } pub enum Type { - Ident(ResolvableName), + Ident(NamedType), RustBox(Box<Ty1>), RustVec(Box<Ty1>), UniquePtr(Box<Ty1>), + SharedPtr(Box<Ty1>), + WeakPtr(Box<Ty1>), Ref(Box<Ref>), + Ptr(Box<Ptr>), Str(Box<Ref>), CxxVector(Box<Ty1>), Fn(Box<Signature>), Void(Span), - Slice(Box<Slice>), - SliceRefU8(Box<Ref>), + SliceRef(Box<SliceRef>), + Array(Box<Array>), } pub struct Ty1 { @@ -169,15 +222,38 @@ pub struct Ty1 { } pub struct Ref { + pub pinned: bool, pub ampersand: Token![&], pub lifetime: Option<Lifetime>, + pub mutable: bool, + pub inner: Type, + pub pin_tokens: Option<(kw::Pin, Token![<], Token![>])>, + pub mutability: Option<Token![mut]>, +} + +pub struct Ptr { + pub star: Token![*], + pub mutable: bool, + pub inner: Type, pub mutability: Option<Token![mut]>, + pub constness: Option<Token![const]>, +} + +pub struct SliceRef { + pub ampersand: Token![&], + pub lifetime: Option<Lifetime>, + pub mutable: bool, + pub bracket: Bracket, pub inner: Type, + pub mutability: Option<Token![mut]>, } -pub struct Slice { +pub struct Array { pub bracket: Bracket, pub inner: Type, + pub semi_token: Token![;], + pub len: usize, + pub len_token: LitInt, } #[derive(Copy, Clone, PartialEq)] @@ -191,13 +267,14 @@ pub enum Lang { #[derive(Clone)] pub struct Pair { pub namespace: Namespace, - pub cxx: Ident, + pub cxx: ForeignName, 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 { +#[derive(PartialEq, Eq, Hash)] +pub struct NamedType { pub rust: Ident, + pub generics: Lifetimes, } diff --git a/syntax/names.rs b/syntax/names.rs index 25ae9f4a..cb6ace53 100644 --- a/syntax/names.rs +++ b/syntax/names.rs @@ -1,73 +1,70 @@ -use crate::syntax::{Namespace, Pair, ResolvableName, Symbol, Types}; +use crate::syntax::symbol::Segment; +use crate::syntax::{Lifetimes, NamedType, Pair, Symbol}; use proc_macro2::{Ident, Span}; +use std::fmt::{self, Display}; use std::iter; -use syn::Token; +use syn::parse::{Error, Result}; +use syn::punctuated::Punctuated; -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, - } - } +#[derive(Clone)] +pub struct ForeignName { + text: String, + span: Span, +} +impl Pair { pub fn to_symbol(&self) -> Symbol { - Symbol::from_idents(self.iter_all_segments()) + let segments = self + .namespace + .iter() + .map(|ident| ident as &dyn Segment) + .chain(iter::once(&self.cxx as &dyn Segment)); + Symbol::from_idents(segments) } pub fn to_fully_qualified(&self) -> String { - format!("::{}", self.join("::")) + let mut fully_qualified = String::new(); + for segment in &self.namespace { + fully_qualified += "::"; + fully_qualified += &segment.to_string(); + } + fully_qualified += "::"; + fully_qualified += &self.cxx.to_string(); + fully_qualified } +} - fn iter_all_segments(&self) -> impl Iterator<Item = &Ident> { - self.namespace.iter().chain(iter::once(&self.cxx)) +impl NamedType { + pub fn new(rust: Ident) -> Self { + let generics = Lifetimes { + lt_token: None, + lifetimes: Punctuated::new(), + gt_token: None, + }; + NamedType { rust, generics } } - fn join(&self, sep: &str) -> String { - self.iter_all_segments() - .map(|s| s.to_string()) - .collect::<Vec<_>>() - .join(sep) + pub fn span(&self) -> Span { + self.rust.span() } } -impl ResolvableName { - pub fn new(ident: Ident) -> Self { - Self { rust: ident } - } - - pub fn make_self(span: Span) -> Self { - Self { - rust: Token![Self](span).into(), +impl ForeignName { + pub fn parse(text: &str, span: Span) -> Result<Self> { + // TODO: support C++ names containing whitespace (`unsigned int`) or + // non-alphanumeric characters (`operator++`). + match syn::parse_str::<Ident>(text) { + Ok(ident) => { + let text = ident.to_string(); + Ok(ForeignName { text, span }) + } + Err(err) => Err(Error::new(span, err)), } } +} - 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() +impl Display for ForeignName { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(&self.text) } } diff --git a/syntax/parse.rs b/syntax/parse.rs index a5f8bd10..d7920761 100644 --- a/syntax/parse.rs +++ b/syntax/parse.rs @@ -1,23 +1,28 @@ +use crate::syntax::attrs::OtherAttrs; use crate::syntax::discriminant::DiscriminantSet; use crate::syntax::file::{Item, ItemForeignMod}; use crate::syntax::report::Errors; use crate::syntax::Atom::*; use crate::syntax::{ - attrs, error, Api, Doc, Enum, ExternFn, ExternType, Impl, Include, IncludeKind, Lang, - Namespace, Pair, Receiver, Ref, ResolvableName, Signature, Slice, Struct, Ty1, Type, TypeAlias, - Var, Variant, + attrs, error, Api, Array, Derive, Doc, Enum, ExternFn, ExternType, ForeignName, Impl, Include, + IncludeKind, Lang, Lifetimes, NamedType, Namespace, Pair, Ptr, Receiver, Ref, Signature, + SliceRef, Struct, Ty1, Type, TypeAlias, Var, Variant, }; -use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; +use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree}; use quote::{format_ident, quote, quote_spanned}; +use std::mem; use syn::parse::{ParseStream, Parser}; use syn::punctuated::Punctuated; use syn::{ - Abi, Attribute, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType, - GenericArgument, Ident, ItemEnum, ItemImpl, ItemStruct, LitStr, Pat, PathArguments, Result, - ReturnType, Token, Type as RustType, TypeBareFn, TypePath, TypeReference, TypeSlice, + Abi, Attribute, Error, Expr, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType, + GenericArgument, GenericParam, Generics, Ident, ItemEnum, ItemImpl, ItemStruct, Lit, LitStr, + Pat, PathArguments, Result, ReturnType, Signature as RustSignature, Token, TraitBound, + TraitBoundModifier, Type as RustType, TypeArray, TypeBareFn, TypeParamBound, TypePath, TypePtr, + TypeReference, Variant as RustVariant, Visibility, }; pub mod kw { + syn::custom_keyword!(Pin); syn::custom_keyword!(Result); } @@ -34,14 +39,11 @@ pub fn parse_items( Ok(strct) => apis.push(strct), Err(err) => cx.push(err), }, - Item::Enum(item) => match parse_enum(cx, item, namespace) { - Ok(enm) => apis.push(enm), - Err(err) => cx.push(err), - }, + Item::Enum(item) => apis.push(parse_enum(cx, item, namespace)), Item::ForeignMod(foreign_mod) => { parse_foreign_mod(cx, foreign_mod, &mut apis, trusted, namespace) } - Item::Impl(item) => match parse_impl(item, namespace) { + Item::Impl(item) => match parse_impl(item) { Ok(imp) => apis.push(imp), Err(err) => cx.push(err), }, @@ -52,29 +54,21 @@ pub fn parse_items( apis } -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; - let ident = &item.ident; - let where_clause = &generics.where_clause; - let span = quote!(#struct_token #ident #generics #where_clause); - return Err(Error::new_spanned( - span, - "struct with generic parameters is not supported yet", - )); - } - +fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) -> Result<Api> { let mut doc = Doc::new(); let mut derives = Vec::new(); let mut namespace = namespace.clone(); - attrs::parse( + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( cx, - &item.attrs, + mem::take(&mut item.attrs), attrs::Parser { doc: Some(&mut doc), derives: Some(&mut derives), namespace: Some(&mut namespace), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), ..Default::default() }, ); @@ -87,87 +81,149 @@ fn parse_struct(cx: &mut Errors, item: ItemStruct, namespace: &Namespace) -> Res } }; - let fields = named_fields - .named - .into_iter() - .map(|field| { - Ok(Var { - ident: field.ident.unwrap(), - ty: parse_type(&field.ty, &namespace)?, - }) - }) - .collect::<Result<_>>()?; + let mut lifetimes = Punctuated::new(); + let mut has_unsupported_generic_param = false; + for pair in item.generics.params.into_pairs() { + let (param, punct) = pair.into_tuple(); + match param { + GenericParam::Lifetime(param) => { + if !param.bounds.is_empty() && !has_unsupported_generic_param { + let msg = "lifetime parameter with bounds is not supported yet"; + cx.error(¶m, msg); + has_unsupported_generic_param = true; + } + lifetimes.push_value(param.lifetime); + if let Some(punct) = punct { + lifetimes.push_punct(punct); + } + } + GenericParam::Type(param) => { + if !has_unsupported_generic_param { + let msg = "struct with generic type parameter is not supported yet"; + cx.error(¶m, msg); + has_unsupported_generic_param = true; + } + } + GenericParam::Const(param) => { + if !has_unsupported_generic_param { + let msg = "struct with const generic parameter is not supported yet"; + cx.error(¶m, msg); + has_unsupported_generic_param = true; + } + } + } + } + + if let Some(where_clause) = &item.generics.where_clause { + cx.error( + where_clause, + "struct with where-clause is not supported yet", + ); + } + + let mut fields = Vec::new(); + for field in named_fields.named { + let ident = field.ident.unwrap(); + let mut doc = Doc::new(); + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( + cx, + field.attrs, + attrs::Parser { + doc: Some(&mut doc), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), + ..Default::default() + }, + ); + let ty = match parse_type(&field.ty) { + Ok(ty) => ty, + Err(err) => { + cx.push(err); + continue; + } + }; + let visibility = visibility_pub(&field.vis, ident.span()); + let name = pair(Namespace::default(), &ident, cxx_name, rust_name); + fields.push(Var { + doc, + attrs, + visibility, + name, + ty, + }); + } + + let struct_token = item.struct_token; + let visibility = visibility_pub(&item.vis, struct_token.span); + let name = pair(namespace, &item.ident, cxx_name, rust_name); + let generics = Lifetimes { + lt_token: item.generics.lt_token, + lifetimes, + gt_token: item.generics.gt_token, + }; + let brace_token = named_fields.brace_token; Ok(Api::Struct(Struct { doc, derives, - struct_token: item.struct_token, - name: Pair::new(namespace, item.ident), - brace_token: named_fields.brace_token, + attrs, + visibility, + struct_token, + name, + generics, + brace_token, fields, })) } -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; - let ident = &item.ident; - let where_clause = &generics.where_clause; - let span = quote!(#enum_token #ident #generics #where_clause); - return Err(Error::new_spanned( - span, - "enums with generic parameters are not allowed", - )); - } - +fn parse_enum(cx: &mut Errors, item: ItemEnum, namespace: &Namespace) -> Api { let mut doc = Doc::new(); + let mut derives = Vec::new(); let mut repr = None; let mut namespace = namespace.clone(); - attrs::parse( + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( cx, - &item.attrs, + item.attrs, attrs::Parser { doc: Some(&mut doc), + derives: Some(&mut derives), repr: Some(&mut repr), namespace: Some(&mut namespace), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), ..Default::default() }, ); + if !item.generics.params.is_empty() { + let vis = &item.vis; + let enum_token = item.enum_token; + let ident = &item.ident; + let generics = &item.generics; + let span = quote!(#vis #enum_token #ident #generics); + cx.error(span, "enum with generic parameters is not supported"); + } else if let Some(where_clause) = &item.generics.where_clause { + cx.error(where_clause, "enum with where-clause is not supported"); + } + let mut variants = Vec::new(); let mut discriminants = DiscriminantSet::new(repr); for variant in item.variants { - match variant.fields { - Fields::Unit => {} - _ => { - cx.error(variant, "enums with data are not supported yet"); - break; - } + match parse_variant(cx, variant, &mut discriminants) { + Ok(variant) => variants.push(variant), + Err(err) => cx.push(err), } - let expr = variant.discriminant.as_ref().map(|(_, expr)| expr); - let try_discriminant = match &expr { - Some(lit) => discriminants.insert(lit), - None => discriminants.insert_next(), - }; - let discriminant = match try_discriminant { - Ok(discriminant) => discriminant, - Err(err) => { - cx.error(variant, err); - break; - } - }; - let expr = variant.discriminant.map(|(_, expr)| expr); - variants.push(Variant { - ident: variant.ident, - discriminant, - expr, - }); } let enum_token = item.enum_token; + let visibility = visibility_pub(&item.vis, enum_token.span); let brace_token = item.brace_token; + let explicit_repr = repr.is_some(); let mut repr = U8; match discriminants.inferred_repr() { Ok(inferred) => repr = inferred, @@ -178,14 +234,78 @@ fn parse_enum(cx: &mut Errors, item: ItemEnum, namespace: &Namespace) -> Result< } } - Ok(Api::Enum(Enum { + let name = pair(namespace, &item.ident, cxx_name, rust_name); + let repr_ident = Ident::new(repr.as_ref(), Span::call_site()); + let repr_type = Type::Ident(NamedType::new(repr_ident)); + let generics = Lifetimes { + lt_token: None, + lifetimes: Punctuated::new(), + gt_token: None, + }; + + Api::Enum(Enum { doc, + derives, + attrs, + visibility, enum_token, - name: Pair::new(namespace, item.ident), + name, + generics, brace_token, variants, repr, - })) + repr_type, + explicit_repr, + }) +} + +fn parse_variant( + cx: &mut Errors, + mut variant: RustVariant, + discriminants: &mut DiscriminantSet, +) -> Result<Variant> { + let mut doc = Doc::new(); + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( + cx, + mem::take(&mut variant.attrs), + attrs::Parser { + doc: Some(&mut doc), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), + ..Default::default() + }, + ); + + match variant.fields { + Fields::Unit => {} + _ => { + let msg = "enums with data are not supported yet"; + return Err(Error::new_spanned(variant, msg)); + } + } + + let expr = variant.discriminant.as_ref().map(|(_, expr)| expr); + let try_discriminant = match &expr { + Some(lit) => discriminants.insert(lit), + None => discriminants.insert_next(), + }; + let discriminant = match try_discriminant { + Ok(discriminant) => discriminant, + Err(err) => return Err(Error::new_spanned(variant, err)), + }; + + let name = pair(Namespace::ROOT, &variant.ident, cxx_name, rust_name); + let expr = variant.discriminant.map(|(_, expr)| expr); + + Ok(Variant { + doc, + attrs, + name, + discriminant, + expr, + }) } fn parse_foreign_mod( @@ -204,7 +324,7 @@ fn parse_foreign_mod( Lang::Rust => { if foreign_mod.unsafety.is_some() { let unsafety = foreign_mod.unsafety; - let abi = foreign_mod.abi; + let abi = &foreign_mod.abi; let span = quote!(#unsafety #abi); cx.error(span, "extern \"Rust\" block does not need to be unsafe"); } @@ -217,7 +337,7 @@ fn parse_foreign_mod( let mut namespace = namespace.clone(); attrs::parse( cx, - &foreign_mod.attrs, + foreign_mod.attrs, attrs::Parser { namespace: Some(&mut namespace), ..Default::default() @@ -225,18 +345,18 @@ fn parse_foreign_mod( ); let mut items = Vec::new(); - for foreign in &foreign_mod.items { + for foreign in foreign_mod.items { match foreign { ForeignItem::Type(foreign) => { - match parse_extern_type(cx, foreign, lang, trusted, &namespace) { - Ok(ety) => items.push(ety), + let ety = parse_extern_type(cx, foreign, lang, trusted, &namespace); + items.push(ety); + } + ForeignItem::Fn(foreign) => { + match parse_extern_fn(cx, foreign, lang, trusted, &namespace) { + Ok(efn) => items.push(efn), Err(err) => cx.push(err), } } - ForeignItem::Fn(foreign) => match parse_extern_fn(cx, foreign, lang, &namespace) { - Ok(efn) => items.push(efn), - Err(err) => cx.push(err), - }, ForeignItem::Macro(foreign) if foreign.mac.path.is_ident("include") => { match foreign.mac.parse_body_with(parse_include) { Ok(include) => items.push(Api::Include(include)), @@ -244,7 +364,7 @@ fn parse_foreign_mod( } } ForeignItem::Verbatim(tokens) => { - match parse_extern_verbatim(cx, tokens, lang, &namespace) { + match parse_extern_verbatim(cx, tokens, lang, trusted, &namespace) { Ok(api) => items.push(api), Err(err) => cx.push(err), } @@ -253,6 +373,18 @@ fn parse_foreign_mod( } } + if !trusted + && items.iter().any(|api| match api { + Api::CxxFunction(efn) => efn.unsafety.is_none(), + _ => false, + }) + { + cx.error( + foreign_mod.abi, + "block must be declared `unsafe extern \"C++\"` if it contains any safe-to-call C++ functions", + ); + } + let mut types = items.iter().filter_map(|item| match item { Api::CxxType(ety) | Api::RustType(ety) => Some(&ety.name), Api::TypeAlias(alias) => Some(&alias.name), @@ -263,8 +395,8 @@ fn parse_foreign_mod( for item in &mut items { if let Api::CxxFunction(efn) | Api::RustFunction(efn) = item { if let Some(receiver) = &mut efn.receiver { - if receiver.ty.is_self() { - receiver.ty = ResolvableName::new(single_type.rust.clone()); + if receiver.ty.rust == "Self" { + receiver.ty.rust = single_type.rust.clone(); } } } @@ -280,64 +412,113 @@ fn parse_lang(abi: &Abi) -> Result<Lang> { None => { return Err(Error::new_spanned( abi, - "ABI name is required, extern \"C\" or extern \"Rust\"", + "ABI name is required, extern \"C++\" or extern \"Rust\"", )); } }; + match name.value().as_str() { - "C" | "C++" => Ok(Lang::Cxx), + "C++" => Ok(Lang::Cxx), "Rust" => Ok(Lang::Rust), - _ => Err(Error::new_spanned(abi, "unrecognized ABI")), + _ => Err(Error::new_spanned( + abi, + "unrecognized ABI, requires either \"C++\" or \"Rust\"", + )), } } fn parse_extern_type( cx: &mut Errors, - foreign_type: &ForeignItemType, + foreign_type: ForeignItemType, lang: Lang, trusted: bool, namespace: &Namespace, -) -> Result<Api> { +) -> Api { let mut doc = Doc::new(); + let mut derives = Vec::new(); let mut namespace = namespace.clone(); - attrs::parse( + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( cx, - &foreign_type.attrs, + foreign_type.attrs, attrs::Parser { doc: Some(&mut doc), + derives: Some(&mut derives), namespace: Some(&mut namespace), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), ..Default::default() }, ); + let type_token = foreign_type.type_token; - let ident = foreign_type.ident.clone(); + let visibility = visibility_pub(&foreign_type.vis, type_token.span); + let name = pair(namespace, &foreign_type.ident, cxx_name, rust_name); + let generics = Lifetimes { + lt_token: None, + lifetimes: Punctuated::new(), + gt_token: None, + }; + let colon_token = None; + let bounds = Vec::new(); let semi_token = foreign_type.semi_token; - let api_type = match lang { + + (match lang { Lang::Cxx => Api::CxxType, Lang::Rust => Api::RustType, - }; - Ok(api_type(ExternType { + })(ExternType { + lang, doc, + derives, + attrs, + visibility, type_token, - name: Pair::new(namespace, ident), + name, + generics, + colon_token, + bounds, semi_token, trusted, - })) + }) } fn parse_extern_fn( cx: &mut Errors, - foreign_fn: &ForeignItemFn, + mut foreign_fn: ForeignItemFn, lang: Lang, + trusted: bool, namespace: &Namespace, ) -> Result<Api> { + let mut doc = Doc::new(); + let mut namespace = namespace.clone(); + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( + cx, + mem::take(&mut foreign_fn.attrs), + attrs::Parser { + doc: Some(&mut doc), + namespace: Some(&mut namespace), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), + ..Default::default() + }, + ); + let generics = &foreign_fn.sig.generics; - if !generics.params.is_empty() || generics.where_clause.is_some() { + if generics.where_clause.is_some() + || generics.params.iter().any(|param| match param { + GenericParam::Lifetime(lifetime) => !lifetime.bounds.is_empty(), + GenericParam::Type(_) | GenericParam::Const(_) => true, + }) + { return Err(Error::new_spanned( foreign_fn, "extern function with generic parameters is not supported yet", )); } + if let Some(variadic) = &foreign_fn.sig.variadic { return Err(Error::new_spanned( variadic, @@ -345,21 +526,26 @@ fn parse_extern_fn( )); } - 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() - }, - ); + if foreign_fn.sig.asyncness.is_some() { + return Err(Error::new_spanned( + foreign_fn, + "async function is not directly supported yet, but see https://cxx.rs/async.html for a working approach", + )); + } + + if foreign_fn.sig.constness.is_some() { + return Err(Error::new_spanned( + foreign_fn, + "const extern function is not supported", + )); + } + + if let Some(abi) = &foreign_fn.sig.abi { + return Err(Error::new_spanned( + abi, + "explicit ABI on extern function is not supported", + )); + } let mut receiver = None; let mut args = Punctuated::new(); @@ -369,12 +555,15 @@ fn parse_extern_fn( FnArg::Receiver(arg) => { if let Some((ampersand, lifetime)) = &arg.reference { receiver = Some(Receiver { + pinned: false, ampersand: *ampersand, lifetime: lifetime.clone(), - mutability: arg.mutability, + mutable: arg.mutability.is_some(), var: arg.self_token, - ty: ResolvableName::make_self(arg.self_token.span), + ty: NamedType::new(Ident::new("Self", arg.self_token.span)), shorthand: true, + pin_tokens: None, + mutability: arg.mutability, }); continue; } @@ -384,13 +573,23 @@ fn parse_extern_fn( let ident = match arg.pat.as_ref() { Pat::Ident(pat) => pat.ident.clone(), Pat::Wild(pat) => { - Ident::new(&format!("_{}", args.len()), pat.underscore_token.span) + Ident::new(&format!("arg{}", args.len()), pat.underscore_token.span) } _ => return Err(Error::new_spanned(arg, "unsupported signature")), }; - let ty = parse_type(&arg.ty, &namespace)?; + let ty = parse_type(&arg.ty)?; if ident != "self" { - args.push_value(Var { ident, ty }); + let doc = Doc::new(); + let attrs = OtherAttrs::none(); + let visibility = Token![pub](ident.span()); + let name = pair(Namespace::default(), &ident, None, None); + args.push_value(Var { + doc, + attrs, + visibility, + name, + ty, + }); if let Some(comma) = comma { args.push_punct(*comma); } @@ -399,12 +598,15 @@ fn parse_extern_fn( if let Type::Ref(reference) = ty { if let Type::Ident(ident) = reference.inner { receiver = Some(Receiver { + pinned: reference.pinned, ampersand: reference.ampersand, lifetime: reference.lifetime, - mutability: reference.mutability, + mutable: reference.mutable, var: Token![self](ident.rust.span()), ty: ident, shorthand: false, + pin_tokens: reference.pin_tokens, + mutability: reference.mutability, }); continue; } @@ -415,29 +617,30 @@ fn parse_extern_fn( } let mut throws_tokens = None; - let ret = parse_return_type(&foreign_fn.sig.output, &mut throws_tokens, &namespace)?; + let ret = parse_return_type(&foreign_fn.sig.output, &mut throws_tokens)?; let throws = throws_tokens.is_some(); let unsafety = foreign_fn.sig.unsafety; let fn_token = foreign_fn.sig.fn_token; - let 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 inherited_span = unsafety.map_or(fn_token.span, |unsafety| unsafety.span); + let visibility = visibility_pub(&foreign_fn.vis, inherited_span); + let name = pair(namespace, &foreign_fn.sig.ident, cxx_name, rust_name); + let generics = generics.clone(); let paren_token = foreign_fn.sig.paren_token; let semi_token = foreign_fn.semi_token; - let api_function = match lang { + + Ok(match lang { Lang::Cxx => Api::CxxFunction, Lang::Rust => Api::RustFunction, - }; - - Ok(api_function(ExternFn { + }(ExternFn { lang, doc, + attrs, + visibility, name, sig: Signature { unsafety, fn_token, + generics, receiver, args, ret, @@ -446,72 +649,258 @@ fn parse_extern_fn( throws_tokens, }, semi_token, + trusted, })) } fn parse_extern_verbatim( cx: &mut Errors, - tokens: &TokenStream, + tokens: TokenStream, lang: Lang, + trusted: bool, namespace: &Namespace, ) -> Result<Api> { - // type Alias = crate::path::to::Type; - let parse = |input: ParseStream| -> Result<TypeAlias> { + |input: ParseStream| -> Result<Api> { let attrs = input.call(Attribute::parse_outer)?; - let type_token: Token![type] = match input.parse()? { - Some(type_token) => type_token, - None => { - let span = input.cursor().token_stream(); - return Err(Error::new_spanned(span, "unsupported foreign item")); - } - }; - let ident: Ident = input.parse()?; - let eq_token: Token![=] = input.parse()?; - let ty: RustType = input.parse()?; - let semi_token: Token![;] = input.parse()?; - 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() - }, - ); + let visibility: Visibility = input.parse()?; + if input.peek(Token![type]) { + parse_extern_verbatim_type(cx, attrs, visibility, input, lang, trusted, namespace) + } else if input.peek(Token![fn]) { + parse_extern_verbatim_fn(input) + } else { + let span = input.cursor().token_stream(); + Err(Error::new_spanned( + span, + "unsupported foreign item, expected `type` or `fn`", + )) + } + } + .parse2(tokens) +} - Ok(TypeAlias { - doc, - type_token, - name: Pair::new(namespace, ident), - eq_token, - ty, - semi_token, - }) +fn parse_extern_verbatim_type( + cx: &mut Errors, + attrs: Vec<Attribute>, + visibility: Visibility, + input: ParseStream, + lang: Lang, + trusted: bool, + namespace: &Namespace, +) -> Result<Api> { + let type_token: Token![type] = input.parse()?; + let ident: Ident = input.parse()?; + let generics: Generics = input.parse()?; + let mut lifetimes = Punctuated::new(); + let mut has_unsupported_generic_param = false; + for pair in generics.params.into_pairs() { + let (param, punct) = pair.into_tuple(); + match param { + GenericParam::Lifetime(param) => { + if !param.bounds.is_empty() && !has_unsupported_generic_param { + let msg = "lifetime parameter with bounds is not supported yet"; + cx.error(¶m, msg); + has_unsupported_generic_param = true; + } + lifetimes.push_value(param.lifetime); + if let Some(punct) = punct { + lifetimes.push_punct(punct); + } + } + GenericParam::Type(param) => { + if !has_unsupported_generic_param { + let msg = "extern type with generic type parameter is not supported yet"; + cx.error(¶m, msg); + has_unsupported_generic_param = true; + } + } + GenericParam::Const(param) => { + if !has_unsupported_generic_param { + let msg = "extern type with const generic parameter is not supported yet"; + cx.error(¶m, msg); + has_unsupported_generic_param = true; + } + } + } + } + let lifetimes = Lifetimes { + lt_token: generics.lt_token, + lifetimes, + gt_token: generics.gt_token, }; + let lookahead = input.lookahead1(); + if lookahead.peek(Token![=]) { + // type Alias = crate::path::to::Type; + parse_type_alias( + cx, attrs, visibility, type_token, ident, lifetimes, input, lang, namespace, + ) + } else if lookahead.peek(Token![:]) || lookahead.peek(Token![;]) { + // type Opaque: Bound2 + Bound2; + parse_extern_type_bounded( + cx, attrs, visibility, type_token, ident, lifetimes, input, lang, trusted, namespace, + ) + } else { + Err(lookahead.error()) + } +} - let type_alias = parse.parse2(tokens.clone())?; - match lang { - Lang::Cxx => Ok(Api::TypeAlias(type_alias)), - Lang::Rust => { - let (type_token, semi_token) = (type_alias.type_token, type_alias.semi_token); - let span = quote!(#type_token #semi_token); - let msg = "type alias in extern \"Rust\" block is not supported"; - Err(Error::new_spanned(span, msg)) +fn parse_extern_verbatim_fn(input: ParseStream) -> Result<Api> { + input.parse::<RustSignature>()?; + input.parse::<Token![;]>()?; + unreachable!() +} + +fn parse_type_alias( + cx: &mut Errors, + attrs: Vec<Attribute>, + visibility: Visibility, + type_token: Token![type], + ident: Ident, + generics: Lifetimes, + input: ParseStream, + lang: Lang, + namespace: &Namespace, +) -> Result<Api> { + let eq_token: Token![=] = input.parse()?; + let ty: RustType = input.parse()?; + let semi_token: Token![;] = input.parse()?; + + let mut doc = Doc::new(); + let mut derives = Vec::new(); + let mut namespace = namespace.clone(); + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( + cx, + attrs, + attrs::Parser { + doc: Some(&mut doc), + derives: Some(&mut derives), + namespace: Some(&mut namespace), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), + ..Default::default() + }, + ); + + if lang == Lang::Rust { + let span = quote!(#type_token #semi_token); + let msg = "type alias in extern \"Rust\" block is not supported"; + return Err(Error::new_spanned(span, msg)); + } + + let visibility = visibility_pub(&visibility, type_token.span); + let name = pair(namespace, &ident, cxx_name, rust_name); + + Ok(Api::TypeAlias(TypeAlias { + doc, + derives, + attrs, + visibility, + type_token, + name, + generics, + eq_token, + ty, + semi_token, + })) +} + +fn parse_extern_type_bounded( + cx: &mut Errors, + attrs: Vec<Attribute>, + visibility: Visibility, + type_token: Token![type], + ident: Ident, + generics: Lifetimes, + input: ParseStream, + lang: Lang, + trusted: bool, + namespace: &Namespace, +) -> Result<Api> { + let mut bounds = Vec::new(); + let colon_token: Option<Token![:]> = input.parse()?; + if colon_token.is_some() { + loop { + match input.parse()? { + TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path, + }) if if let Some(derive) = path.get_ident().and_then(Derive::from) { + bounds.push(derive); + true + } else { + false + } => {} + bound @ TypeParamBound::Trait(_) | bound @ TypeParamBound::Lifetime(_) => { + cx.error(bound, "unsupported trait"); + } + } + + let lookahead = input.lookahead1(); + if lookahead.peek(Token![+]) { + input.parse::<Token![+]>()?; + } else if lookahead.peek(Token![;]) { + break; + } else { + return Err(lookahead.error()); + } } } + let semi_token: Token![;] = input.parse()?; + + let mut doc = Doc::new(); + let mut derives = Vec::new(); + let mut namespace = namespace.clone(); + let mut cxx_name = None; + let mut rust_name = None; + let attrs = attrs::parse( + cx, + attrs, + attrs::Parser { + doc: Some(&mut doc), + derives: Some(&mut derives), + namespace: Some(&mut namespace), + cxx_name: Some(&mut cxx_name), + rust_name: Some(&mut rust_name), + ..Default::default() + }, + ); + + let visibility = visibility_pub(&visibility, type_token.span); + let name = pair(namespace, &ident, cxx_name, rust_name); + + Ok(match lang { + Lang::Cxx => Api::CxxType, + Lang::Rust => Api::RustType, + }(ExternType { + lang, + doc, + derives, + attrs, + visibility, + type_token, + name, + generics, + colon_token, + bounds, + semi_token, + trusted, + })) } -fn parse_impl(imp: ItemImpl, namespace: &Namespace) -> Result<Api> { +fn parse_impl(imp: ItemImpl) -> Result<Api> { + let impl_token = imp.impl_token; + if !imp.items.is_empty() { let mut span = Group::new(Delimiter::Brace, TokenStream::new()); span.set_span(imp.brace_token.span); return Err(Error::new_spanned(span, "expected an empty impl block")); } - let self_ty = &imp.self_ty; if let Some((bang, path, for_token)) = &imp.trait_ { + let self_ty = &imp.self_ty; let span = quote!(#bang #path #for_token #self_ty); return Err(Error::new_spanned( span, @@ -519,18 +908,83 @@ fn parse_impl(imp: ItemImpl, namespace: &Namespace) -> Result<Api> { )); } - let generics = &imp.generics; - if !generics.params.is_empty() || generics.where_clause.is_some() { + if let Some(where_clause) = imp.generics.where_clause { return Err(Error::new_spanned( - imp, - "generic parameters on an impl is not supported", + where_clause, + "where-clause on an impl is not supported yet", )); } + let mut impl_generics = Lifetimes { + lt_token: imp.generics.lt_token, + lifetimes: Punctuated::new(), + gt_token: imp.generics.gt_token, + }; + for pair in imp.generics.params.into_pairs() { + let (param, punct) = pair.into_tuple(); + match param { + GenericParam::Lifetime(def) if def.bounds.is_empty() => { + impl_generics.lifetimes.push_value(def.lifetime); + if let Some(punct) = punct { + impl_generics.lifetimes.push_punct(punct); + } + } + _ => { + let span = quote!(#impl_token #impl_generics); + return Err(Error::new_spanned( + span, + "generic parameter on an impl is not supported yet", + )); + } + } + } + + let mut negative_token = None; + let mut self_ty = *imp.self_ty; + if let RustType::Verbatim(ty) = &self_ty { + let mut iter = ty.clone().into_iter(); + if let Some(TokenTree::Punct(punct)) = iter.next() { + if punct.as_char() == '!' { + let ty = iter.collect::<TokenStream>(); + if !ty.is_empty() { + negative_token = Some(Token![!](punct.span())); + self_ty = syn::parse2(ty)?; + } + } + } + } + + let ty = parse_type(&self_ty)?; + let ty_generics = match &ty { + Type::RustBox(ty) + | Type::RustVec(ty) + | Type::UniquePtr(ty) + | Type::SharedPtr(ty) + | Type::WeakPtr(ty) + | Type::CxxVector(ty) => match &ty.inner { + Type::Ident(ident) => ident.generics.clone(), + _ => Lifetimes::default(), + }, + Type::Ident(_) + | Type::Ref(_) + | Type::Ptr(_) + | Type::Str(_) + | Type::Fn(_) + | Type::Void(_) + | Type::SliceRef(_) + | Type::Array(_) => Lifetimes::default(), + }; + + let negative = negative_token.is_some(); + let brace_token = imp.brace_token; Ok(Api::Impl(Impl { - impl_token: imp.impl_token, - ty: parse_type(&self_ty, namespace)?, - brace_token: imp.brace_token, + impl_token, + impl_generics, + negative, + ty, + ty_generics, + brace_token, + negative_token, })) } @@ -578,20 +1032,42 @@ fn parse_include(input: ParseStream) -> Result<Include> { Err(input.error("expected \"quoted/path/to\" or <bracketed/path/to>")) } -fn parse_type(ty: &RustType, namespace: &Namespace) -> Result<Type> { +fn parse_type(ty: &RustType) -> Result<Type> { match 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::Reference(ty) => parse_type_reference(ty), + RustType::Ptr(ty) => parse_type_ptr(ty), + RustType::Path(ty) => parse_type_path(ty), + RustType::Array(ty) => parse_type_array(ty), + RustType::BareFn(ty) => parse_type_fn(ty), RustType::Tuple(ty) if ty.elems.is_empty() => Ok(Type::Void(ty.paren_token.span)), _ => Err(Error::new_spanned(ty, "unsupported type")), } } -fn parse_type_reference(ty: &TypeReference, namespace: &Namespace) -> Result<Type> { - let inner = parse_type(&ty.elem, namespace)?; - let which = match &inner { +fn parse_type_reference(ty: &TypeReference) -> Result<Type> { + let ampersand = ty.and_token; + let lifetime = ty.lifetime.clone(); + let mutable = ty.mutability.is_some(); + let mutability = ty.mutability; + + if let RustType::Slice(slice) = ty.elem.as_ref() { + let inner = parse_type(&slice.elem)?; + let bracket = slice.bracket_token; + return Ok(Type::SliceRef(Box::new(SliceRef { + ampersand, + lifetime, + mutable, + bracket, + inner, + mutability, + }))); + } + + let inner = parse_type(&ty.elem)?; + let pinned = false; + let pin_tokens = None; + + Ok(match &inner { Type::Ident(ident) if ident.rust == "str" => { if ty.mutability.is_some() { return Err(Error::new_spanned(ty, "unsupported type")); @@ -599,31 +1075,46 @@ fn parse_type_reference(ty: &TypeReference, namespace: &Namespace) -> Result<Typ Type::Str } } - Type::Slice(slice) => match &slice.inner { - Type::Ident(ident) if ident.rust == U8 && ty.mutability.is_none() => Type::SliceRefU8, - _ => Type::Ref, - }, _ => Type::Ref, - }; - Ok(which(Box::new(Ref { - ampersand: ty.and_token, - lifetime: ty.lifetime.clone(), - mutability: ty.mutability, + }(Box::new(Ref { + pinned, + ampersand, + lifetime, + mutable, inner, + pin_tokens, + mutability, }))) } -fn parse_type_path(ty: &TypePath, namespace: &Namespace) -> Result<Type> { +fn parse_type_ptr(ty: &TypePtr) -> Result<Type> { + let star = ty.star_token; + let mutable = ty.mutability.is_some(); + let constness = ty.const_token; + let mutability = ty.mutability; + + let inner = parse_type(&ty.elem)?; + + Ok(Type::Ptr(Box::new(Ptr { + star, + mutable, + inner, + mutability, + constness, + }))) +} + +fn parse_type_path(ty: &TypePath) -> Result<Type> { let path = &ty.path; if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 { let segment = &path.segments[0]; let ident = segment.ident.clone(); match &segment.arguments { - PathArguments::None => return Ok(Type::Ident(ResolvableName::new(ident))), + PathArguments::None => return Ok(Type::Ident(NamedType::new(ident))), PathArguments::AngleBracketed(generic) => { if ident == "UniquePtr" && generic.args.len() == 1 { if let GenericArgument::Type(arg) = &generic.args[0] { - let inner = parse_type(arg, namespace)?; + let inner = parse_type(arg)?; return Ok(Type::UniquePtr(Box::new(Ty1 { name: ident, langle: generic.lt_token, @@ -631,9 +1122,29 @@ fn parse_type_path(ty: &TypePath, namespace: &Namespace) -> Result<Type> { rangle: generic.gt_token, }))); } + } else if ident == "SharedPtr" && generic.args.len() == 1 { + if let GenericArgument::Type(arg) = &generic.args[0] { + let inner = parse_type(arg)?; + return Ok(Type::SharedPtr(Box::new(Ty1 { + name: ident, + langle: generic.lt_token, + inner, + rangle: generic.gt_token, + }))); + } + } else if ident == "WeakPtr" && generic.args.len() == 1 { + if let GenericArgument::Type(arg) = &generic.args[0] { + let inner = parse_type(arg)?; + return Ok(Type::WeakPtr(Box::new(Ty1 { + name: ident, + langle: generic.lt_token, + inner, + rangle: generic.gt_token, + }))); + } } else if ident == "CxxVector" && generic.args.len() == 1 { if let GenericArgument::Type(arg) = &generic.args[0] { - let inner = parse_type(arg, namespace)?; + let inner = parse_type(arg)?; return Ok(Type::CxxVector(Box::new(Ty1 { name: ident, langle: generic.lt_token, @@ -643,7 +1154,7 @@ fn parse_type_path(ty: &TypePath, namespace: &Namespace) -> Result<Type> { } } else if ident == "Box" && generic.args.len() == 1 { if let GenericArgument::Type(arg) = &generic.args[0] { - let inner = parse_type(arg, namespace)?; + let inner = parse_type(arg)?; return Ok(Type::RustBox(Box::new(Ty1 { name: ident, langle: generic.lt_token, @@ -653,7 +1164,7 @@ fn parse_type_path(ty: &TypePath, namespace: &Namespace) -> Result<Type> { } } else if ident == "Vec" && generic.args.len() == 1 { if let GenericArgument::Type(arg) = &generic.args[0] { - let inner = parse_type(arg, namespace)?; + let inner = parse_type(arg)?; return Ok(Type::RustVec(Box::new(Ty1 { name: ident, langle: generic.lt_token, @@ -661,59 +1172,144 @@ fn parse_type_path(ty: &TypePath, namespace: &Namespace) -> Result<Type> { rangle: generic.gt_token, }))); } + } else if ident == "Pin" && generic.args.len() == 1 { + if let GenericArgument::Type(arg) = &generic.args[0] { + let inner = parse_type(arg)?; + let pin_token = kw::Pin(ident.span()); + if let Type::Ref(mut inner) = inner { + inner.pinned = true; + inner.pin_tokens = + Some((pin_token, generic.lt_token, generic.gt_token)); + return Ok(Type::Ref(inner)); + } + } + } else { + let mut lifetimes = Punctuated::new(); + let mut only_lifetimes = true; + for pair in generic.args.pairs() { + let (param, punct) = pair.into_tuple(); + if let GenericArgument::Lifetime(param) = param { + lifetimes.push_value(param.clone()); + if let Some(punct) = punct { + lifetimes.push_punct(*punct); + } + } else { + only_lifetimes = false; + break; + } + } + if only_lifetimes { + return Ok(Type::Ident(NamedType { + rust: ident, + generics: Lifetimes { + lt_token: Some(generic.lt_token), + lifetimes, + gt_token: Some(generic.gt_token), + }, + })); + } } } PathArguments::Parenthesized(_) => {} } } + Err(Error::new_spanned(ty, "unsupported type")) } -fn parse_type_slice(ty: &TypeSlice, namespace: &Namespace) -> Result<Type> { - let inner = parse_type(&ty.elem, namespace)?; - Ok(Type::Slice(Box::new(Slice { - bracket: ty.bracket_token, +fn parse_type_array(ty: &TypeArray) -> Result<Type> { + let inner = parse_type(&ty.elem)?; + + let len_expr = if let Expr::Lit(lit) = &ty.len { + lit + } else { + let msg = "unsupported expression, array length must be an integer literal"; + return Err(Error::new_spanned(&ty.len, msg)); + }; + + let len_token = if let Lit::Int(int) = &len_expr.lit { + int.clone() + } else { + let msg = "array length must be an integer literal"; + return Err(Error::new_spanned(len_expr, msg)); + }; + + let len = len_token.base10_parse::<usize>()?; + if len == 0 { + let msg = "array with zero size is not supported"; + return Err(Error::new_spanned(ty, msg)); + } + + let bracket = ty.bracket_token; + let semi_token = ty.semi_token; + + Ok(Type::Array(Box::new(Array { + bracket, inner, + semi_token, + len, + len_token, }))) } -fn parse_type_fn(ty: &TypeBareFn, namespace: &Namespace) -> Result<Type> { +fn parse_type_fn(ty: &TypeBareFn) -> Result<Type> { if ty.lifetimes.is_some() { return Err(Error::new_spanned( ty, "function pointer with lifetime parameters is not supported yet", )); } + if ty.variadic.is_some() { return Err(Error::new_spanned( ty, "variadic function pointer is not supported yet", )); } + let args = ty .inputs .iter() .enumerate() .map(|(i, arg)| { - let ty = parse_type(&arg.ty, namespace)?; + let ty = parse_type(&arg.ty)?; let ident = match &arg.name { Some(ident) => ident.0.clone(), - None => format_ident!("_{}", i), + None => format_ident!("arg{}", i), }; - Ok(Var { ident, ty }) + let doc = Doc::new(); + let attrs = OtherAttrs::none(); + let visibility = Token![pub](ident.span()); + let name = pair(Namespace::default(), &ident, None, None); + Ok(Var { + doc, + attrs, + visibility, + name, + ty, + }) }) .collect::<Result<_>>()?; + let mut throws_tokens = None; - let ret = parse_return_type(&ty.output, &mut throws_tokens, namespace)?; + let ret = parse_return_type(&ty.output, &mut throws_tokens)?; let throws = throws_tokens.is_some(); + + let unsafety = ty.unsafety; + let fn_token = ty.fn_token; + let generics = Generics::default(); + let receiver = None; + let paren_token = ty.paren_token; + Ok(Type::Fn(Box::new(Signature { - unsafety: ty.unsafety, - fn_token: ty.fn_token, - receiver: None, + unsafety, + fn_token, + generics, + receiver, args, ret, throws, - paren_token: ty.paren_token, + paren_token, throws_tokens, }))) } @@ -721,12 +1317,12 @@ fn parse_type_fn(ty: &TypeBareFn, namespace: &Namespace) -> Result<Type> { 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), ReturnType::Type(_, ret) => ret.as_ref(), }; + if let RustType::Path(ty) = ret { let path = &ty.path; if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 { @@ -743,8 +1339,32 @@ fn parse_return_type( } } } - match parse_type(ret, namespace)? { + + match parse_type(ret)? { Type::Void(_) => Ok(None), ty => Ok(Some(ty)), } } + +fn visibility_pub(vis: &Visibility, inherited: Span) -> Token![pub] { + Token![pub](match vis { + Visibility::Public(vis) => vis.pub_token.span, + Visibility::Crate(vis) => vis.crate_token.span, + Visibility::Restricted(vis) => vis.pub_token.span, + Visibility::Inherited => inherited, + }) +} + +fn pair( + namespace: Namespace, + default: &Ident, + cxx: Option<ForeignName>, + rust: Option<Ident>, +) -> Pair { + Pair { + namespace, + cxx: cxx + .unwrap_or_else(|| ForeignName::parse(&default.to_string(), default.span()).unwrap()), + rust: rust.unwrap_or_else(|| default.clone()), + } +} diff --git a/syntax/pod.rs b/syntax/pod.rs new file mode 100644 index 00000000..0bf152ee --- /dev/null +++ b/syntax/pod.rs @@ -0,0 +1,36 @@ +use crate::syntax::atom::Atom::{self, *}; +use crate::syntax::{derive, Trait, Type, Types}; + +impl<'a> Types<'a> { + pub fn is_guaranteed_pod(&self, ty: &Type) -> bool { + match ty { + Type::Ident(ident) => { + let ident = &ident.rust; + if let Some(atom) = Atom::from(ident) { + match atom { + Bool | Char | U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 + | Isize | F32 | F64 => true, + CxxString | RustString => false, + } + } else if let Some(strct) = self.structs.get(ident) { + derive::contains(&strct.derives, Trait::Copy) + || strct + .fields + .iter() + .all(|field| self.is_guaranteed_pod(&field.ty)) + } else { + self.enums.contains_key(ident) + } + } + Type::RustBox(_) + | Type::RustVec(_) + | Type::UniquePtr(_) + | Type::SharedPtr(_) + | Type::WeakPtr(_) + | Type::CxxVector(_) + | Type::Void(_) => false, + Type::Ref(_) | Type::Str(_) | Type::Fn(_) | Type::SliceRef(_) | Type::Ptr(_) => true, + Type::Array(array) => self.is_guaranteed_pod(&array.inner), + } + } +} diff --git a/syntax/qualified.rs b/syntax/qualified.rs index 5eefb8db..96f07c19 100644 --- a/syntax/qualified.rs +++ b/syntax/qualified.rs @@ -10,14 +10,14 @@ impl QualifiedName { pub fn parse_unquoted(input: ParseStream) -> Result<Self> { let mut segments = Vec::new(); let mut trailing_punct = true; - input.parse::<Option<Token![::]>>()?; + let leading_colons: Option<Token![::]> = input.parse()?; while trailing_punct && input.peek(Ident::peek_any) { let ident = Ident::parse_any(input)?; segments.push(ident); let colons: Option<Token![::]> = input.parse()?; trailing_punct = colons.is_some(); } - if segments.is_empty() { + if segments.is_empty() && leading_colons.is_none() { return Err(input.error("expected path")); } else if trailing_punct { return Err(input.error("expected path segment")); @@ -28,7 +28,12 @@ impl QualifiedName { 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) + if lit.value().is_empty() { + let segments = Vec::new(); + Ok(QualifiedName { segments }) + } else { + lit.parse_with(Self::parse_unquoted) + } } else { Self::parse_unquoted(input) } diff --git a/syntax/resolve.rs b/syntax/resolve.rs new file mode 100644 index 00000000..3a2635bd --- /dev/null +++ b/syntax/resolve.rs @@ -0,0 +1,46 @@ +use crate::syntax::instantiate::NamedImplKey; +use crate::syntax::{Lifetimes, NamedType, Pair, Types}; +use proc_macro2::Ident; + +#[derive(Copy, Clone)] +pub struct Resolution<'a> { + pub name: &'a Pair, + pub generics: &'a Lifetimes, +} + +impl<'a> Types<'a> { + pub fn resolve(&self, ident: &impl UnresolvedName) -> Resolution<'a> { + let ident = ident.ident(); + match self.try_resolve(ident) { + Some(resolution) => resolution, + None => panic!("Unable to resolve type `{}`", ident), + } + } + + pub fn try_resolve(&self, ident: &impl UnresolvedName) -> Option<Resolution<'a>> { + let ident = ident.ident(); + self.resolutions.get(ident).copied() + } +} + +pub trait UnresolvedName { + fn ident(&self) -> &Ident; +} + +impl UnresolvedName for Ident { + fn ident(&self) -> &Ident { + self + } +} + +impl UnresolvedName for NamedType { + fn ident(&self) -> &Ident { + &self.rust + } +} + +impl<'a> UnresolvedName for NamedImplKey<'a> { + fn ident(&self) -> &Ident { + self.rust + } +} diff --git a/syntax/set.rs b/syntax/set.rs index 6508f556..ca0c43e0 100644 --- a/syntax/set.rs +++ b/syntax/set.rs @@ -50,11 +50,21 @@ mod ordered { } } + impl<'a, T> OrderedSet<&'a T> { + pub fn is_empty(&self) -> bool { + self.vec.is_empty() + } + + pub fn iter(&self) -> Iter<'_, 'a, T> { + Iter(self.vec.iter()) + } + } + impl<'s, 'a, T> IntoIterator for &'s OrderedSet<&'a T> { type Item = &'a T; type IntoIter = Iter<'s, 'a, T>; fn into_iter(self) -> Self::IntoIter { - Iter(self.vec.iter()) + self.iter() } } } @@ -95,6 +105,10 @@ mod unordered { { self.0.get(value) } + + pub fn retain(&mut self, f: impl FnMut(&T) -> bool) { + self.0.retain(f); + } } } @@ -102,9 +116,14 @@ pub struct Iter<'s, 'a, T>(slice::Iter<'s, &'a T>); impl<'s, 'a, T> Iterator for Iter<'s, 'a, T> { type Item = &'a T; + fn next(&mut self) -> Option<Self::Item> { self.0.next().copied() } + + fn size_hint(&self) -> (usize, Option<usize>) { + self.0.size_hint() + } } impl<'a, T> Debug for OrderedSet<&'a T> diff --git a/syntax/symbol.rs b/syntax/symbol.rs index 253f57dc..a13a4f3c 100644 --- a/syntax/symbol.rs +++ b/syntax/symbol.rs @@ -1,11 +1,11 @@ use crate::syntax::namespace::Namespace; -use crate::syntax::Pair; +use crate::syntax::{ForeignName, Pair}; use proc_macro2::{Ident, TokenStream}; use quote::ToTokens; use std::fmt::{self, Display, Write}; // A mangled symbol consisting of segments separated by '$'. -// For example: cxxbridge05$string$new +// For example: cxxbridge1$string$new pub struct Symbol(String); impl Display for Symbol { @@ -30,7 +30,7 @@ impl Symbol { assert!(self.0.len() > len_before); } - pub fn from_idents<'a, T: Iterator<Item = &'a Ident>>(it: T) -> Self { + pub fn from_idents<'a>(it: impl Iterator<Item = &'a dyn Segment>) -> Self { let mut symbol = Symbol(String::new()); for segment in it { segment.write(&mut symbol); @@ -55,16 +55,19 @@ impl Segment for str { symbol.push(&self); } } + impl Segment for usize { fn write(&self, symbol: &mut Symbol) { symbol.push(&self); } } + impl Segment for Ident { fn write(&self, symbol: &mut Symbol) { symbol.push(&self); } } + impl Segment for Symbol { fn write(&self, symbol: &mut Symbol) { symbol.push(&self); @@ -86,6 +89,14 @@ impl Segment for Pair { } } +impl Segment for ForeignName { + fn write(&self, symbol: &mut Symbol) { + // TODO: support C++ names containing whitespace (`unsigned int`) or + // non-alphanumeric characters (`operator++`). + self.to_string().write(symbol); + } +} + impl<T> Segment for &'_ T where T: ?Sized + Segment + Display, diff --git a/syntax/tokens.rs b/syntax/tokens.rs index 500ea0b3..c1a06a21 100644 --- a/syntax/tokens.rs +++ b/syntax/tokens.rs @@ -1,77 +1,155 @@ use crate::syntax::atom::Atom::*; use crate::syntax::{ - Atom, Derive, Enum, ExternFn, ExternType, Impl, Receiver, Ref, ResolvableName, Signature, - Slice, Struct, Ty1, Type, TypeAlias, Var, + Array, Atom, Derive, Enum, ExternFn, ExternType, Impl, Lifetimes, NamedType, Ptr, Receiver, + Ref, Signature, SliceRef, Struct, Ty1, Type, TypeAlias, Var, }; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote_spanned, ToTokens}; -use syn::Token; +use syn::{token, Token}; impl ToTokens for Type { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Type::Ident(ident) => { - if ident.rust == CxxString { + if ident.rust == Char { + let span = ident.rust.span(); + tokens.extend(quote_spanned!(span=> ::std::os::raw::)); + } else if ident.rust == CxxString { let span = ident.rust.span(); tokens.extend(quote_spanned!(span=> ::cxx::)); } - ident.rust.to_tokens(tokens); - } - Type::RustBox(ty) | Type::UniquePtr(ty) | Type::CxxVector(ty) | Type::RustVec(ty) => { - ty.to_tokens(tokens) + ident.to_tokens(tokens); } - Type::Ref(r) | Type::Str(r) | Type::SliceRefU8(r) => r.to_tokens(tokens), - Type::Slice(s) => s.to_tokens(tokens), + Type::RustBox(ty) + | Type::UniquePtr(ty) + | Type::SharedPtr(ty) + | Type::WeakPtr(ty) + | Type::CxxVector(ty) + | Type::RustVec(ty) => ty.to_tokens(tokens), + Type::Ref(r) | Type::Str(r) => r.to_tokens(tokens), + Type::Ptr(p) => p.to_tokens(tokens), + Type::Array(a) => a.to_tokens(tokens), Type::Fn(f) => f.to_tokens(tokens), Type::Void(span) => tokens.extend(quote_spanned!(*span=> ())), + Type::SliceRef(r) => r.to_tokens(tokens), } } } impl ToTokens for Var { fn to_tokens(&self, tokens: &mut TokenStream) { - self.ident.to_tokens(tokens); - Token![:](self.ident.span()).to_tokens(tokens); - self.ty.to_tokens(tokens); + let Var { + doc: _, + attrs: _, + visibility: _, + name, + ty, + } = self; + name.rust.to_tokens(tokens); + Token![:](name.rust.span()).to_tokens(tokens); + ty.to_tokens(tokens); } } impl ToTokens for Ty1 { fn to_tokens(&self, tokens: &mut TokenStream) { - let span = self.name.span(); - let name = self.name.to_string(); - if let "UniquePtr" | "CxxVector" = name.as_str() { - tokens.extend(quote_spanned!(span=> ::cxx::)); - } else if name == "Vec" { - tokens.extend(quote_spanned!(span=> ::std::vec::)); + let Ty1 { + name, + langle, + inner, + rangle, + } = self; + let span = name.span(); + match name.to_string().as_str() { + "UniquePtr" | "SharedPtr" | "WeakPtr" | "CxxVector" => { + tokens.extend(quote_spanned!(span=> ::cxx::)); + } + "Vec" => { + tokens.extend(quote_spanned!(span=> ::std::vec::)); + } + _ => {} } - self.name.to_tokens(tokens); - self.langle.to_tokens(tokens); - self.inner.to_tokens(tokens); - self.rangle.to_tokens(tokens); + name.to_tokens(tokens); + langle.to_tokens(tokens); + inner.to_tokens(tokens); + rangle.to_tokens(tokens); } } impl ToTokens for Ref { fn to_tokens(&self, tokens: &mut TokenStream) { - self.ampersand.to_tokens(tokens); - self.lifetime.to_tokens(tokens); - self.mutability.to_tokens(tokens); - self.inner.to_tokens(tokens); + let Ref { + pinned: _, + ampersand, + lifetime, + mutable: _, + inner, + pin_tokens, + mutability, + } = self; + if let Some((pin, langle, _rangle)) = pin_tokens { + tokens.extend(quote_spanned!(pin.span=> ::std::pin::Pin)); + langle.to_tokens(tokens); + } + ampersand.to_tokens(tokens); + lifetime.to_tokens(tokens); + mutability.to_tokens(tokens); + inner.to_tokens(tokens); + if let Some((_pin, _langle, rangle)) = pin_tokens { + rangle.to_tokens(tokens); + } + } +} + +impl ToTokens for Ptr { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Ptr { + star, + mutable: _, + inner, + mutability, + constness, + } = self; + star.to_tokens(tokens); + mutability.to_tokens(tokens); + constness.to_tokens(tokens); + inner.to_tokens(tokens); } } -impl ToTokens for Slice { +impl ToTokens for SliceRef { fn to_tokens(&self, tokens: &mut TokenStream) { - self.bracket.surround(tokens, |tokens| { - self.inner.to_tokens(tokens); + let SliceRef { + ampersand, + lifetime, + mutable: _, + bracket, + inner, + mutability, + } = self; + ampersand.to_tokens(tokens); + lifetime.to_tokens(tokens); + mutability.to_tokens(tokens); + bracket.surround(tokens, |tokens| { + inner.to_tokens(tokens); }); } } -impl ToTokens for Derive { +impl ToTokens for Array { fn to_tokens(&self, tokens: &mut TokenStream) { - Ident::new(self.as_ref(), Span::call_site()).to_tokens(tokens); + let Array { + bracket, + inner, + semi_token, + len: _, + len_token, + } = self; + bracket.surround(tokens, |tokens| { + inner.to_tokens(tokens); + semi_token.to_tokens(tokens); + len_token.to_tokens(tokens); + }); } } @@ -81,11 +159,18 @@ impl ToTokens for Atom { } } +impl ToTokens for Derive { + fn to_tokens(&self, tokens: &mut TokenStream) { + Ident::new(self.what.as_ref(), self.span).to_tokens(tokens); + } +} + impl ToTokens for ExternType { fn to_tokens(&self, tokens: &mut TokenStream) { // Notional token range for error reporting purposes. self.type_token.to_tokens(tokens); self.name.rust.to_tokens(tokens); + self.generics.to_tokens(tokens); } } @@ -94,6 +179,7 @@ impl ToTokens for TypeAlias { // Notional token range for error reporting purposes. self.type_token.to_tokens(tokens); self.name.rust.to_tokens(tokens); + self.generics.to_tokens(tokens); } } @@ -102,6 +188,7 @@ impl ToTokens for Struct { // Notional token range for error reporting purposes. self.struct_token.to_tokens(tokens); self.name.rust.to_tokens(tokens); + self.generics.to_tokens(tokens); } } @@ -110,12 +197,14 @@ impl ToTokens for Enum { // Notional token range for error reporting purposes. self.enum_token.to_tokens(tokens); self.name.rust.to_tokens(tokens); + self.generics.to_tokens(tokens); } } impl ToTokens for ExternFn { fn to_tokens(&self, tokens: &mut TokenStream) { // Notional token range for error reporting purposes. + self.unsafety.to_tokens(tokens); self.sig.fn_token.to_tokens(tokens); self.semi_token.to_tokens(tokens); } @@ -123,21 +212,56 @@ impl ToTokens for ExternFn { 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| {}); + let Impl { + impl_token, + impl_generics, + negative: _, + ty, + ty_generics: _, + brace_token, + negative_token, + } = self; + impl_token.to_tokens(tokens); + impl_generics.to_tokens(tokens); + negative_token.to_tokens(tokens); + ty.to_tokens(tokens); + brace_token.surround(tokens, |_tokens| {}); + } +} + +impl ToTokens for Lifetimes { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Lifetimes { + lt_token, + lifetimes, + gt_token, + } = self; + lt_token.to_tokens(tokens); + lifetimes.to_tokens(tokens); + gt_token.to_tokens(tokens); } } impl ToTokens for Signature { fn to_tokens(&self, tokens: &mut TokenStream) { - self.fn_token.to_tokens(tokens); - self.paren_token.surround(tokens, |tokens| { - self.args.to_tokens(tokens); + let Signature { + unsafety: _, + fn_token, + generics: _, + receiver: _, + args, + ret, + throws: _, + paren_token, + throws_tokens, + } = self; + fn_token.to_tokens(tokens); + paren_token.surround(tokens, |tokens| { + args.to_tokens(tokens); }); - if let Some(ret) = &self.ret { - Token![->](self.paren_token.span).to_tokens(tokens); - if let Some((result, langle, rangle)) = self.throws_tokens { + if let Some(ret) = ret { + Token![->](paren_token.span).to_tokens(tokens); + if let Some((result, langle, rangle)) = throws_tokens { result.to_tokens(tokens); langle.to_tokens(tokens); ret.to_tokens(tokens); @@ -145,30 +269,89 @@ impl ToTokens for Signature { } else { ret.to_tokens(tokens); } + } else if let Some((result, langle, rangle)) = throws_tokens { + Token![->](paren_token.span).to_tokens(tokens); + result.to_tokens(tokens); + langle.to_tokens(tokens); + token::Paren(langle.span).surround(tokens, |_| ()); + rangle.to_tokens(tokens); } } } -impl ToTokens for ResolvableName { +impl ToTokens for NamedType { fn to_tokens(&self, tokens: &mut TokenStream) { - self.rust.to_tokens(tokens); + let NamedType { rust, generics } = self; + rust.to_tokens(tokens); + generics.to_tokens(tokens); } } pub struct ReceiverType<'a>(&'a Receiver); +pub struct ReceiverTypeSelf<'a>(&'a Receiver); impl Receiver { // &TheType pub fn ty(&self) -> ReceiverType { ReceiverType(self) } + + // &Self + pub fn ty_self(&self) -> ReceiverTypeSelf { + ReceiverTypeSelf(self) + } } impl ToTokens for ReceiverType<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { - self.0.ampersand.to_tokens(tokens); - self.0.lifetime.to_tokens(tokens); - self.0.mutability.to_tokens(tokens); - self.0.ty.to_tokens(tokens); + let Receiver { + pinned: _, + ampersand, + lifetime, + mutable: _, + var: _, + ty, + shorthand: _, + pin_tokens, + mutability, + } = &self.0; + if let Some((pin, langle, _rangle)) = pin_tokens { + tokens.extend(quote_spanned!(pin.span=> ::std::pin::Pin)); + langle.to_tokens(tokens); + } + ampersand.to_tokens(tokens); + lifetime.to_tokens(tokens); + mutability.to_tokens(tokens); + ty.to_tokens(tokens); + if let Some((_pin, _langle, rangle)) = pin_tokens { + rangle.to_tokens(tokens); + } + } +} + +impl ToTokens for ReceiverTypeSelf<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Receiver { + pinned: _, + ampersand, + lifetime, + mutable: _, + var: _, + ty, + shorthand: _, + pin_tokens, + mutability, + } = &self.0; + if let Some((pin, langle, _rangle)) = pin_tokens { + tokens.extend(quote_spanned!(pin.span=> ::std::pin::Pin)); + langle.to_tokens(tokens); + } + ampersand.to_tokens(tokens); + lifetime.to_tokens(tokens); + mutability.to_tokens(tokens); + Token![Self](ty.rust.span()).to_tokens(tokens); + if let Some((_pin, _langle, rangle)) = pin_tokens { + rangle.to_tokens(tokens); + } } } diff --git a/syntax/toposort.rs b/syntax/toposort.rs index 876f9ad1..8fe55b8b 100644 --- a/syntax/toposort.rs +++ b/syntax/toposort.rs @@ -1,6 +1,6 @@ +use crate::syntax::map::{Entry, UnorderedMap as Map}; use crate::syntax::report::Errors; use crate::syntax::{Api, Struct, Type, Types}; -use std::collections::btree_map::{BTreeMap as Map, Entry}; enum Mark { Visiting, diff --git a/syntax/trivial.rs b/syntax/trivial.rs new file mode 100644 index 00000000..fe95e2b7 --- /dev/null +++ b/syntax/trivial.rs @@ -0,0 +1,257 @@ +use crate::syntax::map::UnorderedMap; +use crate::syntax::set::{OrderedSet as Set, UnorderedSet}; +use crate::syntax::{Api, Enum, ExternFn, NamedType, Pair, Struct, Type}; +use proc_macro2::Ident; +use std::fmt::{self, Display}; + +#[derive(Copy, Clone)] +pub enum TrivialReason<'a> { + StructField(&'a Struct), + FunctionArgument(&'a ExternFn), + FunctionReturn(&'a ExternFn), + BoxTarget, + VecElement, + UnpinnedMut(&'a ExternFn), +} + +pub fn required_trivial_reasons<'a>( + apis: &'a [Api], + all: &Set<&'a Type>, + structs: &UnorderedMap<&'a Ident, &'a Struct>, + enums: &UnorderedMap<&'a Ident, &'a Enum>, + cxx: &UnorderedSet<&'a Ident>, +) -> UnorderedMap<&'a Ident, Vec<TrivialReason<'a>>> { + let mut required_trivial = UnorderedMap::new(); + + let mut insist_extern_types_are_trivial = |ident: &'a NamedType, reason| { + if cxx.contains(&ident.rust) + && !structs.contains_key(&ident.rust) + && !enums.contains_key(&ident.rust) + { + required_trivial + .entry(&ident.rust) + .or_insert_with(Vec::new) + .push(reason); + } + }; + + for api in apis { + match api { + Api::Struct(strct) => { + for field in &strct.fields { + if let Type::Ident(ident) = &field.ty { + let reason = TrivialReason::StructField(strct); + insist_extern_types_are_trivial(ident, reason); + } + } + } + Api::CxxFunction(efn) | Api::RustFunction(efn) => { + if let Some(receiver) = &efn.receiver { + if receiver.mutable && !receiver.pinned { + let reason = TrivialReason::UnpinnedMut(efn); + insist_extern_types_are_trivial(&receiver.ty, reason); + } + } + for arg in &efn.args { + match &arg.ty { + Type::Ident(ident) => { + let reason = TrivialReason::FunctionArgument(efn); + insist_extern_types_are_trivial(ident, reason); + } + Type::Ref(ty) => { + if ty.mutable && !ty.pinned { + if let Type::Ident(ident) = &ty.inner { + let reason = TrivialReason::UnpinnedMut(efn); + insist_extern_types_are_trivial(ident, reason); + } + } + } + _ => {} + } + } + if let Some(ret) = &efn.ret { + match ret { + Type::Ident(ident) => { + let reason = TrivialReason::FunctionReturn(efn); + insist_extern_types_are_trivial(ident, reason); + } + Type::Ref(ty) => { + if ty.mutable && !ty.pinned { + if let Type::Ident(ident) = &ty.inner { + let reason = TrivialReason::UnpinnedMut(efn); + insist_extern_types_are_trivial(ident, reason); + } + } + } + _ => {} + } + } + } + _ => {} + } + } + + for ty in all { + match ty { + Type::RustBox(ty) => { + if let Type::Ident(ident) = &ty.inner { + let reason = TrivialReason::BoxTarget; + insist_extern_types_are_trivial(ident, reason); + } + } + Type::RustVec(ty) => { + if let Type::Ident(ident) = &ty.inner { + let reason = TrivialReason::VecElement; + insist_extern_types_are_trivial(ident, reason); + } + } + _ => {} + } + } + + required_trivial +} + +// Context: +// "type {type} should be trivially move constructible and trivially destructible in C++ to be used as {what} in Rust" +// "needs a cxx::ExternType impl in order to be used as {what}" +pub fn as_what<'a>(name: &'a Pair, reasons: &'a [TrivialReason]) -> impl Display + 'a { + struct Description<'a> { + name: &'a Pair, + reasons: &'a [TrivialReason<'a>], + } + + impl<'a> Display for Description<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut field_of = Set::new(); + let mut argument_of = Set::new(); + let mut return_of = Set::new(); + let mut box_target = false; + let mut vec_element = false; + let mut unpinned_mut = Set::new(); + + for reason in self.reasons { + match reason { + TrivialReason::StructField(strct) => { + field_of.insert(&strct.name.rust); + } + TrivialReason::FunctionArgument(efn) => { + argument_of.insert(&efn.name.rust); + } + TrivialReason::FunctionReturn(efn) => { + return_of.insert(&efn.name.rust); + } + TrivialReason::BoxTarget => box_target = true, + TrivialReason::VecElement => vec_element = true, + TrivialReason::UnpinnedMut(efn) => { + unpinned_mut.insert(&efn.name.rust); + } + } + } + + let mut clauses = Vec::new(); + if !field_of.is_empty() { + clauses.push(Clause::Set { + article: "a", + desc: "field of", + set: &field_of, + }); + } + if !argument_of.is_empty() { + clauses.push(Clause::Set { + article: "an", + desc: "argument of", + set: &argument_of, + }); + } + if !return_of.is_empty() { + clauses.push(Clause::Set { + article: "a", + desc: "return value of", + set: &return_of, + }); + } + if box_target { + clauses.push(Clause::Ty1 { + article: "type", + desc: "Box", + param: self.name, + }); + } + if vec_element { + clauses.push(Clause::Ty1 { + article: "a", + desc: "vector element in Vec", + param: self.name, + }); + } + if !unpinned_mut.is_empty() { + clauses.push(Clause::Set { + article: "a", + desc: "non-pinned mutable reference in signature of", + set: &unpinned_mut, + }); + } + + for (i, clause) in clauses.iter().enumerate() { + if i == 0 { + write!(f, "{} ", clause.article())?; + } else if i + 1 < clauses.len() { + write!(f, ", ")?; + } else { + write!(f, " or ")?; + } + clause.fmt(f)?; + } + + Ok(()) + } + } + + enum Clause<'a> { + Set { + article: &'a str, + desc: &'a str, + set: &'a Set<&'a Ident>, + }, + Ty1 { + article: &'a str, + desc: &'a str, + param: &'a Pair, + }, + } + + impl<'a> Clause<'a> { + fn article(&self) -> &'a str { + match self { + Clause::Set { article, .. } | Clause::Ty1 { article, .. } => article, + } + } + + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Clause::Set { + article: _, + desc, + set, + } => { + write!(f, "{} ", desc)?; + for (i, ident) in set.iter().take(3).enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "`{}`", ident)?; + } + Ok(()) + } + Clause::Ty1 { + article: _, + desc, + param, + } => write!(f, "{}<{}>", desc, param.rust), + } + } + } + + Description { name, reasons } +} diff --git a/syntax/types.rs b/syntax/types.rs index 90a8221f..af7916d7 100644 --- a/syntax/types.rs +++ b/syntax/types.rs @@ -1,67 +1,61 @@ -use crate::syntax::atom::Atom::{self, *}; use crate::syntax::improper::ImproperCtype; +use crate::syntax::instantiate::ImplKey; +use crate::syntax::map::{OrderedMap, UnorderedMap}; use crate::syntax::report::Errors; -use crate::syntax::set::{OrderedSet as Set, UnorderedSet}; +use crate::syntax::resolve::Resolution; +use crate::syntax::set::{OrderedSet, UnorderedSet}; +use crate::syntax::trivial::{self, TrivialReason}; +use crate::syntax::visit::{self, Visit}; use crate::syntax::{ - toposort, Api, Derive, Enum, ExternFn, ExternType, Impl, Pair, ResolvableName, Struct, Type, - TypeAlias, + toposort, Api, Atom, Enum, ExternType, Impl, Lifetimes, Pair, Struct, Type, TypeAlias, }; use proc_macro2::Ident; use quote::ToTokens; -use std::collections::BTreeMap as Map; pub struct Types<'a> { - 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 all: OrderedSet<&'a Type>, + pub structs: UnorderedMap<&'a Ident, &'a Struct>, + pub enums: UnorderedMap<&'a Ident, &'a Enum>, + pub cxx: UnorderedSet<&'a Ident>, + pub rust: UnorderedSet<&'a Ident>, + pub aliases: UnorderedMap<&'a Ident, &'a TypeAlias>, + pub untrusted: UnorderedMap<&'a Ident, &'a ExternType>, + pub required_trivial: UnorderedMap<&'a Ident, Vec<TrivialReason<'a>>>, + pub impls: OrderedMap<ImplKey<'a>, Option<&'a Impl>>, + pub resolutions: UnorderedMap<&'a Ident, Resolution<'a>>, pub struct_improper_ctypes: UnorderedSet<&'a Ident>, pub toposorted_structs: Vec<&'a Struct>, } impl<'a> Types<'a> { pub fn collect(cx: &mut Errors, apis: &'a [Api]) -> Self { - let mut all = Set::new(); - let mut structs = Map::new(); - let mut enums = Map::new(); - 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 mut all = OrderedSet::new(); + let mut structs = UnorderedMap::new(); + let mut enums = UnorderedMap::new(); + let mut cxx = UnorderedSet::new(); + let mut rust = UnorderedSet::new(); + let mut aliases = UnorderedMap::new(); + let mut untrusted = UnorderedMap::new(); + let mut impls = OrderedMap::new(); + let mut resolutions = UnorderedMap::new(); let struct_improper_ctypes = UnorderedSet::new(); let toposorted_structs = Vec::new(); - fn visit<'a>(all: &mut Set<&'a Type>, ty: &'a Type) { - all.insert(ty); - match ty { - Type::Ident(_) | Type::Str(_) | Type::Void(_) | Type::SliceRefU8(_) => {} - Type::RustBox(ty) - | Type::UniquePtr(ty) - | Type::CxxVector(ty) - | Type::RustVec(ty) => visit(all, &ty.inner), - Type::Ref(r) => visit(all, &r.inner), - Type::Slice(s) => visit(all, &s.inner), - Type::Fn(f) => { - if let Some(ret) = &f.ret { - visit(all, ret); - } - for arg in &f.args { - visit(all, &arg.ty); - } + fn visit<'a>(all: &mut OrderedSet<&'a Type>, ty: &'a Type) { + struct CollectTypes<'s, 'a>(&'s mut OrderedSet<&'a Type>); + + impl<'s, 'a> Visit<'a> for CollectTypes<'s, 'a> { + fn visit_type(&mut self, ty: &'a Type) { + self.0.insert(ty); + visit::visit_type(self, ty); } } + + CollectTypes(all).visit_type(ty); } - let mut add_resolution = |pair: &'a Pair| { - resolutions.insert(&pair.rust, pair); + let mut add_resolution = |name: &'a Pair, generics: &'a Lifetimes| { + resolutions.insert(&name.rust, Resolution { name, generics }); }; let mut type_names = UnorderedSet::new(); @@ -91,9 +85,10 @@ impl<'a> Types<'a> { for field in &strct.fields { visit(&mut all, &field.ty); } - add_resolution(&strct.name); + add_resolution(&strct.name, &strct.generics); } Api::Enum(enm) => { + all.insert(&enm.repr_type); let ident = &enm.name.rust; if !type_names.insert(ident) && (!cxx.contains(ident) @@ -106,7 +101,7 @@ impl<'a> Types<'a> { duplicate_name(cx, enm, ident); } enums.insert(ident, enm); - add_resolution(&enm.name); + add_resolution(&enm.name, &enm.generics); } Api::CxxType(ety) => { let ident = &ety.name.rust; @@ -123,7 +118,7 @@ impl<'a> Types<'a> { if !ety.trusted { untrusted.insert(ident, ety); } - add_resolution(&ety.name); + add_resolution(&ety.name, &ety.generics); } Api::RustType(ety) => { let ident = &ety.name.rust; @@ -131,7 +126,7 @@ impl<'a> Types<'a> { duplicate_name(cx, ety, ident); } rust.insert(ident); - add_resolution(&ety.name); + add_resolution(&ety.name, &ety.generics); } Api::CxxFunction(efn) | Api::RustFunction(efn) => { // Note: duplication of the C++ name is fine because C++ has @@ -153,12 +148,34 @@ impl<'a> Types<'a> { } cxx.insert(ident); aliases.insert(ident, alias); - add_resolution(&alias.name); + add_resolution(&alias.name, &alias.generics); } Api::Impl(imp) => { visit(&mut all, &imp.ty); - explicit_impls.insert(imp); + if let Some(key) = imp.ty.impl_key() { + impls.insert(key, Some(imp)); + } + } + } + } + + for ty in &all { + let impl_key = match ty.impl_key() { + Some(impl_key) => impl_key, + None => continue, + }; + let implicit_impl = match impl_key { + ImplKey::RustBox(ident) + | ImplKey::RustVec(ident) + | ImplKey::UniquePtr(ident) + | ImplKey::SharedPtr(ident) + | ImplKey::WeakPtr(ident) + | ImplKey::CxxVector(ident) => { + Atom::from(ident.rust).is_none() && !aliases.contains_key(ident.rust) } + }; + if implicit_impl && !impls.contains_key(&impl_key) { + impls.insert(impl_key, None); } } @@ -166,35 +183,8 @@ impl<'a> Types<'a> { // 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 required_trivial = + trivial::required_trivial_reasons(apis, &all, &structs, &enums, &cxx); let mut types = Types { all, @@ -205,7 +195,7 @@ impl<'a> Types<'a> { aliases, untrusted, required_trivial, - explicit_impls, + impls, resolutions, struct_improper_ctypes, toposorted_structs, @@ -213,7 +203,7 @@ impl<'a> Types<'a> { types.toposorted_structs = toposort::sort(cx, apis, &types); - let mut unresolved_structs: Vec<&Ident> = types.structs.keys().copied().collect(); + let mut unresolved_structs = types.structs.keys(); let mut new_information = true; while new_information { new_information = false; @@ -242,25 +232,10 @@ impl<'a> Types<'a> { pub fn needs_indirect_abi(&self, ty: &Type) -> bool { match ty { - Type::Ident(ident) => { - if let Some(strct) = self.structs.get(&ident.rust) { - !self.is_pod(strct) - } else { - Atom::from(&ident.rust) == Some(RustString) - } - } - Type::RustVec(_) => true, - _ => false, - } - } - - pub fn is_pod(&self, strct: &Struct) -> bool { - for derive in &strct.derives { - if *derive == Derive::Copy { - return true; - } + Type::RustBox(_) | Type::UniquePtr(_) => false, + Type::Array(_) => true, + _ => !self.is_guaranteed_pod(ty), } - false } // Types that trigger rustc's default #[warn(improper_ctypes)] lint, even if @@ -275,12 +250,6 @@ impl<'a> Types<'a> { 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> { @@ -291,13 +260,6 @@ impl<'t, 'a> IntoIterator for &'t Types<'a> { } } -#[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/syntax/visit.rs b/syntax/visit.rs new file mode 100644 index 00000000..2f31378f --- /dev/null +++ b/syntax/visit.rs @@ -0,0 +1,34 @@ +use crate::syntax::Type; + +pub trait Visit<'a> { + fn visit_type(&mut self, ty: &'a Type) { + visit_type(self, ty); + } +} + +pub fn visit_type<'a, V>(visitor: &mut V, ty: &'a Type) +where + V: Visit<'a> + ?Sized, +{ + match ty { + Type::Ident(_) | Type::Str(_) | Type::Void(_) => {} + Type::RustBox(ty) + | Type::UniquePtr(ty) + | Type::SharedPtr(ty) + | Type::WeakPtr(ty) + | Type::CxxVector(ty) + | Type::RustVec(ty) => visitor.visit_type(&ty.inner), + Type::Ref(r) => visitor.visit_type(&r.inner), + Type::Ptr(p) => visitor.visit_type(&p.inner), + Type::Array(a) => visitor.visit_type(&a.inner), + Type::SliceRef(s) => visitor.visit_type(&s.inner), + Type::Fn(fun) => { + if let Some(ret) = &fun.ret { + visitor.visit_type(ret); + } + for arg in &fun.args { + visitor.visit_type(&arg.ty); + } + } + } +} @@ -12,7 +12,7 @@ rust_test( rust_library( name = "ffi", srcs = [ - "ffi/extra.rs", + "ffi/cast.rs", "ffi/lib.rs", "ffi/module.rs", ], @@ -28,11 +28,11 @@ cxx_library( srcs = [ "ffi/tests.cc", ":bridge/source", - ":extra/source", ":module/source", ], headers = { "ffi/lib.rs.h": ":bridge/header", + "ffi/module.rs.h": ":module/header", "ffi/tests.h": "ffi/tests.h", }, deps = ["//:core"], @@ -44,11 +44,6 @@ rust_cxx_bridge( ) rust_cxx_bridge( - name = "extra", - src = "ffi/extra.rs", -) - -rust_cxx_bridge( name = "module", src = "ffi/module.rs", ) diff --git a/tests/BUILD b/tests/BUILD index 57ffab99..7886e4fa 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -15,7 +15,7 @@ rust_test( rust_library( name = "cxx_test_suite", srcs = [ - "ffi/extra.rs", + "ffi/cast.rs", "ffi/lib.rs", "ffi/module.rs", ], @@ -30,12 +30,12 @@ cc_library( srcs = [ "ffi/tests.cc", ":bridge/source", - ":extra/source", ":module/source", ], hdrs = ["ffi/tests.h"], deps = [ ":bridge/include", + ":module/include", "//:core", ], ) @@ -47,12 +47,6 @@ rust_cxx_bridge( ) rust_cxx_bridge( - name = "extra", - src = "ffi/extra.rs", - deps = [":impl"], -) - -rust_cxx_bridge( name = "module", src = "ffi/module.rs", deps = [":impl"], diff --git a/tests/cxx_gen.rs b/tests/cxx_gen.rs index 06d7bc7b..e91675d9 100644 --- a/tests/cxx_gen.rs +++ b/tests/cxx_gen.rs @@ -6,7 +6,7 @@ use std::str; const BRIDGE0: &str = r#" #[cxx::bridge] mod ffi { - extern "C" { + unsafe extern "C++" { pub fn do_cpp_thing(foo: &str); } } @@ -20,7 +20,7 @@ fn test_extern_c_function() { 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)")); + assert!(output.contains("void cxxbridge1$do_cpp_thing(::rust::Str foo)")); } #[test] @@ -30,5 +30,5 @@ fn test_impl_annotation() { 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)")); + assert!(output.contains("ANNOTATION void cxxbridge1$do_cpp_thing(::rust::Str foo)")); } diff --git a/tests/cxx_string.rs b/tests/cxx_string.rs new file mode 100644 index 00000000..be693cd2 --- /dev/null +++ b/tests/cxx_string.rs @@ -0,0 +1,15 @@ +use cxx::{let_cxx_string, CxxString}; + +#[test] +fn test_async_cxx_string() { + async fn f() { + let_cxx_string!(s = "..."); + + async fn g(_: &CxxString) {} + g(&s).await; + } + + // https://github.com/dtolnay/cxx/issues/693 + fn assert_send(_: impl Send) {} + assert_send(f()); +} diff --git a/tests/ffi/build.rs b/tests/ffi/build.rs index 4b2cbdfe..86f8cd3a 100644 --- a/tests/ffi/build.rs +++ b/tests/ffi/build.rs @@ -6,9 +6,13 @@ fn main() { } CFG.include_prefix = "tests/ffi"; - let sources = vec!["lib.rs", "extra.rs", "module.rs"]; - cxx_build::bridges(sources) - .file("tests.cc") - .flag_if_supported(cxxbridge_flags::STD) - .compile("cxx-test-suite"); + let sources = vec!["lib.rs", "module.rs"]; + let mut build = cxx_build::bridges(sources); + build.file("tests.cc"); + build.flag_if_supported(cxxbridge_flags::STD); + build.warnings_into_errors(cfg!(deny_warnings)); + if cfg!(not(target_env = "msvc")) { + build.define("CXX_TEST_INSTANTIATIONS", None); + } + build.compile("cxx-test-suite"); } diff --git a/tests/ffi/cast.rs b/tests/ffi/cast.rs new file mode 100644 index 00000000..30ecb6a8 --- /dev/null +++ b/tests/ffi/cast.rs @@ -0,0 +1,14 @@ +use std::os::raw::c_char; +use std::slice; + +pub fn c_char_to_unsigned(slice: &[c_char]) -> &[u8] { + let ptr = slice.as_ptr().cast::<u8>(); + let len = slice.len(); + unsafe { slice::from_raw_parts(ptr, len) } +} + +pub fn unsigned_to_c_char(slice: &[u8]) -> &[c_char] { + let ptr = slice.as_ptr().cast::<c_char>(); + let len = slice.len(); + unsafe { slice::from_raw_parts(ptr, len) } +} diff --git a/tests/ffi/extra.rs b/tests/ffi/extra.rs deleted file mode 100644 index cd76a7d7..00000000 --- a/tests/ffi/extra.rs +++ /dev/null @@ -1,63 +0,0 @@ -// 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 4c7cbe4d..fcbe1530 100644 --- a/tests/ffi/lib.rs +++ b/tests/ffi/lib.rs @@ -1,87 +1,47 @@ #![allow( clippy::boxed_local, clippy::just_underscores_and_digits, + clippy::let_underscore_drop, + clippy::missing_safety_doc, + clippy::must_use_candidate, + clippy::needless_lifetimes, + clippy::needless_pass_by_value, clippy::ptr_arg, - clippy::trivially_copy_pass_by_ref + clippy::trivially_copy_pass_by_ref, + clippy::unnecessary_wraps, + clippy::unused_self )] -pub mod extra; +pub mod cast; pub mod module; -use cxx::{CxxString, CxxVector, UniquePtr}; +use cxx::{CxxString, CxxVector, SharedPtr, UniquePtr}; use std::fmt::{self, Display}; - -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; - } -} +use std::mem::MaybeUninit; +use std::os::raw::c_char; #[cxx::bridge(namespace = "tests")] pub mod ffi { - #[derive(Clone)] + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] struct Shared { z: usize, } + #[derive(PartialEq, PartialOrd)] struct SharedString { msg: String, } + #[derive(Debug, Hash, PartialOrd, Ord)] enum Enum { AVal, BVal = 2020, - CVal, + #[cxx_name = "CVal"] + LastVal, } #[namespace = "A"] - #[derive(Clone)] + #[derive(Copy, Clone, Default)] struct AShared { z: usize, } @@ -112,11 +72,22 @@ pub mod ffi { } #[namespace = "second"] + #[derive(Hash)] struct Second { i: i32, + e: COwnedEnum, } - extern "C" { + pub struct Array { + a: [i32; 4], + } + + #[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct StructWithLifetime<'a> { + s: &'a str, + } + + unsafe extern "C++" { include!("tests/ffi/tests.h"); type C; @@ -125,10 +96,12 @@ pub mod ffi { fn c_return_shared() -> Shared; fn c_return_box() -> Box<R>; fn c_return_unique_ptr() -> UniquePtr<C>; + fn c_return_shared_ptr() -> SharedPtr<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_slice_char(shared: &Shared) -> &[c_char]; + fn c_return_mutsliceu8(slice: &mut [u8]) -> &mut [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>>; @@ -137,10 +110,10 @@ pub mod ffi { 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_mut_vector(c: Pin<&mut C>) -> Pin<&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_mut_rust_vec(c: Pin<&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; @@ -149,6 +122,8 @@ pub mod ffi { 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_return_const_ptr(n: usize) -> *const C; + fn c_return_mut_ptr(n: usize) -> *mut C; fn c_take_primitive(n: usize); fn c_take_shared(shared: Shared); @@ -156,7 +131,11 @@ pub mod ffi { fn c_take_ref_r(r: &R); fn c_take_ref_c(c: &C); fn c_take_str(s: &str); - fn c_take_sliceu8(s: &[u8]); + fn c_take_slice_char(s: &[c_char]); + fn c_take_slice_shared(s: &[Shared]); + fn c_take_slice_shared_sort(s: &mut [Shared]); + fn c_take_slice_r(s: &[R]); + fn c_take_slice_r_sort(s: &mut [R]); fn c_take_rust_string(s: String); fn c_take_unique_ptr_string(s: UniquePtr<CxxString>); fn c_take_unique_ptr_vector_u8(v: UniquePtr<CxxVector<u8>>); @@ -171,6 +150,7 @@ pub mod ffi { 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_rust_vec_shared_sort(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>); @@ -184,6 +164,8 @@ pub mod ffi { 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>); + unsafe fn c_take_const_ptr(c: *const C) -> usize; + unsafe fn c_take_mut_ptr(c: *mut C) -> usize; fn c_try_return_void() -> Result<()>; fn c_try_return_primitive() -> Result<usize>; @@ -192,6 +174,7 @@ pub mod ffi { fn c_try_return_ref(s: &String) -> Result<&String>; fn c_try_return_str(s: &str) -> Result<&str>; fn c_try_return_sliceu8(s: &[u8]) -> Result<&[u8]>; + fn c_try_return_mutsliceu8(s: &mut [u8]) -> Result<&mut [u8]>; 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>>; @@ -199,12 +182,18 @@ pub mod ffi { fn c_try_return_ref_rust_vec(c: &C) -> Result<&Vec<u8>>; fn get(self: &C) -> usize; - fn set(self: &mut C, n: usize) -> usize; + fn set(self: Pin<&mut C>, n: usize) -> usize; fn get2(&self) -> usize; - fn set2(&mut self, n: usize) -> usize; - fn set_succeed(&mut self, n: usize) -> Result<usize>; - fn get_fail(&mut self) -> Result<usize>; + fn getRef(self: &C) -> &usize; + fn getMut(self: Pin<&mut C>) -> &mut usize; + fn set_succeed(self: Pin<&mut C>, n: usize) -> Result<usize>; + fn get_fail(self: Pin<&mut C>) -> Result<usize>; fn c_method_on_shared(self: &Shared) -> usize; + fn c_method_ref_on_shared(self: &Shared) -> &usize; + fn c_method_mut_on_shared(self: &mut Shared) -> &mut usize; + fn c_set_array(self: &mut Array, value: i32); + + fn c_get_use_count(weak: &WeakPtr<C>) -> usize; #[rust_name = "i32_overloaded_method"] fn cOverloadedMethod(&self, x: i32) -> String; @@ -219,31 +208,57 @@ pub mod ffi { fn ns_c_take_ns_shared(shared: AShared); } - extern "C" { + extern "C++" { + include!("tests/ffi/module.rs.h"); + type COwnedEnum; + type Job = crate::module::ffi::Job; + } + + extern "Rust" { + #[derive(ExternType)] + type Reference<'a>; + } + + unsafe extern "C++" { + type Borrow<'a>; + + fn c_return_borrow<'a>(s: &'a CxxString) -> UniquePtr<Borrow<'a>>; + + #[rust_name = "c_return_borrow_elided"] + fn c_return_borrow(s: &CxxString) -> UniquePtr<Borrow>; + + fn const_member(self: &Borrow); + fn nonconst_member(self: Pin<&mut Borrow>); } #[repr(u32)] + #[derive(Hash)] enum COwnedEnum { + #[cxx_name = "CVAL1"] CVal1, + #[cxx_name = "CVAL2"] CVal2, } extern "Rust" { type R; - type R2; fn r_return_primitive() -> usize; fn r_return_shared() -> Shared; fn r_return_box() -> Box<R>; fn r_return_unique_ptr() -> UniquePtr<C>; + fn r_return_shared_ptr() -> SharedPtr<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_sliceu8(shared: &Shared) -> &[u8]; + fn r_return_mutsliceu8(slice: &mut [u8]) -> &mut [u8]; 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_rust_vec_extern_struct() -> Vec<Job>; 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; @@ -254,10 +269,11 @@ pub mod ffi { fn r_take_shared(shared: Shared); fn r_take_box(r: Box<R>); fn r_take_unique_ptr(c: UniquePtr<C>); + fn r_take_shared_ptr(c: SharedPtr<C>); fn r_take_ref_r(r: &R); fn r_take_ref_c(c: &C); fn r_take_str(s: &str); - fn r_take_sliceu8(s: &[u8]); + fn r_take_slice_char(s: &[c_char]); fn r_take_rust_string(s: String); fn r_take_unique_ptr_string(s: UniquePtr<CxxString>); fn r_take_ref_vector(v: &CxxVector<u8>); @@ -272,11 +288,13 @@ pub mod ffi { fn r_try_return_primitive() -> Result<usize>; fn r_try_return_box() -> Result<Box<R>>; fn r_fail_return_primitive() -> Result<usize>; + fn r_try_return_sliceu8(s: &[u8]) -> Result<&[u8]>; + fn r_try_return_mutsliceu8(s: &mut [u8]) -> Result<&mut [u8]>; - fn r_return_r2(n: usize) -> Box<R2>; - fn get(self: &R2) -> usize; - fn set(self: &mut R2, n: usize) -> usize; + fn get(self: &R) -> usize; + fn set(self: &mut R, n: usize) -> usize; fn r_method_on_shared(self: &Shared) -> String; + fn r_get_array_sum(self: &Array) -> i32; #[cxx_name = "rAliasedFunction"] fn r_aliased_function(x: i32) -> String; @@ -302,13 +320,66 @@ pub mod ffi { struct Dag4 { dag0: Dag0, } + + impl Box<Shared> {} } -pub type R = usize; +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; + } +} -pub struct R2(usize); +#[derive(PartialEq, Debug)] +pub struct R(pub usize); -impl R2 { +impl R { fn get(&self) -> usize { self.0 } @@ -319,12 +390,20 @@ impl R2 { } } +pub struct Reference<'a>(&'a String); + impl ffi::Shared { fn r_method_on_shared(&self) -> String { "2020".to_owned() } } +impl ffi::Array { + pub fn r_get_array_sum(&self) -> i32 { + self.a.iter().sum() + } +} + #[derive(Debug)] struct Error; @@ -345,7 +424,7 @@ fn r_return_shared() -> ffi::Shared { } fn r_return_box() -> Box<R> { - Box::new(2020) + Box::new(R(2020)) } fn r_return_unique_ptr() -> UniquePtr<ffi::C> { @@ -355,6 +434,18 @@ fn r_return_unique_ptr() -> UniquePtr<ffi::C> { unsafe { UniquePtr::from_raw(cxx_test_suite_get_unique_ptr()) } } +fn r_return_shared_ptr() -> SharedPtr<ffi::C> { + extern "C" { + fn cxx_test_suite_get_shared_ptr(repr: *mut SharedPtr<ffi::C>); + } + let mut shared_ptr = MaybeUninit::<SharedPtr<ffi::C>>::uninit(); + let repr = shared_ptr.as_mut_ptr(); + unsafe { + cxx_test_suite_get_shared_ptr(repr); + shared_ptr.assume_init() + } +} + fn r_return_ref(shared: &ffi::Shared) -> &usize { &shared.z } @@ -368,6 +459,15 @@ fn r_return_str(shared: &ffi::Shared) -> &str { "2020" } +fn r_return_sliceu8(shared: &ffi::Shared) -> &[u8] { + let _ = shared; + b"2020" +} + +fn r_return_mutsliceu8(slice: &mut [u8]) -> &mut [u8] { + slice +} + fn r_return_rust_string() -> String { "2020".to_owned() } @@ -387,6 +487,10 @@ fn r_return_rust_vec_string() -> Vec<String> { Vec::new() } +fn r_return_rust_vec_extern_struct() -> Vec<ffi::Job> { + Vec::new() +} + fn r_return_ref_rust_vec(shared: &ffi::Shared) -> &Vec<u8> { let _ = shared; unimplemented!() @@ -411,7 +515,7 @@ fn r_return_enum(n: u32) -> ffi::Enum { } else if n <= 2020 { ffi::Enum::BVal } else { - ffi::Enum::CVal + ffi::Enum::LastVal } } @@ -431,6 +535,10 @@ fn r_take_unique_ptr(c: UniquePtr<ffi::C>) { let _ = c; } +fn r_take_shared_ptr(c: SharedPtr<ffi::C>) { + let _ = c; +} + fn r_take_ref_r(r: &R) { let _ = r; } @@ -447,8 +555,9 @@ fn r_take_rust_string(s: String) { assert_eq!(s, "2020"); } -fn r_take_sliceu8(s: &[u8]) { +fn r_take_slice_char(s: &[c_char]) { assert_eq!(s.len(), 5); + let s = cast::c_char_to_unsigned(s); assert_eq!(std::str::from_utf8(s).unwrap(), "2020\0"); } @@ -495,15 +604,19 @@ fn r_try_return_primitive() -> Result<usize, Error> { } fn r_try_return_box() -> Result<Box<R>, Error> { - Ok(Box::new(2020)) + Ok(Box::new(R(2020))) } fn r_fail_return_primitive() -> Result<usize, Error> { Err(Error) } -fn r_return_r2(n: usize) -> Box<R2> { - Box::new(R2(n)) +fn r_try_return_sliceu8(slice: &[u8]) -> Result<&[u8], Error> { + Ok(slice) +} + +fn r_try_return_mutsliceu8(slice: &mut [u8]) -> Result<&mut [u8], Error> { + Ok(slice) } fn r_aliased_function(x: i32) -> String { diff --git a/tests/ffi/module.rs b/tests/ffi/module.rs index 899d45b0..21a86206 100644 --- a/tests/ffi/module.rs +++ b/tests/ffi/module.rs @@ -1,13 +1,78 @@ -// Rustfmt mangles the extern type alias. -// https://github.com/rust-lang/rustfmt/issues/4159 -#[rustfmt::skip] #[cxx::bridge(namespace = "tests")] pub mod ffi { - extern "C" { + struct Job { + raw: u32, + } + + unsafe extern "C++" { include!("tests/ffi/tests.h"); type C = crate::ffi::C; fn c_take_unique_ptr(c: UniquePtr<C>); } + + impl Vec<Job> {} +} + +#[cxx::bridge(namespace = "tests")] +pub mod ffi2 { + unsafe 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_mut_ref(d: &mut D); + fn c_take_trivial_pin_ref(d: Pin<&D>); + fn c_take_trivial_pin_mut_ref(d: Pin<&mut D>); + fn c_take_trivial_ref_method(self: &D); + fn c_take_trivial_mut_ref_method(self: &mut 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_ref_method(self: &E); + fn c_take_opaque_mut_ref_method(self: Pin<&mut 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_opaque_mut_pin(e: Pin<&mut E>) -> Pin<&mut 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>; + } + + impl UniquePtr<D> {} + impl UniquePtr<E> {} + impl UniquePtr<F> {} + impl UniquePtr<G> {} } diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc index 2305766c..ba12ed45 100644 --- a/tests/ffi/tests.cc +++ b/tests/ffi/tests.cc @@ -1,11 +1,13 @@ #include "tests/ffi/tests.h" #include "tests/ffi/lib.rs.h" +#include <cstdlib> #include <cstring> #include <iterator> #include <memory> #include <numeric> #include <stdexcept> #include <string> +#include <tuple> extern "C" void cxx_test_suite_set_correct() noexcept; extern "C" tests::R *cxx_test_suite_get_box() noexcept; @@ -21,22 +23,31 @@ size_t C::get() const { return this->n; } size_t C::get2() const { return this->n; } -size_t C::set(size_t n) { - this->n = n; - return this->n; -} +const size_t &C::getRef() const { return this->n; } + +size_t &C::getMut() { return this->n; } -size_t C::set2(size_t n) { +size_t C::set(size_t n) { this->n = n; return this->n; } -size_t C::set_succeed(size_t n) { return this->set2(n); } +size_t C::set_succeed(size_t n) { return this->set(n); } size_t C::get_fail() { throw std::runtime_error("unimplemented"); } size_t Shared::c_method_on_shared() const noexcept { return 2021; } +const size_t &Shared::c_method_ref_on_shared() const noexcept { + return this->z; +} + +size_t &Shared::c_method_mut_on_shared() noexcept { return this->z; } + +void Array::c_set_array(int32_t val) noexcept { + this->a = {val, val, val, val}; +} + const std::vector<uint8_t> &C::get_v() const { return this->v; } std::vector<uint8_t> &C::get_v() { return this->v; } @@ -50,6 +61,14 @@ Shared c_return_shared() { return Shared{2020}; } ::A::B::ABShared c_return_nested_ns_shared() { return ::A::B::ABShared{2020}; } rust::Box<R> c_return_box() { + Shared shared{0}; + rust::Box<Shared> box{shared}; // explicit constructor from const T& + rust::Box<Shared> other{std::move(shared)}; // explicit constructor from T&& + box = std::move(other); // move assignment + rust::Box<Shared> box2(*box); // copy from another Box + rust::Box<Shared> other2(std::move(other)); // move constructor + rust::Box<Shared>::in_place(shared.z); // placement static factory + rust::Box<Shared>::in_place<size_t>(0); return rust::Box<R>::from_raw(cxx_test_suite_get_box()); } @@ -57,6 +76,10 @@ std::unique_ptr<C> c_return_unique_ptr() { return std::unique_ptr<C>(new C{2020}); } +std::shared_ptr<C> c_return_shared_ptr() { + return std::shared_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"}); } @@ -76,10 +99,13 @@ rust::Str c_return_str(const Shared &shared) { return "2020"; } -rust::Slice<uint8_t> c_return_sliceu8(const Shared &shared) { +rust::Slice<const char> c_return_slice_char(const Shared &shared) { (void)shared; - return rust::Slice<uint8_t>(reinterpret_cast<const uint8_t *>(SLICE_DATA), - sizeof(SLICE_DATA)); + return rust::Slice<const char>(SLICE_DATA, sizeof(SLICE_DATA)); +} + +rust::Slice<uint8_t> c_return_mutsliceu8(rust::Slice<uint8_t> slice) { + return slice; } rust::String c_return_rust_string() { return "2020"; } @@ -129,7 +155,8 @@ const std::vector<uint8_t> &c_return_ref_vector(const C &c) { 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"); + rust::Vec<uint8_t> vec{2, 0, 2, 0}; + return vec; } const rust::Vec<uint8_t> &c_return_ref_rust_vec(const C &c) { @@ -143,7 +170,7 @@ rust::Vec<uint8_t> &c_return_mut_rust_vec(C &c) { } rust::Vec<rust::String> c_return_rust_vec_string() { - throw std::runtime_error("unimplemented"); + return {"2", "0", "2", "0"}; } size_t c_return_identity(size_t n) { return n; } @@ -180,6 +207,20 @@ Enum c_return_enum(uint16_t n) { } } +const C *c_return_const_ptr(size_t c) { return new C(c); } + +C *c_return_mut_ptr(size_t c) { return new C(c); } + +Borrow::Borrow(const std::string &s) : s(s) {} + +void Borrow::const_member() const {} + +void Borrow::nonconst_member() {} + +std::unique_ptr<Borrow> c_return_borrow(const std::string &s) { + return std::unique_ptr<Borrow>(new Borrow(s)); +} + void c_take_primitive(size_t n) { if (n == 2020) { cxx_test_suite_set_correct(); @@ -240,9 +281,44 @@ void c_take_str(rust::Str s) { } } -void c_take_sliceu8(rust::Slice<uint8_t> s) { - if (std::string(reinterpret_cast<const char *>(s.data()), s.size()) == - "2020") { +void c_take_slice_char(rust::Slice<const char> s) { + if (std::string(s.data(), s.size()) == "2020") { + cxx_test_suite_set_correct(); + } +} + +void c_take_slice_shared(rust::Slice<const Shared> s) { + if (s.size() == 2 && s.data()->z == 2020 && s[1].z == 2021 && + s.at(1).z == 2021 && s.front().z == 2020 && s.back().z == 2021) { + cxx_test_suite_set_correct(); + } +} + +void c_take_slice_shared_sort(rust::Slice<Shared> s) { + // Exercise requirements of RandomAccessIterator. + // https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator + std::sort(s.begin(), s.end()); + if (s[0].z == 0 && s[1].z == 2 && s[2].z == 4 && s[3].z == 7) { + cxx_test_suite_set_correct(); + } +} + +void c_take_slice_r(rust::Slice<const R> s) { + if (s.size() == 3 && s[0].get() == 2020 && s[1].get() == 2050) { + cxx_test_suite_set_correct(); + } +} + +bool operator<(const R &a, const R &b) noexcept { return a.get() < b.get(); } + +void c_take_slice_r_sort(rust::Slice<R> s) { + std::qsort(s.data(), s.size(), rust::size_of<decltype(s)::value_type>(), + [](const void *fst, const void *snd) { + auto &a = *static_cast<const R *>(fst); + auto &b = *static_cast<const R *>(snd); + return a < b ? -1 : b < a ? 1 : 0; + }); + if (s[0].get() == 2020 && s[1].get() == 2021 && s[2].get() == 2050) { cxx_test_suite_set_correct(); } } @@ -340,11 +416,23 @@ void c_take_rust_vec_string(rust::Vec<rust::String> v) { 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 - uint32_t sum = 0; + uint32_t sum = 0, csum = 0; for (auto it = v.begin(), it_end = v.end(); it != it_end; it++) { sum += it->z; } - if (sum == 2021) { + for (auto it = v.cbegin(), it_end = v.cend(); it != it_end; it++) { + csum += it->z; + } + if (sum == 2021 && csum == 2021) { + cxx_test_suite_set_correct(); + } +} + +void c_take_rust_vec_shared_sort(rust::Vec<Shared> v) { + // Exercise requirements of RandomAccessIterator. + // https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator + std::sort(v.begin(), v.end()); + if (v[0].z == 0 && v[1].z == 2 && v[2].z == 4 && v[3].z == 7) { cxx_test_suite_set_correct(); } } @@ -424,6 +512,14 @@ void c_take_nested_ns_enum(::A::B::ABEnum e) { } } +size_t c_take_const_ptr(const C *c) { return c->get(); } + +size_t c_take_mut_ptr(C *c) { + size_t result = c->get(); + delete c; + return result; +} + void c_try_return_void() {} size_t c_try_return_primitive() { return 2020; } @@ -436,7 +532,13 @@ const rust::String &c_try_return_ref(const rust::String &s) { return s; } rust::Str c_try_return_str(rust::Str s) { return s; } -rust::Slice<uint8_t> c_try_return_sliceu8(rust::Slice<uint8_t> s) { return s; } +rust::Slice<const uint8_t> c_try_return_sliceu8(rust::Slice<const uint8_t> s) { + return s; +} + +rust::Slice<uint8_t> c_try_return_mutsliceu8(rust::Slice<uint8_t> s) { + return s; +} rust::String c_try_return_rust_string() { return c_return_rust_string(); } @@ -457,10 +559,19 @@ const rust::Vec<uint8_t> &c_try_return_ref_rust_vec(const C &c) { throw std::runtime_error("unimplemented"); } +size_t c_get_use_count(const std::weak_ptr<C> &weak) noexcept { + return weak.use_count(); +} + extern "C" C *cxx_test_suite_get_unique_ptr() noexcept { return std::unique_ptr<C>(new C{2020}).release(); } +extern "C" void +cxx_test_suite_get_shared_ptr(std::shared_ptr<C> *repr) noexcept { + new (repr) std::shared_ptr<C>(new C{2020}); +} + extern "C" std::string *cxx_test_suite_get_unique_ptr_string() noexcept { return std::unique_ptr<std::string>(new std::string("2020")).release(); } @@ -493,6 +604,24 @@ void c_take_trivial_ref(const D &d) { } } +void c_take_trivial_mut_ref(D &d) { (void)d; } + +void c_take_trivial_pin_ref(const D &d) { (void)d; } + +void c_take_trivial_pin_mut_ref(D &d) { (void)d; } + +void D::c_take_trivial_ref_method() const { + if (d == 30) { + cxx_test_suite_set_correct(); + } +} + +void D::c_take_trivial_mut_ref_method() { + if (d == 30) { + cxx_test_suite_set_correct(); + } +} + void c_take_trivial(D d) { if (d.d == 30) { cxx_test_suite_set_correct(); @@ -535,6 +664,18 @@ void c_take_opaque_ref(const E &e) { } } +void E::c_take_opaque_ref_method() const { + if (e == 40 && e_str == "hello") { + cxx_test_suite_set_correct(); + } +} + +void E::c_take_opaque_mut_ref_method() { + if (e == 40 && 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(); @@ -572,6 +713,8 @@ std::unique_ptr<E> c_return_opaque_ptr() { return e; } +E &c_return_opaque_mut_pin(E &e) { 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; @@ -589,10 +732,16 @@ extern "C" const char *cxx_run_test() noexcept { } \ } while (false) + ASSERT(rust::size_of<R>() == sizeof(size_t)); + ASSERT(rust::align_of<R>() == alignof(size_t)); + ASSERT(rust::size_of<size_t>() == sizeof(size_t)); + ASSERT(rust::align_of<size_t>() == alignof(size_t)); + ASSERT(r_return_primitive() == 2020); ASSERT(r_return_shared().z == 2020); ASSERT(cxx_test_suite_r_is_correct(&*r_return_box())); ASSERT(r_return_unique_ptr()->get() == 2020); + ASSERT(r_return_shared_ptr()->get() == 2020); ASSERT(r_return_ref(Shared{2020}) == 2020); ASSERT(std::string(r_return_str(Shared{2020})) == "2020"); ASSERT(std::string(r_return_rust_string()) == "2020"); @@ -606,10 +755,10 @@ extern "C" const char *cxx_run_test() noexcept { r_take_primitive(2020); r_take_shared(Shared{2020}); r_take_unique_ptr(std::unique_ptr<C>(new C{2020})); + r_take_shared_ptr(std::shared_ptr<C>(new C{2020})); r_take_ref_c(C{2020}); r_take_str(rust::Str("2020")); - r_take_sliceu8(rust::Slice<uint8_t>( - reinterpret_cast<const uint8_t *>(SLICE_DATA), sizeof(SLICE_DATA))); + r_take_slice_char(rust::Slice<const char>(SLICE_DATA, sizeof(SLICE_DATA))); r_take_rust_string(rust::String("2020")); r_take_unique_ptr_string( std::unique_ptr<std::string>(new std::string("2020"))); @@ -628,16 +777,74 @@ extern "C" const char *cxx_run_test() noexcept { ASSERT(std::strcmp(e.what(), "rust error") == 0); } - auto r2 = r_return_r2(2020); - ASSERT(r2->get() == 2020); - ASSERT(r2->set(2021) == 2021); + auto r = r_return_box(); + ASSERT(r->get() == 2020); + ASSERT(r->set(2021) == 2021); + ASSERT(r->get() == 2021); + + using std::swap; + auto r2 = r_return_box(); + swap(r, r2); + ASSERT(r->get() == 2020); 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"); + ASSERT(Shared{1} == Shared{1}); + ASSERT(Shared{1} != Shared{2}); + + rust::String first = "first", second = "second", sec = "sec"; + bool (rust::String::*cmp)(const rust::String &) const; + bool first_first, first_second, sec_second, second_sec; + for (auto test : { + std::tuple<decltype(cmp), bool, bool, bool, bool>{ + &rust::String::operator==, true, false, false, false}, + {&rust::String::operator!=, false, true, true, true}, + {&rust::String::operator<, false, true, true, false}, + {&rust::String::operator<=, true, true, true, false}, + {&rust::String::operator>, false, false, false, true}, + {&rust::String::operator>=, true, false, false, true}, + }) { + std::tie(cmp, first_first, first_second, sec_second, second_sec) = test; + ASSERT((first.*cmp)(first) == first_first); + ASSERT((first.*cmp)(second) == first_second); + ASSERT((sec.*cmp)(second) == sec_second); + ASSERT((second.*cmp)(sec) == second_sec); + } + + rust::String cstring = "test"; + ASSERT(cstring.length() == 4); + ASSERT(strncmp(cstring.data(), "test", 4) == 0); + ASSERT(strncmp(cstring.c_str(), "test", 5) == 0); + ASSERT(cstring.length() == 4); + + rust::String other_cstring = "foo"; + swap(cstring, other_cstring); + ASSERT(cstring == "foo"); + ASSERT(other_cstring == "test"); + + rust::Str cstr = "test"; + rust::Str other_cstr = "foo"; + swap(cstr, other_cstr); + ASSERT(cstr == "foo"); + ASSERT(other_cstr == "test"); + + rust::Vec<int> vec1{1, 2}; + rust::Vec<int> vec2{3, 4}; + swap(vec1, vec2); + ASSERT(vec1[0] == 3 && vec1[1] == 4); + ASSERT(vec2[0] == 1 && vec2[1] == 2); + + // Test Vec<usize> and Vec<isize>. These are weird because on Linux and + // Windows size_t is exactly the same C++ type as one of the sized integer + // types (typically uint64_t, both of which are defined as unsigned long), + // while on macOS it is a distinct type. + // https://github.com/dtolnay/cxx/issues/705 + (void)rust::Vec<size_t>(); + (void)rust::Vec<rust::isize>(); + cxx_test_suite_set_correct(); return nullptr; } @@ -671,3 +878,25 @@ std::unique_ptr<I> ns_c_return_unique_ptr_ns() { return std::unique_ptr<I>(new I()); } } // namespace I + +// Instantiate any remaining class member functions not already covered above. +// This is an easy way to at least typecheck anything missed by unit tests. +// https://en.cppreference.com/w/cpp/language/class_template#Explicit_instantiation +// > When an explicit instantiation names a class template specialization, it +// > serves as an explicit instantiation of the same kind (declaration or +// > definition) of each of its non-inherited non-template members that has not +// > been previously explicitly specialized in the translation unit. +#if defined(CXX_TEST_INSTANTIATIONS) +template class rust::Box<tests::Shared>; +template class rust::Slice<const char>; +template class rust::Slice<const uint8_t>; +template class rust::Slice<uint8_t>; +template class rust::Slice<const tests::Shared>; +template class rust::Slice<tests::Shared>; +template class rust::Slice<const tests::R>; +template class rust::Slice<tests::R>; +template class rust::Vec<uint8_t>; +template class rust::Vec<rust::String>; +template class rust::Vec<tests::Shared>; +template class rust::Fn<size_t(rust::String)>; +#endif diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h index 2217c361..74acbf4f 100644 --- a/tests/ffi/tests.h +++ b/tests/ffi/tests.h @@ -45,7 +45,8 @@ public: size_t get() const; size_t set(size_t n); size_t get2() const; - size_t set2(size_t n); + const size_t &getRef() const; + size_t &getMut(); size_t set_succeed(size_t n); size_t get_fail(); const std::vector<uint8_t> &get_v() const; @@ -60,16 +61,27 @@ private: struct D { uint64_t d; + void c_take_trivial_ref_method() const; + void c_take_trivial_mut_ref_method(); }; struct E { uint64_t e; std::string e_str; + void c_take_opaque_ref_method() const; + void c_take_opaque_mut_ref_method(); }; enum COwnedEnum { - CVal1, - CVal2, + CVAL1, + CVAL2, +}; + +struct Borrow { + Borrow(const std::string &s); + void const_member() const; + void nonconst_member(); + const std::string &s; }; size_t c_return_primitive(); @@ -78,13 +90,15 @@ Shared c_return_shared(); ::A::B::ABShared c_return_nested_ns_shared(); rust::Box<R> c_return_box(); std::unique_ptr<C> c_return_unique_ptr(); +std::shared_ptr<C> c_return_shared_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::Slice<const char> c_return_slice_char(const Shared &shared); +rust::Slice<uint8_t> c_return_mutsliceu8(rust::Slice<uint8_t> slice); 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(); @@ -103,6 +117,9 @@ 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); +std::unique_ptr<Borrow> c_return_borrow(const std::string &s); +const C *c_return_const_ptr(size_t n); +C *c_return_mut_ptr(size_t n); void c_take_primitive(size_t n); void c_take_shared(Shared shared); @@ -114,7 +131,11 @@ 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_slice_char(rust::Slice<const char> s); +void c_take_slice_shared(rust::Slice<const Shared> s); +void c_take_slice_shared_sort(rust::Slice<Shared> s); +void c_take_slice_r(rust::Slice<const R> s); +void c_take_slice_r_sort(rust::Slice<R> 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); @@ -132,6 +153,7 @@ 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_rust_vec_shared_sort(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); @@ -141,6 +163,8 @@ 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); +size_t c_take_const_ptr(const C *c); +size_t c_take_mut_ptr(C *c); void c_try_return_void(); size_t c_try_return_primitive(); @@ -148,15 +172,21 @@ size_t c_fail_return_primitive(); rust::Box<R> c_try_return_box(); const rust::String &c_try_return_ref(const rust::String &); rust::Str c_try_return_str(rust::Str); -rust::Slice<uint8_t> c_try_return_sliceu8(rust::Slice<uint8_t>); +rust::Slice<const uint8_t> c_try_return_sliceu8(rust::Slice<const uint8_t>); +rust::Slice<uint8_t> c_try_return_mutsliceu8(rust::Slice<uint8_t>); 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); +size_t c_get_use_count(const std::weak_ptr<C> &weak) noexcept; + void c_take_trivial_ptr(std::unique_ptr<D> d); void c_take_trivial_ref(const D &d); +void c_take_trivial_mut_ref(D &d); +void c_take_trivial_pin_ref(const D &d); +void c_take_trivial_pin_mut_ref(D &d); void c_take_trivial(D d); void c_take_trivial_ns_ptr(std::unique_ptr<::G::G> g); @@ -171,6 +201,7 @@ 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(); +E &c_return_opaque_mut_pin(E &e); std::unique_ptr<::F::F> c_return_ns_opaque_ptr(); rust::String cOverloadedFunction(int32_t x); diff --git a/tests/test.rs b/tests/test.rs index 2bae4d85..32b8e832 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,7 +1,16 @@ -#![allow(clippy::assertions_on_constants, clippy::float_cmp, clippy::unit_cmp)] +#![allow( + clippy::assertions_on_constants, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::float_cmp, + clippy::needless_pass_by_value, + clippy::unit_cmp, + clippy::unseparated_literal_suffix +)] -use cxx_test_suite::extra::ffi2; -use cxx_test_suite::ffi; +use cxx::SharedPtr; +use cxx_test_suite::module::ffi2; +use cxx_test_suite::{cast, ffi, R}; use std::cell::Cell; use std::ffi::CStr; @@ -18,7 +27,7 @@ macro_rules! check { ($run:expr) => {{ CORRECT.with(|correct| correct.set(false)); $run; - assert!(CORRECT.with(|correct| correct.get()), stringify!($run)); + assert!(CORRECT.with(Cell::get), "{}", stringify!($run)); }}; } @@ -30,14 +39,17 @@ fn test_c_return() { assert_eq!(2020, ffi::c_return_primitive()); assert_eq!(2020, ffi::c_return_shared().z); - assert_eq!(2020, *ffi::c_return_box()); + assert_eq!(2020, ffi::c_return_box().0); 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!( + b"2020\0", + cast::c_char_to_unsigned(ffi::c_return_slice_char(&shared)), + ); assert_eq!("2020", ffi::c_return_rust_string()); assert_eq!("2020", ffi::c_return_unique_ptr_string().to_str().unwrap()); assert_eq!(4, ffi::c_return_unique_ptr_vector_u8().len()); @@ -68,7 +80,7 @@ fn test_c_return() { _ => assert!(false), } match ffi::c_return_enum(2021) { - enm @ ffi::Enum::CVal => assert_eq!(2021, enm.repr), + enm @ ffi::Enum::LastVal => assert_eq!(2021, enm.repr), _ => assert!(false), } match ffi::c_return_ns_enum(0) { @@ -89,7 +101,7 @@ fn test_c_try_return() { "logic error", ffi::c_fail_return_primitive().unwrap_err().what(), ); - assert_eq!(2020, *ffi::c_try_return_box().unwrap()); + assert_eq!(2020, ffi::c_try_return_box().unwrap().0); assert_eq!("2020", *ffi::c_try_return_ref(&"2020".to_owned()).unwrap()); assert_eq!("2020", ffi::c_try_return_str("2020").unwrap()); assert_eq!(b"2020", ffi::c_try_return_sliceu8(b"2020").unwrap()); @@ -107,12 +119,33 @@ fn test_c_take() { 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_box(Box::new(R(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")); + check!(ffi::c_take_slice_char(cast::unsigned_to_c_char(b"2020"))); + check!(ffi::c_take_slice_shared(&[ + ffi::Shared { z: 2020 }, + ffi::Shared { z: 2021 }, + ])); + let shared_sort_slice = &mut [ + ffi::Shared { z: 2 }, + ffi::Shared { z: 0 }, + ffi::Shared { z: 7 }, + ffi::Shared { z: 4 }, + ]; + check!(ffi::c_take_slice_shared_sort(shared_sort_slice)); + assert_eq!(shared_sort_slice[0].z, 0); + assert_eq!(shared_sort_slice[1].z, 2); + assert_eq!(shared_sort_slice[2].z, 4); + assert_eq!(shared_sort_slice[3].z, 7); + let r_sort_slice = &mut [R(2020), R(2050), R(2021)]; + check!(ffi::c_take_slice_r(r_sort_slice)); + check!(ffi::c_take_slice_r_sort(r_sort_slice)); + assert_eq!(r_sort_slice[0].0, 2020); + assert_eq!(r_sort_slice[1].0, 2021); + assert_eq!(r_sort_slice[2].0, 2050); check!(ffi::c_take_rust_string("2020".to_owned())); check!(ffi::c_take_unique_ptr_string( ffi::c_return_unique_ptr_string() @@ -137,6 +170,13 @@ fn test_c_take() { check!(ffi::c_take_rust_vec_shared_forward_iterator( shared_test_vec, )); + let shared_sort_vec = vec![ + ffi::Shared { z: 2 }, + ffi::Shared { z: 0 }, + ffi::Shared { z: 7 }, + ffi::Shared { z: 4 }, + ]; + check!(ffi::c_take_rust_vec_shared_sort(shared_sort_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)); @@ -188,13 +228,35 @@ fn test_c_method_calls() { let old_value = unique_ptr.get(); assert_eq!(2020, old_value); - assert_eq!(2021, unique_ptr.set(2021)); + assert_eq!(2021, unique_ptr.pin_mut().set(2021)); assert_eq!(2021, unique_ptr.get()); - assert_eq!(old_value, unique_ptr.set2(old_value)); - 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, unique_ptr.get2()); + assert_eq!(2021, *unique_ptr.getRef()); + assert_eq!(2021, *unique_ptr.pin_mut().getMut()); + assert_eq!(2022, unique_ptr.pin_mut().set_succeed(2022).unwrap()); + assert!(unique_ptr.pin_mut().get_fail().is_err()); assert_eq!(2021, ffi::Shared { z: 0 }.c_method_on_shared()); + assert_eq!(2022, *ffi::Shared { z: 2022 }.c_method_ref_on_shared()); + assert_eq!(2023, *ffi::Shared { z: 2023 }.c_method_mut_on_shared()); + + let val = 42; + let mut array = ffi::Array { a: [0, 0, 0, 0] }; + array.c_set_array(val); + assert_eq!(array.a.len() as i32 * val, array.r_get_array_sum()); +} + +#[test] +fn test_shared_ptr_weak_ptr() { + let shared_ptr = ffi::c_return_shared_ptr(); + let weak_ptr = SharedPtr::downgrade(&shared_ptr); + assert_eq!(1, ffi::c_get_use_count(&weak_ptr)); + + assert!(!weak_ptr.upgrade().is_null()); + assert_eq!(1, ffi::c_get_use_count(&weak_ptr)); + + drop(shared_ptr); + assert_eq!(0, ffi::c_get_use_count(&weak_ptr)); + assert!(weak_ptr.upgrade().is_null()); } #[test] @@ -209,17 +271,24 @@ fn test_c_ns_method_calls() { fn test_enum_representations() { assert_eq!(0, ffi::Enum::AVal.repr); assert_eq!(2020, ffi::Enum::BVal.repr); - assert_eq!(2021, ffi::Enum::CVal.repr); + assert_eq!(2021, ffi::Enum::LastVal.repr); +} + +#[test] +fn test_debug() { + assert_eq!("Shared { z: 1 }", format!("{:?}", ffi::Shared { z: 1 })); + assert_eq!("BVal", format!("{:?}", ffi::Enum::BVal)); + assert_eq!("Enum(9)", format!("{:?}", ffi::Enum { repr: 9 })); } #[no_mangle] -extern "C" fn cxx_test_suite_get_box() -> *mut cxx_test_suite::R { - Box::into_raw(Box::new(2020usize)) +extern "C" fn cxx_test_suite_get_box() -> *mut R { + Box::into_raw(Box::new(R(2020usize))) } #[no_mangle] -unsafe extern "C" fn cxx_test_suite_r_is_correct(r: *const cxx_test_suite::R) -> bool { - *r == 2020 +unsafe extern "C" fn cxx_test_suite_r_is_correct(r: *const R) -> bool { + (*r).0 == 2020 } #[test] @@ -233,10 +302,14 @@ fn test_rust_name_attribute() { #[test] fn test_extern_trivial() { - let d = ffi2::c_return_trivial(); + let mut d = ffi2::c_return_trivial(); check!(ffi2::c_take_trivial_ref(&d)); + check!(d.c_take_trivial_ref_method()); + check!(d.c_take_trivial_mut_ref_method()); check!(ffi2::c_take_trivial(d)); - let d = ffi2::c_return_trivial_ptr(); + let mut d = ffi2::c_return_trivial_ptr(); + check!(d.c_take_trivial_ref_method()); + check!(d.c_take_trivial_mut_ref_method()); check!(ffi2::c_take_trivial_ptr(d)); cxx::UniquePtr::new(ffi2::D { d: 42 }); let d = ffi2::ns_c_return_trivial(); @@ -252,11 +325,29 @@ fn test_extern_trivial() { #[test] fn test_extern_opaque() { - let e = ffi2::c_return_opaque_ptr(); + let mut e = ffi2::c_return_opaque_ptr(); check!(ffi2::c_take_opaque_ref(e.as_ref().unwrap())); + check!(e.c_take_opaque_ref_method()); + check!(e.pin_mut().c_take_opaque_mut_ref_method()); 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)); } + +#[test] +fn test_raw_ptr() { + let c = ffi::c_return_mut_ptr(2023); + let mut c_unique = unsafe { cxx::UniquePtr::from_raw(c) }; + assert_eq!(2023, c_unique.pin_mut().set_succeed(2023).unwrap()); + // c will be dropped as it's now in a UniquePtr + + let c2 = ffi::c_return_mut_ptr(2024); + assert_eq!(2024, unsafe { ffi::c_take_const_ptr(c2) }); + assert_eq!(2024, unsafe { ffi::c_take_mut_ptr(c2) }); // deletes c2 + + let c3 = ffi::c_return_const_ptr(2025); + assert_eq!(2025, unsafe { ffi::c_take_const_ptr(c3) }); + assert_eq!(2025, unsafe { ffi::c_take_mut_ptr(c3 as *mut ffi::C) }); // deletes c3 +} diff --git a/tests/ui/array_len_expr.rs b/tests/ui/array_len_expr.rs new file mode 100644 index 00000000..73da57c8 --- /dev/null +++ b/tests/ui/array_len_expr.rs @@ -0,0 +1,10 @@ +#[cxx::bridge] +mod ffi { + struct Shared { + arraystr: [String; "13"], + arraysub: [String; 15 - 1], + arrayzero: [String; 0], + } +} + +fn main() {} diff --git a/tests/ui/array_len_expr.stderr b/tests/ui/array_len_expr.stderr new file mode 100644 index 00000000..4b45a0b9 --- /dev/null +++ b/tests/ui/array_len_expr.stderr @@ -0,0 +1,17 @@ +error: array length must be an integer literal + --> $DIR/array_len_expr.rs:4:28 + | +4 | arraystr: [String; "13"], + | ^^^^ + +error: unsupported expression, array length must be an integer literal + --> $DIR/array_len_expr.rs:5:28 + | +5 | arraysub: [String; 15 - 1], + | ^^^^^^ + +error: array with zero size is not supported + --> $DIR/array_len_expr.rs:6:20 + | +6 | arrayzero: [String; 0], + | ^^^^^^^^^^^ diff --git a/tests/ui/array_len_suffix.rs b/tests/ui/array_len_suffix.rs new file mode 100644 index 00000000..1e9d514f --- /dev/null +++ b/tests/ui/array_len_suffix.rs @@ -0,0 +1,8 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + fn array() -> [String; 12u16]; + } +} + +fn main() {} diff --git a/tests/ui/array_len_suffix.stderr b/tests/ui/array_len_suffix.stderr new file mode 100644 index 00000000..b72fc022 --- /dev/null +++ b/tests/ui/array_len_suffix.stderr @@ -0,0 +1,10 @@ +error[E0308]: mismatched types + --> $DIR/array_len_suffix.rs:4:32 + | +4 | fn array() -> [String; 12u16]; + | ^^^^^ expected `usize`, found `u16` + | +help: change the type of the numeric literal from `u16` to `usize` + | +4 | fn array() -> [String; 12usize]; + | ^^^^^^^ diff --git a/tests/ui/async_fn.rs b/tests/ui/async_fn.rs new file mode 100644 index 00000000..69f26421 --- /dev/null +++ b/tests/ui/async_fn.rs @@ -0,0 +1,10 @@ +#[cxx::bridge] +mod ffi { + extern "Rust" { + async fn f(); + } +} + +async fn f() {} + +fn main() {} diff --git a/tests/ui/async_fn.stderr b/tests/ui/async_fn.stderr new file mode 100644 index 00000000..dedec7bb --- /dev/null +++ b/tests/ui/async_fn.stderr @@ -0,0 +1,5 @@ +error: async function is not directly supported yet, but see https://cxx.rs/async.html for a working approach + --> $DIR/async_fn.rs:4:9 + | +4 | async fn f(); + | ^^^^^^^^^^^^^ diff --git a/tests/ui/by_value_not_supported.rs b/tests/ui/by_value_not_supported.rs index 3ff950a8..62bf8d4f 100644 --- a/tests/ui/by_value_not_supported.rs +++ b/tests/ui/by_value_not_supported.rs @@ -6,7 +6,7 @@ mod ffi { s: CxxString, } - extern "C" { + extern "C++" { type C; } diff --git a/tests/ui/by_value_not_supported.stderr b/tests/ui/by_value_not_supported.stderr index 0a56dd48..7288c93b 100644 --- a/tests/ui/by_value_not_supported.stderr +++ b/tests/ui/by_value_not_supported.stderr @@ -16,7 +16,7 @@ error: using C++ string by value is not supported 6 | s: CxxString, | ^^^^^^^^^^^^ -error: needs a cxx::ExternType impl in order to be used as a field of `S` +error: needs a cxx::ExternType impl in order to be used as a field of `S`, argument of `f` or return value of `f` --> $DIR/by_value_not_supported.rs:10:9 | 10 | type C; diff --git a/tests/ui/const_fn.rs b/tests/ui/const_fn.rs new file mode 100644 index 00000000..1ad48941 --- /dev/null +++ b/tests/ui/const_fn.rs @@ -0,0 +1,10 @@ +#[cxx::bridge] +mod ffi { + extern "Rust" { + const fn f(); + } +} + +const fn f() {} + +fn main() {} diff --git a/tests/ui/const_fn.stderr b/tests/ui/const_fn.stderr new file mode 100644 index 00000000..a62ca837 --- /dev/null +++ b/tests/ui/const_fn.stderr @@ -0,0 +1,5 @@ +error: const extern function is not supported + --> $DIR/const_fn.rs:4:9 + | +4 | const fn f(); + | ^^^^^^^^^^^^^ diff --git a/tests/ui/deny_missing_docs.rs b/tests/ui/deny_missing_docs.rs new file mode 100644 index 00000000..037894bb --- /dev/null +++ b/tests/ui/deny_missing_docs.rs @@ -0,0 +1,94 @@ +// TODO: More work is needed so that the missing_docs lints produced by rustc +// are properly positioned inside of the bridge. + +//! ... + +#![deny(missing_docs)] + +/// ... +#[cxx::bridge] +pub mod ffi { + pub struct UndocumentedStruct { + pub undocumented_field: u8, + } + + /// ... + pub struct DocumentedStruct { + /// ... + pub documented_field: u8, + } + + pub enum UndocumentedEnum { + UndocumentedVariant = 0, + } + + /// ... + pub enum DocumentedEnum { + /// ... + DocumentedVariant = 0, + } + + extern "Rust" { + pub type UndocumentedRustType; + + /// ... + pub type DocumentedRustType; + + pub fn undocumented_rust_fn() -> u8; + + /// ... + pub fn documented_rust_fn() -> u8; + } + + unsafe extern "C++" { + pub type UndocumentedForeignType; + + /// ... + pub type DocumentedForeignType; + + pub type UndocumentedTypeAlias = crate::bindgen::UndocumentedTypeAlias; + + /// ... + pub type DocumentedTypeAlias = crate::bindgen::DocumentedTypeAlias; + + pub fn undocumented_foreign_fn() -> u8; + + /// ... + pub fn documented_foreign_fn() -> u8; + } + + #[allow(missing_docs)] + pub struct SuppressUndocumentedStruct { + pub undocumented_field: u8, + } +} + +struct UndocumentedRustType; +struct DocumentedRustType; + +mod bindgen { + use cxx::{type_id, ExternType}; + + pub struct UndocumentedTypeAlias; + pub struct DocumentedTypeAlias; + + unsafe impl ExternType for UndocumentedTypeAlias { + type Id = type_id!("UndocumentedTypeAlias"); + type Kind = cxx::kind::Opaque; + } + + unsafe impl ExternType for DocumentedTypeAlias { + type Id = type_id!("DocumentedTypeAlias"); + type Kind = cxx::kind::Opaque; + } +} + +fn undocumented_rust_fn() -> u8 { + 0 +} + +fn documented_rust_fn() -> u8 { + 0 +} + +fn main() {} diff --git a/tests/ui/deny_missing_docs.stderr b/tests/ui/deny_missing_docs.stderr new file mode 100644 index 00000000..e7aadfbe --- /dev/null +++ b/tests/ui/deny_missing_docs.stderr @@ -0,0 +1,47 @@ +error: missing documentation for a struct + --> $DIR/deny_missing_docs.rs:11:5 + | +11 | pub struct UndocumentedStruct { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/deny_missing_docs.rs:6:9 + | +6 | #![deny(missing_docs)] + | ^^^^^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/deny_missing_docs.rs:12:9 + | +12 | pub undocumented_field: u8, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct + --> $DIR/deny_missing_docs.rs:21:5 + | +21 | pub enum UndocumentedEnum { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for an associated constant + --> $DIR/deny_missing_docs.rs:22:9 + | +22 | UndocumentedVariant = 0, + | ^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct + --> $DIR/deny_missing_docs.rs:44:9 + | +44 | pub type UndocumentedForeignType; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a type alias + --> $DIR/deny_missing_docs.rs:49:9 + | +49 | pub type UndocumentedTypeAlias = crate::bindgen::UndocumentedTypeAlias; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/deny_missing_docs.rs:54:9 + | +54 | pub fn undocumented_foreign_fn() -> u8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/derive_duplicate.rs b/tests/ui/derive_duplicate.rs new file mode 100644 index 00000000..3061f765 --- /dev/null +++ b/tests/ui/derive_duplicate.rs @@ -0,0 +1,9 @@ +#[cxx::bridge] +mod ffi { + #[derive(Clone, Clone)] + struct Struct { + x: usize, + } +} + +fn main() {} diff --git a/tests/ui/derive_duplicate.stderr b/tests/ui/derive_duplicate.stderr new file mode 100644 index 00000000..ce271632 --- /dev/null +++ b/tests/ui/derive_duplicate.stderr @@ -0,0 +1,7 @@ +error[E0119]: conflicting implementations of trait `std::clone::Clone` for type `ffi::Struct` + --> $DIR/derive_duplicate.rs:3:21 + | +3 | #[derive(Clone, Clone)] + | ----- ^^^^^ conflicting implementation for `ffi::Struct` + | | + | first implementation here diff --git a/tests/ui/derive_nonclone.rs b/tests/ui/derive_nonclone.rs new file mode 100644 index 00000000..58110289 --- /dev/null +++ b/tests/ui/derive_nonclone.rs @@ -0,0 +1,13 @@ +#[cxx::bridge] +mod ffi { + #[derive(Clone)] + struct TryClone { + other: Other, + } + + struct Other { + x: usize, + } +} + +fn main() {} diff --git a/tests/ui/derive_nonclone.stderr b/tests/ui/derive_nonclone.stderr new file mode 100644 index 00000000..04eae4fd --- /dev/null +++ b/tests/ui/derive_nonclone.stderr @@ -0,0 +1,7 @@ +error[E0277]: the trait bound `ffi::Other: Clone` is not satisfied + --> $DIR/derive_nonclone.rs:5:9 + | +5 | other: Other, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `ffi::Other` + | + = note: required by `clone` diff --git a/tests/ui/derive_noncopy.rs b/tests/ui/derive_noncopy.rs new file mode 100644 index 00000000..ae705fd0 --- /dev/null +++ b/tests/ui/derive_noncopy.rs @@ -0,0 +1,13 @@ +#[cxx::bridge] +mod ffi { + #[derive(Copy)] + struct TryCopy { + other: Other, + } + + struct Other { + x: usize, + } +} + +fn main() {} diff --git a/tests/ui/derive_noncopy.stderr b/tests/ui/derive_noncopy.stderr new file mode 100644 index 00000000..d6410280 --- /dev/null +++ b/tests/ui/derive_noncopy.stderr @@ -0,0 +1,8 @@ +error[E0204]: the trait `Copy` may not be implemented for this type + --> $DIR/derive_noncopy.rs:3:14 + | +3 | #[derive(Copy)] + | ^^^^ +4 | struct TryCopy { +5 | other: Other, + | ------------ this field does not implement `Copy` diff --git a/tests/ui/disallow_lifetime.rs b/tests/ui/disallow_lifetime.rs deleted file mode 100644 index ee39fef9..00000000 --- a/tests/ui/disallow_lifetime.rs +++ /dev/null @@ -1,13 +0,0 @@ -#[cxx::bridge] -mod ffi { - extern "C" { - type C; - fn f(&'static self); - } - - extern "Rust" { - fn f(string: &'a String); - } -} - -fn main() {} diff --git a/tests/ui/disallow_lifetime.stderr b/tests/ui/disallow_lifetime.stderr deleted file mode 100644 index d7c42e4b..00000000 --- a/tests/ui/disallow_lifetime.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: references with explicit lifetimes are not supported - --> $DIR/disallow_lifetime.rs:9:22 - | -9 | fn f(string: &'a String); - | ^^^^^^^^^^ - -error: references with explicit lifetimes are not supported - --> $DIR/disallow_lifetime.rs:5:14 - | -5 | fn f(&'static self); - | ^^^^^^^^^^^^^ diff --git a/tests/ui/drop_shared.rs b/tests/ui/drop_shared.rs new file mode 100644 index 00000000..5e3de4a1 --- /dev/null +++ b/tests/ui/drop_shared.rs @@ -0,0 +1,14 @@ +#[cxx::bridge] +mod ffi { + struct Shared { + fd: i32, + } +} + +impl Drop for ffi::Shared { + fn drop(&mut self) { + println!("close({})", self.fd); + } +} + +fn main() {} diff --git a/tests/ui/drop_shared.stderr b/tests/ui/drop_shared.stderr new file mode 100644 index 00000000..f2724e35 --- /dev/null +++ b/tests/ui/drop_shared.stderr @@ -0,0 +1,8 @@ +error[E0119]: conflicting implementations of trait `ffi::_::forbid::Drop` for type `ffi::Shared` + --> $DIR/drop_shared.rs:3:5 + | +1 | #[cxx::bridge] + | -------------- first implementation here +2 | mod ffi { +3 | struct Shared { + | ^^^^^^^^^^^^^ conflicting implementation for `ffi::Shared` diff --git a/tests/ui/duplicate_enum_discriminants.rs b/tests/ui/duplicate_enum_discriminants.rs deleted file mode 100644 index ec3d61a8..00000000 --- a/tests/ui/duplicate_enum_discriminants.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[cxx::bridge] -mod ffi { - enum A { - V1 = 10, - V2 = 10, - } - - enum B { - V1 = 10, - V2, - V3 = 11, - } -} - -fn main() {} diff --git a/tests/ui/duplicate_enum_discriminants.stderr b/tests/ui/duplicate_enum_discriminants.stderr deleted file mode 100644 index 14505e32..00000000 --- a/tests/ui/duplicate_enum_discriminants.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: discriminant value `10` already exists - --> $DIR/duplicate_enum_discriminants.rs:5:9 - | -5 | V2 = 10, - | ^^^^^^^ - -error: discriminant value `11` already exists - --> $DIR/duplicate_enum_discriminants.rs:11:9 - | -11 | V3 = 11, - | ^^^^^^^ diff --git a/tests/ui/empty_enum.stderr b/tests/ui/empty_enum.stderr index 7f350191..0556da9a 100644 --- a/tests/ui/empty_enum.stderr +++ b/tests/ui/empty_enum.stderr @@ -1,4 +1,4 @@ -error: enums without any variants are not supported +error: explicit #[repr(...)] is required for enum without any variants --> $DIR/empty_enum.rs:3:5 | 3 | enum A {} diff --git a/tests/ui/enum_match_without_wildcard.stderr b/tests/ui/enum_match_without_wildcard.stderr index 85cc0c55..f43678f7 100644 --- a/tests/ui/enum_match_without_wildcard.stderr +++ b/tests/ui/enum_match_without_wildcard.stderr @@ -1,8 +1,8 @@ error[E0004]: non-exhaustive patterns: `A { repr: 2_u8..=u8::MAX }` not covered --> $DIR/enum_match_without_wildcard.rs:12:11 | -1 | #[cxx::bridge] - | -------------- `ffi::A` defined here +3 | enum A { + | ------ `ffi::A` defined here ... 12 | match a { | ^ pattern `A { repr: 2_u8..=u8::MAX }` not covered diff --git a/tests/ui/enum_receiver.rs b/tests/ui/enum_receiver.rs new file mode 100644 index 00000000..a27f2746 --- /dev/null +++ b/tests/ui/enum_receiver.rs @@ -0,0 +1,11 @@ +#[cxx::bridge] +mod ffi { + enum Enum { + Variant, + } + extern "Rust" { + fn f(self: &Enum); + } +} + +fn main() {} diff --git a/tests/ui/enum_receiver.stderr b/tests/ui/enum_receiver.stderr new file mode 100644 index 00000000..e09b4e39 --- /dev/null +++ b/tests/ui/enum_receiver.stderr @@ -0,0 +1,5 @@ +error: unsupported receiver type; C++ does not allow member functions on enums + --> $DIR/enum_receiver.rs:7:20 + | +7 | fn f(self: &Enum); + | ^^^^^ diff --git a/tests/ui/expected_named.rs b/tests/ui/expected_named.rs new file mode 100644 index 00000000..31626d1f --- /dev/null +++ b/tests/ui/expected_named.rs @@ -0,0 +1,9 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + type Borrowed<'a>; + fn borrowed() -> UniquePtr<Borrowed>; + } +} + +fn main() {} diff --git a/tests/ui/expected_named.stderr b/tests/ui/expected_named.stderr new file mode 100644 index 00000000..46764014 --- /dev/null +++ b/tests/ui/expected_named.stderr @@ -0,0 +1,11 @@ +error[E0106]: missing lifetime specifier + --> $DIR/expected_named.rs:5:36 + | +5 | fn borrowed() -> UniquePtr<Borrowed>; + | ^^^^^^^^ expected named lifetime parameter + | + = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from +help: consider using the `'static` lifetime + | +5 | fn borrowed() -> UniquePtr<Borrowed<'static>>; + | ^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/extern_fn_abi.rs b/tests/ui/extern_fn_abi.rs new file mode 100644 index 00000000..1f933388 --- /dev/null +++ b/tests/ui/extern_fn_abi.rs @@ -0,0 +1,8 @@ +#[cxx::bridge] +mod ffi { + extern "C++" { + extern "Java" fn f(); + } +} + +fn main() {} diff --git a/tests/ui/extern_fn_abi.stderr b/tests/ui/extern_fn_abi.stderr new file mode 100644 index 00000000..3abf47ab --- /dev/null +++ b/tests/ui/extern_fn_abi.stderr @@ -0,0 +1,5 @@ +error: explicit ABI on extern function is not supported + --> $DIR/extern_fn_abi.rs:4:9 + | +4 | extern "Java" fn f(); + | ^^^^^^^^^^^^^ diff --git a/tests/ui/extern_type_bound.rs b/tests/ui/extern_type_bound.rs new file mode 100644 index 00000000..958accd3 --- /dev/null +++ b/tests/ui/extern_type_bound.rs @@ -0,0 +1,15 @@ +#[cxx::bridge] +mod ffi { + extern "C++" { + type Opaque: PartialEq + PartialOrd; + } +} + +#[cxx::bridge] +mod ffi { + extern "C++" { + type Opaque: for<'de> Deserialize<'de>; + } +} + +fn main() {} diff --git a/tests/ui/extern_type_bound.stderr b/tests/ui/extern_type_bound.stderr new file mode 100644 index 00000000..ca07ef7f --- /dev/null +++ b/tests/ui/extern_type_bound.stderr @@ -0,0 +1,11 @@ +error: extern type bounds are not implemented yet + --> $DIR/extern_type_bound.rs:4:22 + | +4 | type Opaque: PartialEq + PartialOrd; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: unsupported trait + --> $DIR/extern_type_bound.rs:11:22 + | +11 | type Opaque: for<'de> Deserialize<'de>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/extern_type_generic.rs b/tests/ui/extern_type_generic.rs new file mode 100644 index 00000000..4de2c981 --- /dev/null +++ b/tests/ui/extern_type_generic.rs @@ -0,0 +1,8 @@ +#[cxx::bridge] +mod ffi { + extern "C++" { + type Generic<T>; + } +} + +fn main() {} diff --git a/tests/ui/extern_type_generic.stderr b/tests/ui/extern_type_generic.stderr new file mode 100644 index 00000000..95faec6c --- /dev/null +++ b/tests/ui/extern_type_generic.stderr @@ -0,0 +1,5 @@ +error: extern type with generic type parameter is not supported yet + --> $DIR/extern_type_generic.rs:4:22 + | +4 | type Generic<T>; + | ^ diff --git a/tests/ui/extern_type_lifetime_bound.rs b/tests/ui/extern_type_lifetime_bound.rs new file mode 100644 index 00000000..ad581818 --- /dev/null +++ b/tests/ui/extern_type_lifetime_bound.rs @@ -0,0 +1,8 @@ +#[cxx::bridge] +mod ffi { + extern "C++" { + type Complex<'a, 'b: 'a>; + } +} + +fn main() {} diff --git a/tests/ui/extern_type_lifetime_bound.stderr b/tests/ui/extern_type_lifetime_bound.stderr new file mode 100644 index 00000000..8c58050a --- /dev/null +++ b/tests/ui/extern_type_lifetime_bound.stderr @@ -0,0 +1,5 @@ +error: lifetime parameter with bounds is not supported yet + --> $DIR/extern_type_lifetime_bound.rs:4:26 + | +4 | type Complex<'a, 'b: 'a>; + | ^^^^^^ diff --git a/tests/ui/fallible_fnptr.rs b/tests/ui/fallible_fnptr.rs new file mode 100644 index 00000000..c45813de --- /dev/null +++ b/tests/ui/fallible_fnptr.rs @@ -0,0 +1,8 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + fn f(callback: fn() -> Result<()>); + } +} + +fn main() {} diff --git a/tests/ui/fallible_fnptr.stderr b/tests/ui/fallible_fnptr.stderr new file mode 100644 index 00000000..1d3fbe42 --- /dev/null +++ b/tests/ui/fallible_fnptr.stderr @@ -0,0 +1,5 @@ +error: function pointer returning Result is not supported yet + --> $DIR/fallible_fnptr.rs:4:24 + | +4 | fn f(callback: fn() -> Result<()>); + | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/function_with_body.rs b/tests/ui/function_with_body.rs new file mode 100644 index 00000000..52be79af --- /dev/null +++ b/tests/ui/function_with_body.rs @@ -0,0 +1,8 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + fn f() {} + } +} + +fn main() {} diff --git a/tests/ui/function_with_body.stderr b/tests/ui/function_with_body.stderr new file mode 100644 index 00000000..c5641db8 --- /dev/null +++ b/tests/ui/function_with_body.stderr @@ -0,0 +1,5 @@ +error: expected `;` + --> $DIR/function_with_body.rs:4:16 + | +4 | fn f() {} + | ^ diff --git a/tests/ui/generic_enum.rs b/tests/ui/generic_enum.rs index 808856eb..feeb94be 100644 --- a/tests/ui/generic_enum.rs +++ b/tests/ui/generic_enum.rs @@ -3,6 +3,14 @@ mod ffi { enum A<T> { Field, } + + enum B<T> where T: Copy { + Field, + } + + enum C where void: Copy { + Field, + } } fn main() {} diff --git a/tests/ui/generic_enum.stderr b/tests/ui/generic_enum.stderr index bf267f74..ea83d7c8 100644 --- a/tests/ui/generic_enum.stderr +++ b/tests/ui/generic_enum.stderr @@ -1,5 +1,17 @@ -error: enums with generic parameters are not allowed +error: enum with generic parameters is not supported --> $DIR/generic_enum.rs:3:5 | 3 | enum A<T> { | ^^^^^^^^^ + +error: enum with generic parameters is not supported + --> $DIR/generic_enum.rs:7:5 + | +7 | enum B<T> where T: Copy { + | ^^^^^^^^^ + +error: enum with where-clause is not supported + --> $DIR/generic_enum.rs:11:12 + | +11 | enum C where void: Copy { + | ^^^^^^^^^^^^^^^^ diff --git a/tests/ui/include.rs b/tests/ui/include.rs index 82fa8deb..3e848b58 100644 --- a/tests/ui/include.rs +++ b/tests/ui/include.rs @@ -1,6 +1,6 @@ #[cxx::bridge] mod ffi { - extern "C" { + extern "C++" { include!("path/to" what); include!(<path/to> what); include!(<path/to); diff --git a/tests/ui/lifetime_extern_cxx.rs b/tests/ui/lifetime_extern_cxx.rs new file mode 100644 index 00000000..e85b3392 --- /dev/null +++ b/tests/ui/lifetime_extern_cxx.rs @@ -0,0 +1,9 @@ +#[cxx::bridge] +mod ffi { + extern "C++" { + type Opaque; + unsafe fn f<'a>(&'a self, arg: &str) -> &'a str; + } +} + +fn main() {} diff --git a/tests/ui/lifetime_extern_cxx.stderr b/tests/ui/lifetime_extern_cxx.stderr new file mode 100644 index 00000000..099c6ce4 --- /dev/null +++ b/tests/ui/lifetime_extern_cxx.stderr @@ -0,0 +1,5 @@ +error: extern C++ function with lifetimes must be declared in `unsafe extern "C++"` block + --> $DIR/lifetime_extern_cxx.rs:5:9 + | +5 | unsafe fn f<'a>(&'a self, arg: &str) -> &'a str; + | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lifetime_extern_rust.rs b/tests/ui/lifetime_extern_rust.rs new file mode 100644 index 00000000..72c37cba --- /dev/null +++ b/tests/ui/lifetime_extern_rust.rs @@ -0,0 +1,17 @@ +#[cxx::bridge] +mod ffi { + extern "Rust" { + type Opaque; + fn f<'a>(&'a self, arg: &str) -> &'a str; + } +} + +pub struct Opaque; + +impl Opaque { + fn f(&self, _arg: &str) -> &str { + "" + } +} + +fn main() {} diff --git a/tests/ui/lifetime_extern_rust.stderr b/tests/ui/lifetime_extern_rust.stderr new file mode 100644 index 00000000..0fc806ad --- /dev/null +++ b/tests/ui/lifetime_extern_rust.stderr @@ -0,0 +1,5 @@ +error: must be `unsafe fn f` in order to expose explicit lifetimes to C++ + --> $DIR/lifetime_extern_rust.rs:5:9 + | +5 | fn f<'a>(&'a self, arg: &str) -> &'a str; + | ^^^^^^^^ diff --git a/tests/ui/multiple_parse_error.stderr b/tests/ui/multiple_parse_error.stderr index 41895071..f736fd1f 100644 --- a/tests/ui/multiple_parse_error.stderr +++ b/tests/ui/multiple_parse_error.stderr @@ -1,10 +1,10 @@ -error: struct with generic parameters is not supported yet +error: unit structs are not supported --> $DIR/multiple_parse_error.rs:3:5 | 3 | struct Monad<T>; - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ -error: unrecognized ABI +error: unrecognized ABI, requires either "C++" or "Rust" --> $DIR/multiple_parse_error.rs:5:5 | 5 | extern "Haskell" {} diff --git a/tests/ui/mut_return.rs b/tests/ui/mut_return.rs new file mode 100644 index 00000000..f30988b8 --- /dev/null +++ b/tests/ui/mut_return.rs @@ -0,0 +1,18 @@ +#[cxx::bridge] +mod ffi { + extern "Rust" { + type Mut<'a>; + } + + unsafe extern "C++" { + type Thing; + + fn f(t: &Thing) -> Pin<&mut CxxString>; + unsafe fn g(t: &Thing) -> Pin<&mut CxxString>; + fn h(t: Box<Mut>) -> Pin<&mut CxxString>; + fn i<'a>(t: Box<Mut<'a>>) -> Pin<&'a mut CxxString>; + fn j(t: &Thing) -> &mut [u8]; + } +} + +fn main() {} diff --git a/tests/ui/mut_return.stderr b/tests/ui/mut_return.stderr new file mode 100644 index 00000000..e33393d8 --- /dev/null +++ b/tests/ui/mut_return.stderr @@ -0,0 +1,11 @@ +error: &mut return type is not allowed unless there is a &mut argument + --> $DIR/mut_return.rs:10:9 + | +10 | fn f(t: &Thing) -> Pin<&mut CxxString>; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: &mut return type is not allowed unless there is a &mut argument + --> $DIR/mut_return.rs:14:9 + | +14 | fn j(t: &Thing) -> &mut [u8]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/nonlocal_rust_type.rs b/tests/ui/nonlocal_rust_type.rs new file mode 100644 index 00000000..d8a238ae --- /dev/null +++ b/tests/ui/nonlocal_rust_type.rs @@ -0,0 +1,18 @@ +pub struct MyBuilder<'a> { + _s: &'a str, +} + +type OptBuilder<'a> = Option<MyBuilder<'a>>; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + type OptBuilder<'a>; + } + + struct MyBuilder<'a> { + rs: Box<OptBuilder<'a>>, + } +} + +fn main() {} diff --git a/tests/ui/nonlocal_rust_type.stderr b/tests/ui/nonlocal_rust_type.stderr new file mode 100644 index 00000000..d266353c --- /dev/null +++ b/tests/ui/nonlocal_rust_type.stderr @@ -0,0 +1,21 @@ +error[E0117]: only traits defined in the current crate can be implemented for arbitrary types + --> $DIR/nonlocal_rust_type.rs:10:9 + | +10 | type OptBuilder<'a>; + | ^^^^^-------------- + | | | + | | `Option` is not defined in the current crate + | impl doesn't use only types from inside the current crate + | + = note: define and implement a trait or new type instead + +error[E0117]: only traits defined in the current crate can be implemented for arbitrary types + --> $DIR/nonlocal_rust_type.rs:14:13 + | +14 | rs: Box<OptBuilder<'a>>, + | ^^^^--------------- + | | | + | | `Option` is not defined in the current crate + | impl doesn't use only types from inside the current crate + | + = note: define and implement a trait or new type instead diff --git a/tests/ui/opaque_autotraits.rs b/tests/ui/opaque_autotraits.rs new file mode 100644 index 00000000..99406d74 --- /dev/null +++ b/tests/ui/opaque_autotraits.rs @@ -0,0 +1,16 @@ +#[cxx::bridge] +mod ffi { + extern "C++" { + type Opaque; + } +} + +fn assert_send<T: Send>() {} +fn assert_sync<T: Sync>() {} +fn assert_unpin<T: Unpin>() {} + +fn main() { + assert_send::<ffi::Opaque>(); + assert_sync::<ffi::Opaque>(); + assert_unpin::<ffi::Opaque>(); +} diff --git a/tests/ui/opaque_autotraits.stderr b/tests/ui/opaque_autotraits.stderr new file mode 100644 index 00000000..4f469eff --- /dev/null +++ b/tests/ui/opaque_autotraits.stderr @@ -0,0 +1,40 @@ +error[E0277]: `*const u8` cannot be sent between threads safely + --> $DIR/opaque_autotraits.rs:13:5 + | +8 | fn assert_send<T: Send>() {} + | ---- required by this bound in `assert_send` +... +13 | assert_send::<ffi::Opaque>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `*const u8` cannot be sent between threads safely + | + = help: within `ffi::Opaque`, the trait `Send` is not implemented for `*const u8` + = note: required because it appears within the type `[*const u8; 0]` + = note: required because it appears within the type `cxx::private::Opaque` + = note: required because it appears within the type `ffi::Opaque` + +error[E0277]: `*const u8` cannot be shared between threads safely + --> $DIR/opaque_autotraits.rs:14:5 + | +9 | fn assert_sync<T: Sync>() {} + | ---- required by this bound in `assert_sync` +... +14 | assert_sync::<ffi::Opaque>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `*const u8` cannot be shared between threads safely + | + = help: within `ffi::Opaque`, the trait `Sync` is not implemented for `*const u8` + = note: required because it appears within the type `[*const u8; 0]` + = note: required because it appears within the type `cxx::private::Opaque` + = note: required because it appears within the type `ffi::Opaque` + +error[E0277]: `PhantomPinned` cannot be unpinned + --> $DIR/opaque_autotraits.rs:15:5 + | +10 | fn assert_unpin<T: Unpin>() {} + | ----- required by this bound in `assert_unpin` +... +15 | assert_unpin::<ffi::Opaque>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ within `ffi::Opaque`, the trait `Unpin` is not implemented for `PhantomPinned` + | + = note: required because it appears within the type `PhantomData<PhantomPinned>` + = note: required because it appears within the type `cxx::private::Opaque` + = note: required because it appears within the type `ffi::Opaque` diff --git a/tests/ui/pin_mut_opaque.rs b/tests/ui/pin_mut_opaque.rs new file mode 100644 index 00000000..ac1ca43a --- /dev/null +++ b/tests/ui/pin_mut_opaque.rs @@ -0,0 +1,14 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + type Opaque; + fn f(arg: &mut Opaque); + fn g(&mut self); + fn h(self: &mut Opaque); + fn s(s: &mut CxxString); + fn v(v: &mut CxxVector<u8>); + } + +} + +fn main() {} diff --git a/tests/ui/pin_mut_opaque.stderr b/tests/ui/pin_mut_opaque.stderr new file mode 100644 index 00000000..95af3cd9 --- /dev/null +++ b/tests/ui/pin_mut_opaque.stderr @@ -0,0 +1,35 @@ +error: mutable reference to C++ type requires a pin -- use Pin<&mut Opaque> + --> $DIR/pin_mut_opaque.rs:5:19 + | +5 | fn f(arg: &mut Opaque); + | ^^^^^^^^^^^ + +error: mutable reference to C++ type requires a pin -- use Pin<&mut CxxString> + --> $DIR/pin_mut_opaque.rs:8:17 + | +8 | fn s(s: &mut CxxString); + | ^^^^^^^^^^^^^^ + +error: mutable reference to C++ type requires a pin -- use Pin<&mut CxxVector<...>> + --> $DIR/pin_mut_opaque.rs:9:17 + | +9 | fn v(v: &mut CxxVector<u8>); + | ^^^^^^^^^^^^^^^^^^ + +error: needs a cxx::ExternType impl in order to be used as a non-pinned mutable reference in signature of `f`, `g`, `h` + --> $DIR/pin_mut_opaque.rs:4:9 + | +4 | type Opaque; + | ^^^^^^^^^^^ + +error: mutable reference to opaque C++ type requires a pin -- use `self: Pin<&mut Opaque>` + --> $DIR/pin_mut_opaque.rs:6:14 + | +6 | fn g(&mut self); + | ^^^^^^^^^ + +error: mutable reference to opaque C++ type requires a pin -- use `self: Pin<&mut Opaque>` + --> $DIR/pin_mut_opaque.rs:7:20 + | +7 | fn h(self: &mut Opaque); + | ^^^^^^^^^^^ diff --git a/tests/ui/ptr_in_fnptr.rs b/tests/ui/ptr_in_fnptr.rs new file mode 100644 index 00000000..73c97c66 --- /dev/null +++ b/tests/ui/ptr_in_fnptr.rs @@ -0,0 +1,8 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + fn f(callback: fn(p: *const u8)); + } +} + +fn main() {} diff --git a/tests/ui/ptr_in_fnptr.stderr b/tests/ui/ptr_in_fnptr.stderr new file mode 100644 index 00000000..372d4cd9 --- /dev/null +++ b/tests/ui/ptr_in_fnptr.stderr @@ -0,0 +1,5 @@ +error: pointer argument requires that the function pointer be marked unsafe + --> $DIR/ptr_in_fnptr.rs:4:27 + | +4 | fn f(callback: fn(p: *const u8)); + | ^^^^^^^^^^^^ diff --git a/tests/ui/ptr_missing_unsafe.rs b/tests/ui/ptr_missing_unsafe.rs new file mode 100644 index 00000000..56f0007c --- /dev/null +++ b/tests/ui/ptr_missing_unsafe.rs @@ -0,0 +1,10 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + type C; + + fn not_unsafe_ptr(c: *mut C); + } +} + +fn main() {} diff --git a/tests/ui/ptr_missing_unsafe.stderr b/tests/ui/ptr_missing_unsafe.stderr new file mode 100644 index 00000000..65f53a98 --- /dev/null +++ b/tests/ui/ptr_missing_unsafe.stderr @@ -0,0 +1,5 @@ +error: pointer argument requires that the function be marked unsafe + --> $DIR/ptr_missing_unsafe.rs:6:27 + | +6 | fn not_unsafe_ptr(c: *mut C); + | ^^^^^^^^^ diff --git a/tests/ui/ptr_no_const_mut.rs b/tests/ui/ptr_no_const_mut.rs new file mode 100644 index 00000000..eecec3fb --- /dev/null +++ b/tests/ui/ptr_no_const_mut.rs @@ -0,0 +1,10 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + type C; + + fn get_neither_const_nor_mut() -> *C; + } +} + +fn main() {} diff --git a/tests/ui/ptr_no_const_mut.stderr b/tests/ui/ptr_no_const_mut.stderr new file mode 100644 index 00000000..8a391852 --- /dev/null +++ b/tests/ui/ptr_no_const_mut.stderr @@ -0,0 +1,13 @@ +error: expected mut or const in raw pointer type + --> $DIR/ptr_no_const_mut.rs:6:43 + | +6 | fn get_neither_const_nor_mut() -> *C; + | ^ expected mut or const in raw pointer type + | + = help: use `*mut T` or `*const T` as appropriate + +error: expected `const` or `mut` + --> $DIR/ptr_no_const_mut.rs:6:44 + | +6 | fn get_neither_const_nor_mut() -> *C; + | ^ diff --git a/tests/ui/ptr_unsupported.rs b/tests/ui/ptr_unsupported.rs new file mode 100644 index 00000000..9d59c033 --- /dev/null +++ b/tests/ui/ptr_unsupported.rs @@ -0,0 +1,12 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + type C; + + fn get_ptr_to_reference() -> *mut &C; + fn get_uniqueptr_to_ptr() -> UniquePtr<*mut C>; + fn get_vector_of_ptr() -> UniquePtr<CxxVector<*mut C>>; + } +} + +fn main() {} diff --git a/tests/ui/ptr_unsupported.stderr b/tests/ui/ptr_unsupported.stderr new file mode 100644 index 00000000..a3e93434 --- /dev/null +++ b/tests/ui/ptr_unsupported.stderr @@ -0,0 +1,17 @@ +error: C++ does not allow pointer to reference as a type + --> $DIR/ptr_unsupported.rs:6:38 + | +6 | fn get_ptr_to_reference() -> *mut &C; + | ^^^^^^^ + +error: unsupported unique_ptr target type + --> $DIR/ptr_unsupported.rs:7:38 + | +7 | fn get_uniqueptr_to_ptr() -> UniquePtr<*mut C>; + | ^^^^^^^^^^^^^^^^^ + +error: unsupported vector element type + --> $DIR/ptr_unsupported.rs:8:45 + | +8 | fn get_vector_of_ptr() -> UniquePtr<CxxVector<*mut C>>; + | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/reference_to_reference.rs b/tests/ui/reference_to_reference.rs index 526d0363..91fe1600 100644 --- a/tests/ui/reference_to_reference.rs +++ b/tests/ui/reference_to_reference.rs @@ -1,6 +1,6 @@ #[cxx::bridge] mod ffi { - extern "C" { + unsafe extern "C++" { type ThingC; fn repro_c(t: &&ThingC); } diff --git a/tests/ui/reserved_name.rs b/tests/ui/reserved_name.rs index 27acca5e..409e67c0 100644 --- a/tests/ui/reserved_name.rs +++ b/tests/ui/reserved_name.rs @@ -4,7 +4,7 @@ mod ffi { val: usize, } - extern "C" { + extern "C++" { type Box; } diff --git a/tests/ui/root_namespace.rs b/tests/ui/root_namespace.rs new file mode 100644 index 00000000..886fbd94 --- /dev/null +++ b/tests/ui/root_namespace.rs @@ -0,0 +1,13 @@ +#[cxx::bridge] +mod ffi { + #[namespace = "::"] + extern "Rust" {} + + #[namespace = ""] + extern "Rust" {} + + #[namespace = ] + extern "Rust" {} +} + +fn main() {} diff --git a/tests/ui/root_namespace.stderr b/tests/ui/root_namespace.stderr new file mode 100644 index 00000000..a9c01a2e --- /dev/null +++ b/tests/ui/root_namespace.stderr @@ -0,0 +1,5 @@ +error: expected expression, found `]` + --> $DIR/root_namespace.rs:9:19 + | +9 | #[namespace = ] + | ^ expected expression diff --git a/tests/ui/rust_pinned.rs b/tests/ui/rust_pinned.rs new file mode 100644 index 00000000..34ca7e33 --- /dev/null +++ b/tests/ui/rust_pinned.rs @@ -0,0 +1,14 @@ +use std::marker::PhantomPinned; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + type Pinned; + } +} + +pub struct Pinned { + _pinned: PhantomPinned, +} + +fn main() {} diff --git a/tests/ui/rust_pinned.stderr b/tests/ui/rust_pinned.stderr new file mode 100644 index 00000000..f16f9d55 --- /dev/null +++ b/tests/ui/rust_pinned.stderr @@ -0,0 +1,10 @@ +error[E0277]: `PhantomPinned` cannot be unpinned + --> $DIR/rust_pinned.rs:6:14 + | +6 | type Pinned; + | -----^^^^^^- + | | | + | | within `Pinned`, the trait `Unpin` is not implemented for `PhantomPinned` + | required by this bound in `__AssertUnpin` + | + = note: required because it appears within the type `Pinned` diff --git a/tests/ui/slice_unsupported.rs b/tests/ui/slice_unsupported.rs new file mode 100644 index 00000000..7a148dd7 --- /dev/null +++ b/tests/ui/slice_unsupported.rs @@ -0,0 +1,10 @@ +#[cxx::bridge] +mod ffi { + unsafe extern "C++" { + type Opaque; + + fn f(_: &mut [Opaque]); + } +} + +fn main() {} diff --git a/tests/ui/slice_unsupported.stderr b/tests/ui/slice_unsupported.stderr new file mode 100644 index 00000000..787076fb --- /dev/null +++ b/tests/ui/slice_unsupported.stderr @@ -0,0 +1,5 @@ +error: unsupported &mut [T] element type: opaque C++ type is not supported yet + --> $DIR/slice_unsupported.rs:6:17 + | +6 | fn f(_: &mut [Opaque]); + | ^^^^^^^^^^^^^ diff --git a/tests/ui/unique_ptr_as_mut.rs b/tests/ui/unique_ptr_as_mut.rs new file mode 100644 index 00000000..d2c758a9 --- /dev/null +++ b/tests/ui/unique_ptr_as_mut.rs @@ -0,0 +1,23 @@ +use cxx::UniquePtr; + +#[cxx::bridge] +mod ffi { + struct Shared { + x: i32, + } + + extern "C++" { + type Opaque; + } + + impl UniquePtr<Shared> {} + impl UniquePtr<Opaque> {} +} + +fn main() { + let mut shared = UniquePtr::<ffi::Shared>::null(); + let _: &mut ffi::Shared = &mut shared; + + let mut opaque = UniquePtr::<ffi::Opaque>::null(); + let _: &mut ffi::Opaque = &mut opaque; +} diff --git a/tests/ui/unique_ptr_as_mut.stderr b/tests/ui/unique_ptr_as_mut.stderr new file mode 100644 index 00000000..a41f716a --- /dev/null +++ b/tests/ui/unique_ptr_as_mut.stderr @@ -0,0 +1,7 @@ +error[E0596]: cannot borrow data in a dereference of `UniquePtr<ffi::Opaque>` as mutable + --> $DIR/unique_ptr_as_mut.rs:22:31 + | +22 | let _: &mut ffi::Opaque = &mut opaque; + | ^^^^^^^^^^^ cannot borrow as mutable + | + = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `UniquePtr<ffi::Opaque>` diff --git a/tests/ui/unique_ptr_to_opaque.rs b/tests/ui/unique_ptr_to_opaque.rs index 720ca691..5226ca81 100644 --- a/tests/ui/unique_ptr_to_opaque.rs +++ b/tests/ui/unique_ptr_to_opaque.rs @@ -11,7 +11,7 @@ mod outside { #[cxx::bridge] mod ffi { - extern "C" { + extern "C++" { type C = crate::outside::C; } diff --git a/tests/ui/unique_ptr_twice.rs b/tests/ui/unique_ptr_twice.rs index b6cb4d42..14c6f62a 100644 --- a/tests/ui/unique_ptr_twice.rs +++ b/tests/ui/unique_ptr_twice.rs @@ -1,6 +1,6 @@ #[cxx::bridge] mod here { - extern "C" { + extern "C++" { type C; } @@ -9,7 +9,7 @@ mod here { #[cxx::bridge] mod there { - extern "C" { + extern "C++" { type C = crate::here::C; } diff --git a/tests/ui/unique_ptr_twice.stderr b/tests/ui/unique_ptr_twice.stderr index 5686cf14..ee26b646 100644 --- a/tests/ui/unique_ptr_twice.stderr +++ b/tests/ui/unique_ptr_twice.stderr @@ -1,4 +1,4 @@ -error[E0119]: conflicting implementations of trait `cxx::private::UniquePtrTarget` for type `here::C`: +error[E0119]: conflicting implementations of trait `cxx::memory::UniquePtrTarget` for type `here::C` --> $DIR/unique_ptr_twice.rs:16:5 | 7 | impl UniquePtr<C> {} diff --git a/tests/ui/unnamed_receiver.rs b/tests/ui/unnamed_receiver.rs index 917e991f..5f53a0a5 100644 --- a/tests/ui/unnamed_receiver.rs +++ b/tests/ui/unnamed_receiver.rs @@ -1,6 +1,6 @@ #[cxx::bridge] mod ffi { - extern "C" { + unsafe extern "C++" { type One; type Two; fn f(&mut self); diff --git a/tests/ui/unpin_impl.rs b/tests/ui/unpin_impl.rs new file mode 100644 index 00000000..129fcb11 --- /dev/null +++ b/tests/ui/unpin_impl.rs @@ -0,0 +1,10 @@ +#[cxx::bridge] +mod ffi { + extern "C++" { + type Opaque; + } +} + +impl Unpin for ffi::Opaque {} + +fn main() {} diff --git a/tests/ui/unpin_impl.stderr b/tests/ui/unpin_impl.stderr new file mode 100644 index 00000000..01ee19c1 --- /dev/null +++ b/tests/ui/unpin_impl.stderr @@ -0,0 +1,8 @@ +error[E0282]: type annotations needed for `fn()` + --> $DIR/unpin_impl.rs:4:14 + | +1 | #[cxx::bridge] + | -------------- consider giving this pattern the explicit type `fn()`, with the type parameters specified +... +4 | type Opaque; + | ^^^^^^ cannot infer type diff --git a/tests/ui/unrecognized_receiver.rs b/tests/ui/unrecognized_receiver.rs index 823bfcf3..eee82593 100644 --- a/tests/ui/unrecognized_receiver.rs +++ b/tests/ui/unrecognized_receiver.rs @@ -1,6 +1,6 @@ #[cxx::bridge] mod ffi { - extern "C" { + unsafe extern "C++" { fn f(self: &Unrecognized); } } diff --git a/tests/ui/unsupported_elided.stderr b/tests/ui/unsupported_elided.stderr index 5136390c..932076d9 100644 --- a/tests/ui/unsupported_elided.stderr +++ b/tests/ui/unsupported_elided.stderr @@ -1,3 +1,9 @@ +error[E0726]: implicit elided lifetime not allowed here + --> $DIR/unsupported_elided.rs:6:14 + | +6 | type T; + | ^- help: indicate the anonymous lifetime: `<'_>` + error[E0106]: missing lifetime specifier --> $DIR/unsupported_elided.rs:8:24 | diff --git a/tests/ui/vec_opaque.rs b/tests/ui/vec_opaque.rs new file mode 100644 index 00000000..d0b279cf --- /dev/null +++ b/tests/ui/vec_opaque.rs @@ -0,0 +1,34 @@ +#[cxx::bridge] +mod handle { + extern "C++" { + type Job; + } +} + +#[cxx::bridge] +mod ffi1 { + extern "C++" { + type Job; + } + + extern "Rust" { + fn f() -> Vec<Job>; + } +} + +#[cxx::bridge] +mod ffi2 { + extern "C++" { + type Job = crate::handle::Job; + } + + extern "Rust" { + fn f() -> Vec<Job>; + } +} + +fn f() -> Vec<handle::Job> { + unimplemented!() +} + +fn main() {} diff --git a/tests/ui/vec_opaque.stderr b/tests/ui/vec_opaque.stderr new file mode 100644 index 00000000..b8dad916 --- /dev/null +++ b/tests/ui/vec_opaque.stderr @@ -0,0 +1,22 @@ +error: Rust Vec containing C++ type is not supported yet + --> $DIR/vec_opaque.rs:15:19 + | +15 | fn f() -> Vec<Job>; + | ^^^^^^^^ + +error: needs a cxx::ExternType impl in order to be used as a vector element in Vec<Job> + --> $DIR/vec_opaque.rs:11:9 + | +11 | type Job; + | ^^^^^^^^ + +error[E0271]: type mismatch resolving `<handle::Job as ExternType>::Kind == Trivial` + --> $DIR/vec_opaque.rs:22:9 + | +22 | type Job = crate::handle::Job; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Trivial`, found enum `cxx::kind::Opaque` + | + ::: $WORKSPACE/src/extern_type.rs + | + | pub fn verify_extern_kind<T: ExternType<Kind = Kind>, Kind: self::Kind>() {} + | ----------- required by this bound in `verify_extern_kind` diff --git a/tests/ui/wrong_type_id.rs b/tests/ui/wrong_type_id.rs index e3d13807..9137e516 100644 --- a/tests/ui/wrong_type_id.rs +++ b/tests/ui/wrong_type_id.rs @@ -1,13 +1,13 @@ #[cxx::bridge(namespace = "folly")] mod here { - extern "C" { + extern "C++" { type StringPiece; } } #[cxx::bridge(namespace = "folly")] mod there { - extern "C" { + extern "C++" { type ByteRange = crate::here::StringPiece; } } diff --git a/tests/unique_ptr.rs b/tests/unique_ptr.rs index e5eb66bc..bbaa3164 100644 --- a/tests/unique_ptr.rs +++ b/tests/unique_ptr.rs @@ -1,3 +1,5 @@ +#![allow(clippy::let_underscore_drop)] + use cxx::{CxxString, UniquePtr}; #[test] diff --git a/third-party/BUCK b/third-party/BUCK index a9a39132..84a5e18b 100644 --- a/third-party/BUCK +++ b/third-party/BUCK @@ -7,7 +7,7 @@ rust_library( rust_library( name = "cc", - srcs = glob(["vendor/cc-1.0.62/src/**"]), + srcs = glob(["vendor/cc-1.0.67/src/**"]), visibility = ["PUBLIC"], ) @@ -25,7 +25,7 @@ rust_library( rust_library( name = "codespan-reporting", - srcs = glob(["vendor/codespan-reporting-0.9.5/src/**"]), + srcs = glob(["vendor/codespan-reporting-0.11.1/src/**"]), visibility = ["PUBLIC"], deps = [ ":termcolor", @@ -41,7 +41,7 @@ rust_library( rust_library( name = "proc-macro2", - srcs = glob(["vendor/proc-macro2-1.0.24/src/**"]), + srcs = glob(["vendor/proc-macro2-1.0.26/src/**"]), visibility = ["PUBLIC"], features = [ "proc-macro", @@ -57,7 +57,7 @@ rust_library( rust_library( name = "quote", - srcs = glob(["vendor/quote-1.0.7/src/**"]), + srcs = glob(["vendor/quote-1.0.9/src/**"]), visibility = ["PUBLIC"], features = ["proc-macro"], deps = [":proc-macro2"], @@ -72,7 +72,7 @@ rust_library( rust_library( name = "syn", - srcs = glob(["vendor/syn-1.0.48/src/**"]), + srcs = glob(["vendor/syn-1.0.68/src/**"]), visibility = ["PUBLIC"], features = [ "clone-impls", @@ -91,7 +91,7 @@ rust_library( rust_library( name = "termcolor", - srcs = glob(["vendor/termcolor-1.1.0/src/**"]), + srcs = glob(["vendor/termcolor-1.1.2/src/**"]), ) rust_library( diff --git a/third-party/BUILD b/third-party/BUILD index d72b0835..cf1a2883 100644 --- a/third-party/BUILD +++ b/third-party/BUILD @@ -1,10 +1,8 @@ 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 = "bitflags", @@ -13,7 +11,7 @@ rust_library( rust_library( name = "cc", - srcs = glob(["vendor/cc-1.0.62/src/**"]), + srcs = glob(["vendor/cc-1.0.67/src/**"]), visibility = ["//visibility:public"], ) @@ -31,7 +29,7 @@ rust_library( rust_library( name = "codespan-reporting", - srcs = glob(["vendor/codespan-reporting-0.9.5/src/**"]), + srcs = glob(["vendor/codespan-reporting-0.11.1/src/**"]), visibility = ["//visibility:public"], deps = [ ":termcolor", @@ -47,7 +45,7 @@ rust_library( rust_library( name = "proc-macro2", - srcs = glob(["vendor/proc-macro2-1.0.24/src/**"]), + srcs = glob(["vendor/proc-macro2-1.0.26/src/**"]), crate_features = [ "proc-macro", "span-locations", @@ -63,7 +61,7 @@ rust_library( rust_library( name = "quote", - srcs = glob(["vendor/quote-1.0.7/src/**"]), + srcs = glob(["vendor/quote-1.0.9/src/**"]), crate_features = ["proc-macro"], visibility = ["//visibility:public"], deps = [":proc-macro2"], @@ -78,7 +76,7 @@ rust_library( rust_library( name = "syn", - srcs = glob(["vendor/syn-1.0.48/src/**"]), + srcs = glob(["vendor/syn-1.0.68/src/**"]), crate_features = [ "clone-impls", "derive", @@ -97,7 +95,7 @@ rust_library( rust_library( name = "termcolor", - srcs = glob(["vendor/termcolor-1.1.0/src/**"]), + srcs = glob(["vendor/termcolor-1.1.2/src/**"]), ) rust_library( diff --git a/third-party/Cargo.lock b/third-party/Cargo.lock index 76b95f0e..a95e737b 100644 --- a/third-party/Cargo.lock +++ b/third-party/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ansi_term" version = "0.11.0" @@ -28,9 +30,12 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "cc" -version = "1.0.62" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +dependencies = [ + "jobserver", +] [[package]] name = "clap" @@ -49,9 +54,9 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.9.5" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0762455306b1ed42bc651ef6a2197aabda5e1d4a43c34d5eab5c1a3634e81d" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ "termcolor", "unicode-width", @@ -59,7 +64,7 @@ dependencies = [ [[package]] name = "cxx" -version = "0.5.9" +version = "1.0.42" dependencies = [ "cc", "cxx-build", @@ -74,7 +79,7 @@ dependencies = [ [[package]] name = "cxx-build" -version = "0.5.9" +version = "1.0.42" dependencies = [ "cc", "codespan-reporting", @@ -89,7 +94,7 @@ dependencies = [ [[package]] name = "cxx-gen" -version = "0.6.6" +version = "0.7.42" dependencies = [ "cc", "codespan-reporting", @@ -109,7 +114,7 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "0.5.9" +version = "1.0.42" dependencies = [ "clap", "codespan-reporting", @@ -120,11 +125,11 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "0.5.9" +version = "1.0.42" [[package]] name = "cxxbridge-macro" -version = "0.5.9" +version = "1.0.42" dependencies = [ "cxx", "proc-macro2", @@ -154,18 +159,27 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "hermit-abi" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] [[package]] name = "itoa" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +dependencies = [ + "libc", +] [[package]] name = "lazy_static" @@ -175,15 +189,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.80" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" [[package]] name = "link-cplusplus" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f96aa785c87218ec773df6c510af203872b34e2df2cf47d6e908e5f36231e354" +checksum = "8f1becd27d473556dc610b8afa1636ef90747b574a84553bc11e82371d5ef2d1" dependencies = [ "cc", ] @@ -196,18 +210,18 @@ checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] @@ -232,18 +246,18 @@ checksum = "7e114536316b51a5aa7a0e59fc49661fd263c5507dd08bd28de052e57626ce69" [[package]] name = "serde" -version = "1.0.117" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.117" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ "proc-macro2", "quote", @@ -252,9 +266,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.59" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "itoa", "ryu", @@ -269,9 +283,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.48" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" dependencies = [ "proc-macro2", "quote", @@ -280,9 +294,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ "winapi-util", ] @@ -298,18 +312,18 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] [[package]] name = "trybuild" -version = "1.0.35" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7d30fe369fd650072b352b1a9cb9587669de6b89be3b8225544012c1c45292d" +checksum = "99471a206425fba51842a9186315f32d91c56eadc21ea4c21f847b59cf778f8b" dependencies = [ "dissimilar", "glob", diff --git a/tools/bazel/BUILD b/tools/bazel/BUILD index e69de29b..d42fc71c 100644 --- a/tools/bazel/BUILD +++ b/tools/bazel/BUILD @@ -0,0 +1,7 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "bzl_srcs", + srcs = glob(["**/*.bzl"]), + visibility = ["//visibility:public"], +) diff --git a/tools/bazel/rust.bzl b/tools/bazel/rust.bzl index b7b23e55..9f71923a 100644 --- a/tools/bazel/rust.bzl +++ b/tools/bazel/rust.bzl @@ -1,5 +1,7 @@ +"""A module wrapping the core rules of `rules_rust`""" + load( - "@io_bazel_rules_rust//rust:rust.bzl", + "@rules_rust//rust:rust.bzl", _rust_binary = "rust_binary", _rust_library = "rust_library", _rust_test = "rust_test", diff --git a/tools/bazel/rust_cxx_bridge.bzl b/tools/bazel/rust_cxx_bridge.bzl index 534f6b5a..c7d07e8a 100644 --- a/tools/bazel/rust_cxx_bridge.bzl +++ b/tools/bazel/rust_cxx_bridge.bzl @@ -1,7 +1,15 @@ +# buildifier: disable=module-docstring load("@bazel_skylib//rules:run_binary.bzl", "run_binary") load("@rules_cc//cc:defs.bzl", "cc_library") def rust_cxx_bridge(name, src, deps = []): + """A macro defining a cxx bridge library + + Args: + name (string): The name of the new target + src (string): The rust source file to generate a bridge for + deps (list, optional): A list of dependencies for the underlying cc_library. Defaults to []. + """ native.alias( name = "%s/header" % name, actual = src + ".h", @@ -26,7 +34,7 @@ def rust_cxx_bridge(name, src, deps = []): "-o", "$(location %s.cc)" % src, ], - tool = "//:codegen", + tool = "@cxx.rs//:codegen", ) cc_library( diff --git a/tools/bazel/vendor.bzl b/tools/bazel/vendor.bzl index e9f10ac6..e404ad99 100644 --- a/tools/bazel/vendor.bzl +++ b/tools/bazel/vendor.bzl @@ -1,3 +1,7 @@ +"""A module defining a repository rule for vendoring the dependencies +of a crate in the current workspace. +""" + def _impl(repository_ctx): # Link cxx repository into @third-party. lockfile = repository_ctx.path(repository_ctx.attr.lockfile) @@ -48,7 +52,12 @@ def _log_cargo_vendor(repository_ctx, result): repository_ctx.execute(print, quiet = False) vendor = repository_rule( - attrs = {"lockfile": attr.label()}, + doc = "A rule used to vendor the dependencies of a crate in the current workspace", + attrs = { + "lockfile": attr.label( + doc = "A lockfile providing the set of crates to vendor", + ), + }, local = True, implementation = _impl, ) diff --git a/tools/cargo/build.rs b/tools/cargo/build.rs index 6c9d22d8..401c7418 100644 --- a/tools/cargo/build.rs +++ b/tools/cargo/build.rs @@ -58,11 +58,11 @@ fn main() { #[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() + let original_dir = parent_dir.join("original"); + let link_dir = parent_dir.join("link"); + if fs::create_dir_all(&original_dir).is_ok() + && (!link_dir.exists() || fs::remove_dir(&link_dir).is_ok()) + && windows::symlink_dir(&original_dir, &link_dir).is_err() { message = DENIED; } |