mirror of
https://gitee.com/openharmony/third_party_rust_cxx
synced 2024-11-27 01:11:38 +00:00
Safe FFI between Rust and C++
This commit is contained in:
commit
7db7369797
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/Cargo.lock
|
||||
/expand.cc
|
||||
/expand.rs
|
||||
/target
|
3
.travis.yml
Normal file
3
.travis.yml
Normal file
@ -0,0 +1,3 @@
|
||||
language: rust
|
||||
rust: nightly
|
||||
script: cargo run --manifest-path demo-rs/Cargo.toml
|
35
Cargo.toml
Normal file
35
Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "cxx"
|
||||
version = "0.0.0"
|
||||
authors = ["David Tolnay <dtolnay@gmail.com>"]
|
||||
edition = "2018"
|
||||
links = "cxxbridge00"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Safe interop between Rust and C++"
|
||||
repository = "https://github.com/dtolnay/cxx"
|
||||
documentation = "https://docs.rs/cxx"
|
||||
readme = "README.md"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "dtolnay/cxx" }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
cc = "1.0.49"
|
||||
codespan = "0.7"
|
||||
codespan-reporting = "0.7"
|
||||
cxxbridge-macro = { version = "0.0", path = "macro" }
|
||||
proc-macro2 = { version = "1.0", features = ["span-locations"] }
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.49"
|
||||
|
||||
[dev-dependencies]
|
||||
rustversion = "1.0"
|
||||
trybuild = "1.0"
|
||||
|
||||
[workspace]
|
||||
members = ["cmd", "demo-rs", "macro"]
|
1
FUNDING.yml
Normal file
1
FUNDING.yml
Normal file
@ -0,0 +1 @@
|
||||
github: dtolnay
|
201
LICENSE-APACHE
Normal file
201
LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
23
LICENSE-MIT
Normal file
23
LICENSE-MIT
Normal file
@ -0,0 +1,23 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
361
README.md
Normal file
361
README.md
Normal file
@ -0,0 +1,361 @@
|
||||
CXX — safe FFI between Rust and C++
|
||||
=========================================
|
||||
|
||||
[![Build Status](https://api.travis-ci.com/dtolnay/cxx.svg?branch=master)](https://travis-ci.com/dtolnay/cxx)
|
||||
[![Latest Version](https://img.shields.io/crates/v/cxx.svg)](https://crates.io/crates/cxx)
|
||||
[![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://docs.rs/cxx)
|
||||
|
||||
This library provides a **safe** mechanism for calling C++ code from Rust and
|
||||
Rust code from C++, not subject to the many ways that things can go wrong when
|
||||
using bindgen or cbindgen to generate unsafe C-style bindings.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
cxx = "0.0"
|
||||
```
|
||||
|
||||
*Compiler support: requires rustc 1.42+ (beta on January 30, stable on March
|
||||
12)*
|
||||
|
||||
<br>
|
||||
|
||||
## Overview
|
||||
|
||||
The idea is that we define the signatures of both sides of our FFI boundary
|
||||
embedded together in one Rust module (the next section shows an example). From
|
||||
this, CXX receives a complete picture of the boundary to perform static analyses
|
||||
against the types and function signatures to uphold both Rust's and C++'s
|
||||
invariants and requirements.
|
||||
|
||||
If everything checks out statically, then CXX uses a pair of code generators to
|
||||
emit the relevant `extern "C"` signatures on both sides together with any
|
||||
necessary static assertions for later in the build process to verify
|
||||
correctness. On the Rust side this code generator is simply an attribute
|
||||
procedural macro. On the C++ side it can be a small Cargo build script if your
|
||||
build is managed by Cargo, or for other build systems like Bazel or Buck we
|
||||
provide a command line tool which generates the header and source file and
|
||||
should be easy to integrate.
|
||||
|
||||
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 types from whichever side they please,
|
||||
such as Rust's `String` or C++'s `std::string`, Rust's `Box` or C++'s
|
||||
`std::unique_ptr`, Rust's `Vec` or C++'s `std::vector`, etc in any combination.
|
||||
CXX guarantees an ABI-compatible signature that both sides understand, based on
|
||||
builtin bindings for key standard library types to expose an idiomatic API on
|
||||
those types to the other language. For example when manipulating a C++ string
|
||||
from Rust, its `len()` method becomes a call of the `size()` member function
|
||||
defined by C++; when manipulation a Rust string from C++, its `size()` member
|
||||
function calls Rust's `len()`.
|
||||
|
||||
<br>
|
||||
|
||||
## Example
|
||||
|
||||
A runnable version of this example is provided under the *demo-rs* directory of
|
||||
this repo (with the C++ side of the implementation in the *demo-cxx* directory).
|
||||
To try it out, jump into demo-rs and run `cargo run`.
|
||||
|
||||
```rust
|
||||
#[cxx::bridge]
|
||||
mod ffi {
|
||||
// Any shared structs, whose fields will be visible to both languages.
|
||||
struct SharedThing {
|
||||
z: i32,
|
||||
y: Box<ThingR>,
|
||||
x: UniquePtr<ThingC>,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// One or more headers with the matching C++ declarations. Our code
|
||||
// generators don't read it but it gets #include'd and used in static
|
||||
// assertions to ensure our picture of the FFI boundary is accurate.
|
||||
include!("demo-cxx/demo.h");
|
||||
|
||||
// Zero or more opaque types which both languages can pass around but
|
||||
// only C++ can see the fields.
|
||||
type ThingC;
|
||||
|
||||
// Functions implemented in C++.
|
||||
fn make_demo(appname: &str) -> UniquePtr<ThingC>;
|
||||
fn get_name(thing: &ThingC) -> &CxxString;
|
||||
fn do_thing(state: SharedThing);
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
// Zero or more opaque types which both languages can pass around but
|
||||
// only Rust can see the fields.
|
||||
type ThingR;
|
||||
|
||||
// Functions implemented in Rust.
|
||||
fn print_r(r: &ThingR);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we simply provide C++ definitions of all the things in the `extern "C"`
|
||||
block and Rust definitions of all the things in the `extern "Rust"` block, and
|
||||
get to call back and forth safely.
|
||||
|
||||
Here are links to the complete set of source files involved in the demo:
|
||||
|
||||
- [demo-rs/src/main.rs](demo-rs/src/main.rs)
|
||||
- [demo-rs/build.rs](demo-rs/build.rs)
|
||||
- [demo-cxx/demo.h](demo-cxx/demo.h)
|
||||
- [demo-cxx/demo.cc](demo-cxx/demo.cc)
|
||||
|
||||
To look at the code generated in both languages for the example by the CXX code
|
||||
generators:
|
||||
|
||||
```console
|
||||
# run Rust code generator and print to stdout
|
||||
# (requires https://github.com/dtolnay/cargo-expand)
|
||||
$ cargo expand --manifest-path demo-rs/Cargo.toml
|
||||
|
||||
# run C++ code generator and print to stdout
|
||||
$ cargo run --manifest-path cmd/Cargo.toml -- demo-rs/src/main.rs
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Details
|
||||
|
||||
As seen in the example, the language of the FFI boundary involves 3 kinds of
|
||||
items:
|
||||
|
||||
- **Shared structs** — their fields are made visible to both languages.
|
||||
The definition written within cxx::bridge is the single 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 `UniquePtr`. 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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
where necessary to make it all work.
|
||||
|
||||
<br>
|
||||
|
||||
## Comparison vs bindgen and cbindgen
|
||||
|
||||
Notice that with CXX there is repetition of all the function signatures: they
|
||||
are typed out once where the implementation is defined (in C++ or Rust) and
|
||||
again inside the cxx::bridge module, though compile-time assertions guarantee
|
||||
these are kept in sync. This is different from [bindgen] and [cbindgen] where
|
||||
function signatures are typed by a human once and the tool consumes them in one
|
||||
language and emits them in the other language.
|
||||
|
||||
[bindgen]: https://github.com/rust-lang/rust-bindgen
|
||||
[cbindgen]: https://github.com/eqrion/cbindgen/
|
||||
|
||||
This is because CXX fills a somewhat different role. It is a lower level tool
|
||||
than bindgen or cbindgen in a sense; you can think of it as being a replacement
|
||||
for the concept of `extern "C"` signatures as we know them, rather than a
|
||||
replacement for a bindgen. It would be reasonable to build a higher level
|
||||
bindgen-like tool on top of CXX which consumes a C++ header and/or Rust module
|
||||
(and/or IDL like Thrift) as source of truth and generates the cxx::bridge,
|
||||
eliminating the repetition while leveraging the static analysis safety
|
||||
guarantees of CXX.
|
||||
|
||||
But note in other ways CXX is higher level than the bindgens, with rich support
|
||||
for common standard library types. Frequently with bindgen when we are dealing
|
||||
with an idiomatic C++ API we would end up manually wrapping that API in C-style
|
||||
raw pointer functions, applying bindgen to get unsafe raw pointer Rust
|
||||
functions, and replicating the API again to expose those idiomatically in Rust.
|
||||
That's a much worse form of repetition because it is unsafe all the way through.
|
||||
|
||||
By using a CXX bridge as the shared understanding between the languages, rather
|
||||
than `extern "C"` C-style signatures as the shared understanding, common FFI use
|
||||
cases become expressible using 100% safe code.
|
||||
|
||||
It would also be reasonable to mix and match, using CXX bridge for the 95% of
|
||||
your FFI that is straightforward and doing the remaining few oddball signatures
|
||||
the old fashioned way with bindgen and cbindgen, if for some reason CXX's static
|
||||
restrictions get in the way. Please file an issue if you end up taking this
|
||||
approach so that we know what ways it would be worthwhile to make the tool more
|
||||
expressive.
|
||||
|
||||
<br>
|
||||
|
||||
## Cargo-based setup
|
||||
|
||||
For builds that are orchestrated by Cargo, you will use a build script that runs
|
||||
CXX's C++ code generator and compiles the resulting C++ code along with any
|
||||
other C++ code for your crate.
|
||||
|
||||
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
|
||||
// build.rs
|
||||
|
||||
fn main() {
|
||||
cxx::Build::new()
|
||||
.bridge("src/main.rs") // returns a cc::Build
|
||||
.file("../demo-cxx/demo.cc")
|
||||
.flag("-std=c++11")
|
||||
.compile("cxxbridge-demo");
|
||||
|
||||
println!("cargo:rerun-if-changed=src/main.rs");
|
||||
println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
|
||||
println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
|
||||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Non-Cargo setup
|
||||
|
||||
For use in non-Cargo builds like Bazel or Buck, CXX provides an alternate way of
|
||||
invoking the C++ code generator as a standalone command line tool. The tool is
|
||||
packaged as the `cxxbridge-cmd` crate on crates.io or can be built from the
|
||||
*cmd* directory of this repo.
|
||||
|
||||
```bash
|
||||
$ cargo install cxxbridge-cmd
|
||||
|
||||
$ cxxbridge src/main.rs --header > path/to/mybridge.h
|
||||
$ cxxbridge src/main.rs > path/to/mybridge.cc
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Safety
|
||||
|
||||
Be aware that the design of this library is intentionally restrictive and
|
||||
opinionated! It isn't a goal to be powerful enough to handle arbitrary
|
||||
signatures in either language. Instead this project is about carving out a
|
||||
reasonably expressive set of functionality about which we can make useful safety
|
||||
guarantees today and maybe 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
|
||||
are used to.
|
||||
|
||||
Some of the considerations that go into ensuring safety are:
|
||||
|
||||
- By design, our paired code generators work together to control both sides of
|
||||
the FFI boundary. Ordinarily in Rust writing your own `extern "C"` blocks is
|
||||
unsafe because the Rust compiler has no way to know whether the signatures
|
||||
you've written actually match the signatures implemented in the other
|
||||
language. With CXX we achieve that visibility and know what's on the other
|
||||
side.
|
||||
|
||||
- Our static analysis detects and prevents passing types by value that shouldn't
|
||||
be passed by value from C++ to Rust, for example because they may contain
|
||||
internal pointers that would be screwed up by Rust's move behavior.
|
||||
|
||||
- To many people's surprise, it is possible to have a struct in Rust and a
|
||||
struct in C++ with exactly the same layout / fields / alignment / everything,
|
||||
and still not the same ABI when passed by value. This is a longstanding
|
||||
bindgen bug that leads to segfaults in absolutely correct-looking code
|
||||
([rust-lang/rust-bindgen#778]). CXX knows about this and can insert the
|
||||
necessary zero-cost workaround transparently where needed, so go ahead and
|
||||
pass your structs by value without worries. This is made possible by owning
|
||||
both sides of the boundary rather than just one.
|
||||
|
||||
- Template instantiations: for example in order to expose a UniquePtr\<T\> type
|
||||
in Rust backed by a real C++ unique\_ptr, we have a way of using a Rust trait
|
||||
to connect the behavior back to the template instantiations performed by the
|
||||
other language.
|
||||
|
||||
[rust-lang/rust-bindgen#778]: https://github.com/rust-lang/rust-bindgen/issues/778
|
||||
|
||||
<br>
|
||||
|
||||
## Builtin types
|
||||
|
||||
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 functions.
|
||||
|
||||
<table>
|
||||
<tr><th>name in Rust</th><th>name in C++</th><th>restrictions</th></tr>
|
||||
<tr><td>String</td><td>cxxbridge::RustString</td><td></td></tr>
|
||||
<tr><td>&str</td><td>cxxbridge::RustStr</td><td></td></tr>
|
||||
<tr><td><a href="https://docs.rs/cxx/0.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>cxxbridge::RustBox<T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
|
||||
<tr><td><a href="https://docs.rs/cxx/0.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></td><td></td><td></td></tr>
|
||||
</table>
|
||||
|
||||
The C++ API of the `cxxbridge` namespace is defined by the *include/cxxbridge.h*
|
||||
file in this repo. You will need to include this header in your C++ code when
|
||||
working with those types.
|
||||
|
||||
The following types are intended to be supported "soon" but are just not
|
||||
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.
|
||||
|
||||
<table>
|
||||
<tr><th>name in Rust</th><th>name in C++</th></tr>
|
||||
<tr><td>&[T]</td><td></td></tr>
|
||||
<tr><td>Vec<T></td><td></td></tr>
|
||||
<tr><td>BTreeMap<K, V></td><td></td></tr>
|
||||
<tr><td>HashMap<K, V></td><td></td></tr>
|
||||
<tr><td></td><td>std::vector<T></td></tr>
|
||||
<tr><td></td><td>std::map<K, V></td></tr>
|
||||
<tr><td></td><td>std::unordered_map<K, V></td></tr>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
|
||||
## Remaining work
|
||||
|
||||
This is still early days for CXX; I am releasing it as a minimum viable product
|
||||
to collect feedback on the direction and invite collaborators. Here are some of
|
||||
the facets that I still intend for this project to tackle:
|
||||
|
||||
- [ ] Support associated methods: `extern "Rust" { fn f(self: &Struct); }`
|
||||
- [ ] Support C++ member functions
|
||||
- [ ] Support passing function pointers across the FFI
|
||||
- [ ] Support translating between Result ⟷ exceptions
|
||||
- [ ] Support structs with type parameters
|
||||
- [ ] Support async functions
|
||||
|
||||
On the build side, I don't have much experience with the `cc` crate so I expect
|
||||
there may be someone who can suggest ways to make that aspect of this crate
|
||||
friendlier or more robust. Please report issues if you run into trouble building
|
||||
or linking any of this stuff.
|
||||
|
||||
Finally, I know more about Rust library design than C++ library design so I
|
||||
would appreciate help making the C++ APIs in this project more idiomatic where
|
||||
anyone has suggestions.
|
||||
|
||||
<br>
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in this project by you, as defined in the Apache-2.0 license,
|
||||
shall be dual licensed as above, without any additional terms or conditions.
|
||||
</sub>
|
9
build.rs
Normal file
9
build.rs
Normal file
@ -0,0 +1,9 @@
|
||||
fn main() {
|
||||
cc::Build::new()
|
||||
.file("src/cxxbridge.cc")
|
||||
.flag("-std=c++11")
|
||||
.compile("cxxbridge00");
|
||||
println!("cargo:rustc-flags=-l dylib=stdc++");
|
||||
println!("cargo:rerun-if-changed=src/cxxbridge.cc");
|
||||
println!("cargo:rerun-if-changed=include/cxxbridge.h");
|
||||
}
|
25
cmd/Cargo.toml
Normal file
25
cmd/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "cxxbridge-cmd"
|
||||
version = "0.0.0"
|
||||
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"
|
||||
|
||||
[[bin]]
|
||||
name = "cxxbridge"
|
||||
path = "src/main.rs"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "dtolnay/cxx" }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
codespan = "0.7"
|
||||
codespan-reporting = "0.7"
|
||||
proc-macro2 = { version = "1.0", features = ["span-locations"] }
|
||||
quote = "1.0"
|
||||
structopt = "0.3"
|
||||
syn = { version = "1.0", features = ["full"] }
|
||||
thiserror = "1.0"
|
1
cmd/src/gen
Symbolic link
1
cmd/src/gen
Symbolic link
@ -0,0 +1 @@
|
||||
../../gen
|
29
cmd/src/main.rs
Normal file
29
cmd/src/main.rs
Normal file
@ -0,0 +1,29 @@
|
||||
mod gen;
|
||||
mod syntax;
|
||||
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(name = "cxxbridge", author)]
|
||||
struct Opt {
|
||||
/// Input Rust source file containing #[cxx::bridge]
|
||||
#[structopt(parse(from_os_str))]
|
||||
input: PathBuf,
|
||||
|
||||
/// Emit header with declarations only
|
||||
#[structopt(long)]
|
||||
header: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opt = Opt::from_args();
|
||||
let gen = if opt.header {
|
||||
gen::do_generate_header
|
||||
} else {
|
||||
gen::do_generate_bridge
|
||||
};
|
||||
let bridge = gen(&opt.input);
|
||||
let _ = io::stdout().lock().write_all(bridge.as_ref());
|
||||
}
|
1
cmd/src/syntax
Symbolic link
1
cmd/src/syntax
Symbolic link
@ -0,0 +1 @@
|
||||
../../syntax
|
21
demo-cxx/demo.cc
Normal file
21
demo-cxx/demo.cc
Normal file
@ -0,0 +1,21 @@
|
||||
#include "demo-cxx/demo.h"
|
||||
#include "demo-rs/src/main.rs"
|
||||
#include <iostream>
|
||||
|
||||
namespace org {
|
||||
namespace rust {
|
||||
|
||||
ThingC::ThingC(std::string appname) : appname(std::move(appname)) {}
|
||||
|
||||
ThingC::~ThingC() { std::cout << "done with ThingC" << std::endl; }
|
||||
|
||||
std::unique_ptr<ThingC> make_demo(cxxbridge::RustStr appname) {
|
||||
return std::unique_ptr<ThingC>(new ThingC(appname));
|
||||
}
|
||||
|
||||
const std::string &get_name(const ThingC &thing) { return thing.appname; }
|
||||
|
||||
void do_thing(SharedThing state) { print_r(*state.y); }
|
||||
|
||||
} // namespace rust
|
||||
} // namespace org
|
24
demo-cxx/demo.h
Normal file
24
demo-cxx/demo.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "../include/cxxbridge.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace org {
|
||||
namespace rust {
|
||||
|
||||
class ThingC {
|
||||
public:
|
||||
ThingC(std::string appname);
|
||||
~ThingC();
|
||||
|
||||
std::string appname;
|
||||
};
|
||||
|
||||
struct SharedThing;
|
||||
|
||||
std::unique_ptr<ThingC> make_demo(cxxbridge::RustStr appname);
|
||||
const std::string &get_name(const ThingC &thing);
|
||||
void do_thing(SharedThing state);
|
||||
|
||||
} // namespace rust
|
||||
} // namespace org
|
12
demo-rs/Cargo.toml
Normal file
12
demo-rs/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "cxxbridge-demo"
|
||||
version = "0.0.0"
|
||||
authors = ["David Tolnay <dtolnay@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
cxx = { path = ".." }
|
||||
|
||||
[build-dependencies]
|
||||
cxx = { path = ".." }
|
11
demo-rs/build.rs
Normal file
11
demo-rs/build.rs
Normal file
@ -0,0 +1,11 @@
|
||||
fn main() {
|
||||
cxx::Build::new()
|
||||
.bridge("src/main.rs")
|
||||
.file("../demo-cxx/demo.cc")
|
||||
.flag("-std=c++11")
|
||||
.compile("cxxbridge-demo");
|
||||
|
||||
println!("cargo:rerun-if-changed=src/main.rs");
|
||||
println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
|
||||
println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
|
||||
}
|
39
demo-rs/src/main.rs
Normal file
39
demo-rs/src/main.rs
Normal file
@ -0,0 +1,39 @@
|
||||
#[cxx::bridge(namespace = org::rust)]
|
||||
mod ffi {
|
||||
struct SharedThing {
|
||||
z: i32,
|
||||
y: Box<ThingR>,
|
||||
x: UniquePtr<ThingC>,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
include!("demo-cxx/demo.h");
|
||||
|
||||
type ThingC;
|
||||
fn make_demo(appname: &str) -> UniquePtr<ThingC>;
|
||||
fn get_name(thing: &ThingC) -> &CxxString;
|
||||
fn do_thing(state: SharedThing);
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type ThingR;
|
||||
fn print_r(r: &ThingR);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ThingR(usize);
|
||||
|
||||
fn print_r(r: &ThingR) {
|
||||
println!("called back with r={}", r.0);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = ffi::make_demo("demo of cxx::bridge");
|
||||
println!("this is a {}", ffi::get_name(x.as_ref().unwrap()));
|
||||
|
||||
ffi::do_thing(ffi::SharedThing {
|
||||
z: 222,
|
||||
y: Box::new(ThingR(333)),
|
||||
x,
|
||||
});
|
||||
}
|
77
gen/error.rs
Normal file
77
gen/error.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use crate::gen::Error;
|
||||
use crate::syntax;
|
||||
use anyhow::anyhow;
|
||||
use codespan::{FileId, Files};
|
||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream, WriteColor};
|
||||
use codespan_reporting::term::{self, Config};
|
||||
use std::io::Write;
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
|
||||
pub(super) fn format_err(path: &Path, source: &str, error: Error) -> ! {
|
||||
match error {
|
||||
Error::Syn(syn_error) => {
|
||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
||||
let ref mut stderr = writer.lock();
|
||||
for error in syn_error {
|
||||
let _ = writeln!(stderr);
|
||||
display_syn_error(stderr, path, source, error);
|
||||
}
|
||||
}
|
||||
_ => eprintln!("cxxbridge: {:?}", anyhow!(error)),
|
||||
}
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
fn display_syn_error(stderr: &mut dyn WriteColor, path: &Path, source: &str, error: syn::Error) {
|
||||
let span = error.span();
|
||||
let start = span.start();
|
||||
let end = span.end();
|
||||
|
||||
let mut start_offset = 0;
|
||||
for _ in 1..start.line {
|
||||
start_offset += source[start_offset..].find('\n').unwrap() + 1;
|
||||
}
|
||||
start_offset += start.column;
|
||||
|
||||
let mut end_offset = start_offset;
|
||||
if start.line == end.line {
|
||||
end_offset -= start.column;
|
||||
} else {
|
||||
for _ in 0..end.line - start.line {
|
||||
end_offset += source[end_offset..].find('\n').unwrap() + 1;
|
||||
}
|
||||
}
|
||||
end_offset += end.column;
|
||||
|
||||
let mut files = Files::new();
|
||||
let file = files.add(path.to_string_lossy(), source);
|
||||
|
||||
let range = start_offset as u32..end_offset as u32;
|
||||
let diagnostic = diagnose(file, range, error);
|
||||
|
||||
let config = Config::default();
|
||||
let _ = term::emit(stderr, &config, &files, &diagnostic);
|
||||
}
|
||||
|
||||
fn diagnose(file: FileId, range: Range<u32>, error: syn::Error) -> Diagnostic {
|
||||
let message = error.to_string();
|
||||
let info = syntax::error::ERRORS
|
||||
.iter()
|
||||
.find(|e| message.contains(e.msg));
|
||||
let mut diagnostic = if let Some(info) = info {
|
||||
let label = Label::new(file, range, info.label.unwrap_or(&message));
|
||||
let mut diagnostic = Diagnostic::new_error(&message, label);
|
||||
if let Some(note) = info.note {
|
||||
diagnostic = diagnostic.with_notes(vec![note.to_owned()]);
|
||||
}
|
||||
diagnostic
|
||||
} else {
|
||||
let label = Label::new(file, range, &message);
|
||||
Diagnostic::new_error(&message, label)
|
||||
};
|
||||
diagnostic.code = Some("cxxbridge".to_owned());
|
||||
diagnostic
|
||||
}
|
1
gen/include
Symbolic link
1
gen/include
Symbolic link
@ -0,0 +1 @@
|
||||
../include
|
13
gen/include.rs
Normal file
13
gen/include.rs
Normal file
@ -0,0 +1,13 @@
|
||||
static HEADER: &str = include_str!("include/cxxbridge.h");
|
||||
|
||||
pub fn get(guard: &str) -> &'static str {
|
||||
let ifndef = format!("#ifndef {}\n", guard);
|
||||
let endif = format!("#endif // {}\n", guard);
|
||||
let begin = HEADER.find(&ifndef);
|
||||
let end = HEADER.find(&endif);
|
||||
if let (Some(begin), Some(end)) = (begin, end) {
|
||||
&HEADER[begin..end + endif.len()]
|
||||
} else {
|
||||
panic!("not found in cxxbridge.h header: {}", guard)
|
||||
}
|
||||
}
|
114
gen/mod.rs
Normal file
114
gen/mod.rs
Normal file
@ -0,0 +1,114 @@
|
||||
// Functionality that is shared between the cxx::generate_bridge entry point and
|
||||
// the cmd.
|
||||
|
||||
mod error;
|
||||
pub(super) mod include;
|
||||
pub(super) mod out;
|
||||
mod write;
|
||||
|
||||
use self::error::format_err;
|
||||
use self::out::OutFile;
|
||||
use crate::syntax::{self, check, ident, Types};
|
||||
use quote::quote;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use syn::parse::ParseStream;
|
||||
use syn::{Attribute, File, Item, Token};
|
||||
use thiserror::Error;
|
||||
|
||||
pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(super) enum Error {
|
||||
#[error("no #[cxx::bridge] module found")]
|
||||
NoBridgeMod,
|
||||
#[error("#[cxx::bridge] module must have inline contents")]
|
||||
OutOfLineMod,
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
Syn(#[from] syn::Error),
|
||||
}
|
||||
|
||||
struct Input {
|
||||
namespace: Vec<String>,
|
||||
module: Vec<Item>,
|
||||
}
|
||||
|
||||
pub(super) fn do_generate_bridge(path: &Path) -> OutFile {
|
||||
let header = false;
|
||||
generate(path, header)
|
||||
}
|
||||
|
||||
pub(super) fn do_generate_header(path: &Path) -> OutFile {
|
||||
let header = true;
|
||||
generate(path, header)
|
||||
}
|
||||
|
||||
fn generate(path: &Path, header: bool) -> OutFile {
|
||||
let source = match fs::read_to_string(path) {
|
||||
Ok(source) => source,
|
||||
Err(err) => format_err(path, "", Error::Io(err)),
|
||||
};
|
||||
match (|| -> Result<_> {
|
||||
let syntax = syn::parse_file(&source)?;
|
||||
let bridge = find_bridge_mod(syntax)?;
|
||||
let apis = syntax::parse_items(bridge.module)?;
|
||||
let types = Types::collect(&apis)?;
|
||||
check::typecheck(&apis, &types)?;
|
||||
let out = write::gen(bridge.namespace, &apis, &types, header);
|
||||
Ok(out)
|
||||
})() {
|
||||
Ok(out) => out,
|
||||
Err(err) => format_err(path, &source, err),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_bridge_mod(syntax: File) -> Result<Input> {
|
||||
for item in syntax.items {
|
||||
if let Item::Mod(item) = item {
|
||||
for attr in &item.attrs {
|
||||
let path = &attr.path;
|
||||
if quote!(#path).to_string() == "cxx :: bridge" {
|
||||
let module = match item.content {
|
||||
Some(module) => module.1,
|
||||
None => {
|
||||
return Err(Error::Syn(syn::Error::new_spanned(
|
||||
item,
|
||||
Error::OutOfLineMod,
|
||||
)));
|
||||
}
|
||||
};
|
||||
return Ok(Input {
|
||||
namespace: parse_args(attr)?,
|
||||
module,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::NoBridgeMod)
|
||||
}
|
||||
|
||||
fn parse_args(attr: &Attribute) -> syn::Result<Vec<String>> {
|
||||
if attr.tokens.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
attr.parse_args_with(|input: ParseStream| {
|
||||
mod kw {
|
||||
syn::custom_keyword!(namespace);
|
||||
}
|
||||
input.parse::<kw::namespace>()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
let path = syn::Path::parse_mod_style(input)?;
|
||||
input.parse::<Option<Token![,]>>()?;
|
||||
path.segments
|
||||
.into_iter()
|
||||
.map(|seg| {
|
||||
ident::check(&seg.ident)?;
|
||||
Ok(seg.ident.to_string())
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
74
gen/out.rs
Normal file
74
gen/out.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::fmt::{self, Arguments, Write};
|
||||
|
||||
pub(crate) struct OutFile {
|
||||
pub namespace: Vec<String>,
|
||||
pub header: bool,
|
||||
content: Vec<u8>,
|
||||
section_pending: bool,
|
||||
block: &'static str,
|
||||
block_pending: bool,
|
||||
}
|
||||
|
||||
impl OutFile {
|
||||
pub fn new(namespace: Vec<String>, header: bool) -> Self {
|
||||
OutFile {
|
||||
namespace,
|
||||
header,
|
||||
content: Vec::new(),
|
||||
section_pending: false,
|
||||
block: "",
|
||||
block_pending: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Write a blank line if the preceding section had any contents.
|
||||
pub fn next_section(&mut self) {
|
||||
self.section_pending = true;
|
||||
}
|
||||
|
||||
pub fn begin_block(&mut self, block: &'static str) {
|
||||
self.block = block;
|
||||
self.block_pending = true;
|
||||
}
|
||||
|
||||
pub fn end_block(&mut self) {
|
||||
if self.block_pending {
|
||||
self.block_pending = false;
|
||||
} else {
|
||||
self.content.extend_from_slice(b"} // ");
|
||||
self.content.extend_from_slice(self.block.as_bytes());
|
||||
self.content.push(b'\n');
|
||||
self.block = "";
|
||||
self.section_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_fmt(&mut self, args: Arguments) {
|
||||
Write::write_fmt(self, args).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for OutFile {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
if !s.is_empty() {
|
||||
if self.block_pending {
|
||||
self.content.push(b'\n');
|
||||
self.content.extend_from_slice(self.block.as_bytes());
|
||||
self.content.extend_from_slice(b" {\n");
|
||||
self.block_pending = false;
|
||||
self.section_pending = false;
|
||||
} else if self.section_pending {
|
||||
self.content.push(b'\n');
|
||||
self.section_pending = false;
|
||||
}
|
||||
self.content.extend_from_slice(s.as_bytes());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for OutFile {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.content
|
||||
}
|
||||
}
|
623
gen/write.rs
Normal file
623
gen/write.rs
Normal file
@ -0,0 +1,623 @@
|
||||
use crate::gen::include;
|
||||
use crate::gen::out::OutFile;
|
||||
use crate::syntax::atom::Atom::{self, *};
|
||||
use crate::syntax::{Api, ExternFn, Struct, Type, Types, Var};
|
||||
use proc_macro2::Ident;
|
||||
|
||||
pub(super) fn gen(namespace: Vec<String>, apis: &[Api], types: &Types, header: bool) -> OutFile {
|
||||
let mut out_file = OutFile::new(namespace.clone(), header);
|
||||
let out = &mut out_file;
|
||||
|
||||
if header {
|
||||
writeln!(out, "#pragma once");
|
||||
}
|
||||
|
||||
for api in apis {
|
||||
if let Api::Include(include) = api {
|
||||
writeln!(out, "#include \"{}\"", include.value().escape_default());
|
||||
}
|
||||
}
|
||||
|
||||
write_includes(out, types);
|
||||
write_include_cxxbridge(out, types);
|
||||
|
||||
if !header {
|
||||
out.next_section();
|
||||
write_namespace_alias(out, types);
|
||||
}
|
||||
|
||||
out.next_section();
|
||||
for name in &namespace {
|
||||
writeln!(out, "namespace {} {{", name);
|
||||
}
|
||||
|
||||
if header {
|
||||
out.next_section();
|
||||
write_namespace_alias(out, types);
|
||||
}
|
||||
|
||||
out.next_section();
|
||||
for api in apis {
|
||||
match api {
|
||||
Api::Struct(strct) => write_struct_decl(out, &strct.ident),
|
||||
Api::CxxType(ety) | Api::RustType(ety) => write_struct_decl(out, &ety.ident),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for api in apis {
|
||||
if let Api::Struct(strct) = api {
|
||||
out.next_section();
|
||||
write_struct(out, strct);
|
||||
}
|
||||
}
|
||||
|
||||
if !header {
|
||||
out.begin_block("extern \"C\"");
|
||||
for api in apis {
|
||||
let (efn, write): (_, fn(_, _, _)) = match api {
|
||||
Api::CxxFunction(efn) => (efn, write_cxx_function_shim),
|
||||
Api::RustFunction(efn) => (efn, write_rust_function_decl),
|
||||
_ => continue,
|
||||
};
|
||||
out.next_section();
|
||||
write(out, efn, types);
|
||||
}
|
||||
out.end_block();
|
||||
}
|
||||
|
||||
for api in apis {
|
||||
if let Api::RustFunction(efn) = api {
|
||||
out.next_section();
|
||||
write_rust_function_shim(out, efn, types);
|
||||
}
|
||||
}
|
||||
|
||||
out.next_section();
|
||||
for name in namespace.iter().rev() {
|
||||
writeln!(out, "}} // namespace {}", name);
|
||||
}
|
||||
|
||||
if !header {
|
||||
out.next_section();
|
||||
write_generic_instantiations(out, types);
|
||||
}
|
||||
|
||||
out_file
|
||||
}
|
||||
|
||||
fn write_includes(out: &mut OutFile, types: &Types) {
|
||||
let mut has_int = false;
|
||||
let mut has_unique_ptr = false;
|
||||
let mut has_string = false;
|
||||
|
||||
for ty in types {
|
||||
match ty {
|
||||
Type::Ident(ident) => match Atom::from(ident) {
|
||||
Some(U8) | Some(U16) | Some(U32) | Some(U64) | Some(Usize) | Some(I8)
|
||||
| Some(I16) | Some(I32) | Some(I64) | Some(Isize) => has_int = true,
|
||||
Some(CxxString) => has_string = true,
|
||||
Some(Bool) | Some(RustString) | None => {}
|
||||
},
|
||||
Type::UniquePtr(_) => has_unique_ptr = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if has_int {
|
||||
writeln!(out, "#include <cstdint>");
|
||||
}
|
||||
if has_unique_ptr {
|
||||
writeln!(out, "#include <memory>");
|
||||
}
|
||||
if has_string {
|
||||
writeln!(out, "#include <string>");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_include_cxxbridge(out: &mut OutFile, types: &Types) {
|
||||
let mut needs_rust_box = false;
|
||||
for ty in types {
|
||||
if let Type::RustBox(_) = ty {
|
||||
needs_rust_box = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out.begin_block("namespace cxxbridge00");
|
||||
if needs_rust_box {
|
||||
writeln!(out, "// #include \"cxxbridge.h\"");
|
||||
for line in include::get("CXXBRIDGE00_RUST_BOX").lines() {
|
||||
if !line.trim_start().starts_with("//") {
|
||||
writeln!(out, "{}", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
out.end_block();
|
||||
}
|
||||
|
||||
fn write_namespace_alias(out: &mut OutFile, types: &Types) {
|
||||
let mut needs_namespace_alias = false;
|
||||
for ty in types {
|
||||
if let Type::RustBox(_) = ty {
|
||||
needs_namespace_alias = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if needs_namespace_alias {
|
||||
writeln!(out, "namespace cxxbridge = cxxbridge00;");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_struct(out: &mut OutFile, strct: &Struct) {
|
||||
for line in strct.doc.to_string().lines() {
|
||||
writeln!(out, "//{}", line);
|
||||
}
|
||||
writeln!(out, "struct {} final {{", strct.ident);
|
||||
for field in &strct.fields {
|
||||
write!(out, " ");
|
||||
write_type_space(out, &field.ty);
|
||||
writeln!(out, "{};", field.ident);
|
||||
}
|
||||
writeln!(out, "}};");
|
||||
}
|
||||
|
||||
fn write_struct_decl(out: &mut OutFile, ident: &Ident) {
|
||||
writeln!(out, "struct {};", ident);
|
||||
}
|
||||
|
||||
fn write_cxx_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
|
||||
let indirect_return = efn
|
||||
.ret
|
||||
.as_ref()
|
||||
.map_or(false, |ret| types.needs_indirect_abi(ret));
|
||||
write_extern_return_type(out, &efn.ret, types);
|
||||
for name in out.namespace.clone() {
|
||||
write!(out, "{}$", name);
|
||||
}
|
||||
write!(out, "cxxbridge00${}(", efn.ident);
|
||||
for (i, arg) in efn.args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(out, ", ");
|
||||
}
|
||||
write_extern_arg(out, arg, types);
|
||||
}
|
||||
if indirect_return {
|
||||
if !efn.args.is_empty() {
|
||||
write!(out, ", ");
|
||||
}
|
||||
write_return_type(out, &efn.ret);
|
||||
write!(out, "*return$");
|
||||
}
|
||||
writeln!(out, ") noexcept {{");
|
||||
write!(out, " ");
|
||||
write_return_type(out, &efn.ret);
|
||||
write!(out, "(*{}$)(", efn.ident);
|
||||
for (i, arg) in efn.args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(out, ", ");
|
||||
}
|
||||
write_type(out, &arg.ty);
|
||||
}
|
||||
writeln!(out, ") = {};", efn.ident);
|
||||
write!(out, " ");
|
||||
if indirect_return {
|
||||
write!(out, "new (return$) ");
|
||||
write_type(out, efn.ret.as_ref().unwrap());
|
||||
write!(out, "(");
|
||||
} else if efn.ret.is_some() {
|
||||
write!(out, "return ");
|
||||
}
|
||||
write!(out, "{}$(", efn.ident);
|
||||
for (i, arg) in efn.args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(out, ", ");
|
||||
}
|
||||
if let Type::RustBox(_) = &arg.ty {
|
||||
write_type(out, &arg.ty);
|
||||
write!(out, "::from_raw({})", arg.ident);
|
||||
} else if let Type::UniquePtr(_) = &arg.ty {
|
||||
write_type(out, &arg.ty);
|
||||
write!(out, "({})", arg.ident);
|
||||
} else if types.needs_indirect_abi(&arg.ty) {
|
||||
write!(out, "std::move(*{})", arg.ident);
|
||||
} else {
|
||||
write!(out, "{}", arg.ident);
|
||||
}
|
||||
}
|
||||
write!(out, ")");
|
||||
match &efn.ret {
|
||||
Some(Type::RustBox(_)) => write!(out, ".into_raw()"),
|
||||
Some(Type::UniquePtr(_)) => write!(out, ".release()"),
|
||||
_ => {}
|
||||
}
|
||||
if indirect_return {
|
||||
write!(out, ")");
|
||||
}
|
||||
writeln!(out, ";");
|
||||
writeln!(out, "}}");
|
||||
}
|
||||
|
||||
fn write_rust_function_decl(out: &mut OutFile, efn: &ExternFn, types: &Types) {
|
||||
write_extern_return_type(out, &efn.ret, types);
|
||||
for name in out.namespace.clone() {
|
||||
write!(out, "{}$", name);
|
||||
}
|
||||
write!(out, "cxxbridge00${}(", efn.ident);
|
||||
for (i, arg) in efn.args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(out, ", ");
|
||||
}
|
||||
write_extern_arg(out, arg, types);
|
||||
}
|
||||
if efn
|
||||
.ret
|
||||
.as_ref()
|
||||
.map_or(false, |ret| types.needs_indirect_abi(ret))
|
||||
{
|
||||
if !efn.args.is_empty() {
|
||||
write!(out, ", ");
|
||||
}
|
||||
write_return_type(out, &efn.ret);
|
||||
write!(out, "*return$");
|
||||
}
|
||||
writeln!(out, ") noexcept;");
|
||||
}
|
||||
|
||||
fn write_rust_function_shim(out: &mut OutFile, efn: &ExternFn, types: &Types) {
|
||||
let indirect_return = efn
|
||||
.ret
|
||||
.as_ref()
|
||||
.map_or(false, |ret| types.needs_indirect_abi(ret));
|
||||
for line in efn.doc.to_string().lines() {
|
||||
writeln!(out, "//{}", line);
|
||||
}
|
||||
write_return_type(out, &efn.ret);
|
||||
write!(out, "{}(", efn.ident);
|
||||
for (i, arg) in efn.args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(out, ", ");
|
||||
}
|
||||
write_type_space(out, &arg.ty);
|
||||
write!(out, "{}", arg.ident);
|
||||
}
|
||||
write!(out, ") noexcept");
|
||||
if out.header {
|
||||
writeln!(out, ";");
|
||||
} else {
|
||||
writeln!(out, " {{");
|
||||
write!(out, " ");
|
||||
if indirect_return {
|
||||
write!(out, "char return$[sizeof(");
|
||||
write_type(out, efn.ret.as_ref().unwrap());
|
||||
writeln!(out, ")];");
|
||||
write!(out, " ");
|
||||
} else if efn.ret.is_some() {
|
||||
write!(out, "return ");
|
||||
}
|
||||
for name in out.namespace.clone() {
|
||||
write!(out, "{}$", name);
|
||||
}
|
||||
write!(out, "cxxbridge00${}(", efn.ident);
|
||||
for (i, arg) in efn.args.iter().enumerate() {
|
||||
if i > 0 {
|
||||
write!(out, ", ");
|
||||
}
|
||||
if types.needs_indirect_abi(&arg.ty) {
|
||||
write!(out, "&");
|
||||
}
|
||||
write!(out, "{}", arg.ident);
|
||||
}
|
||||
if indirect_return {
|
||||
if !efn.args.is_empty() {
|
||||
write!(out, ", ");
|
||||
}
|
||||
write!(out, "reinterpret_cast<");
|
||||
write_return_type(out, &efn.ret);
|
||||
write!(out, "*>(return$)");
|
||||
}
|
||||
writeln!(out, ");");
|
||||
if indirect_return {
|
||||
write!(out, " return ");
|
||||
write_type(out, efn.ret.as_ref().unwrap());
|
||||
write!(out, "(*reinterpret_cast<");
|
||||
write_return_type(out, &efn.ret);
|
||||
writeln!(out, "*>(return$));");
|
||||
}
|
||||
writeln!(out, "}}");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_return_type(out: &mut OutFile, ty: &Option<Type>) {
|
||||
match ty {
|
||||
None => write!(out, "void "),
|
||||
Some(ty) => write_type_space(out, ty),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_extern_return_type(out: &mut OutFile, ty: &Option<Type>, types: &Types) {
|
||||
match ty {
|
||||
Some(Type::RustBox(ty)) | Some(Type::UniquePtr(ty)) => {
|
||||
write_type_space(out, &ty.inner);
|
||||
write!(out, "*");
|
||||
}
|
||||
Some(Type::Str(_)) => write!(out, "cxxbridge::RustStr::Repr "),
|
||||
Some(ty) if types.needs_indirect_abi(ty) => write!(out, "void "),
|
||||
_ => write_return_type(out, ty),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_extern_arg(out: &mut OutFile, arg: &Var, types: &Types) {
|
||||
match &arg.ty {
|
||||
Type::RustBox(ty) | Type::UniquePtr(ty) => {
|
||||
write_type_space(out, &ty.inner);
|
||||
write!(out, "*");
|
||||
}
|
||||
Type::Str(_) => write!(out, "cxxbridge::RustStr::Repr "),
|
||||
_ => write_type_space(out, &arg.ty),
|
||||
}
|
||||
if types.needs_indirect_abi(&arg.ty) {
|
||||
write!(out, "*");
|
||||
}
|
||||
write!(out, "{}", arg.ident);
|
||||
}
|
||||
|
||||
fn write_type(out: &mut OutFile, ty: &Type) {
|
||||
match ty {
|
||||
Type::Ident(ident) => match Atom::from(ident) {
|
||||
Some(Bool) => write!(out, "bool"),
|
||||
Some(U8) => write!(out, "uint8_t"),
|
||||
Some(U16) => write!(out, "uint16_t"),
|
||||
Some(U32) => write!(out, "uint32_t"),
|
||||
Some(U64) => write!(out, "uint64_t"),
|
||||
Some(Usize) => write!(out, "size_t"),
|
||||
Some(I8) => write!(out, "int8_t"),
|
||||
Some(I16) => write!(out, "int16_t"),
|
||||
Some(I32) => write!(out, "int32_t"),
|
||||
Some(I64) => write!(out, "int64_t"),
|
||||
Some(Isize) => write!(out, "ssize_t"),
|
||||
Some(CxxString) => write!(out, "std::string"),
|
||||
Some(RustString) => write!(out, "cxxbridge::RustString"),
|
||||
None => write!(out, "{}", ident),
|
||||
},
|
||||
Type::RustBox(ty) => {
|
||||
write!(out, "cxxbridge::RustBox<");
|
||||
write_type(out, &ty.inner);
|
||||
write!(out, ">");
|
||||
}
|
||||
Type::UniquePtr(ptr) => {
|
||||
write!(out, "std::unique_ptr<");
|
||||
write_type(out, &ptr.inner);
|
||||
write!(out, ">");
|
||||
}
|
||||
Type::Ref(r) => {
|
||||
if r.mutability.is_none() {
|
||||
write!(out, "const ");
|
||||
}
|
||||
write_type(out, &r.inner);
|
||||
write!(out, " &");
|
||||
}
|
||||
Type::Str(_) => {
|
||||
write!(out, "cxxbridge::RustStr");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_type_space(out: &mut OutFile, ty: &Type) {
|
||||
write_type(out, ty);
|
||||
match ty {
|
||||
Type::Ident(_) | Type::RustBox(_) | Type::UniquePtr(_) | Type::Str(_) => write!(out, " "),
|
||||
Type::Ref(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_generic_instantiations(out: &mut OutFile, types: &Types) {
|
||||
fn allow_unique_ptr(ident: &Ident) -> bool {
|
||||
Atom::from(ident).is_none()
|
||||
}
|
||||
|
||||
out.begin_block("extern \"C\"");
|
||||
for ty in types {
|
||||
if let Type::RustBox(ty) = ty {
|
||||
if let Type::Ident(inner) = &ty.inner {
|
||||
out.next_section();
|
||||
write_rust_box_extern(out, inner);
|
||||
}
|
||||
} else if let Type::UniquePtr(ptr) = ty {
|
||||
if let Type::Ident(inner) = &ptr.inner {
|
||||
if allow_unique_ptr(inner) {
|
||||
out.next_section();
|
||||
write_unique_ptr(out, inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.end_block();
|
||||
|
||||
out.begin_block("namespace cxxbridge00");
|
||||
for ty in types {
|
||||
if let Type::RustBox(ty) = ty {
|
||||
if let Type::Ident(inner) = &ty.inner {
|
||||
write_rust_box_impl(out, inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
out.end_block();
|
||||
}
|
||||
|
||||
fn write_rust_box_extern(out: &mut OutFile, ident: &Ident) {
|
||||
let mut inner = String::new();
|
||||
for name in &out.namespace {
|
||||
inner += name;
|
||||
inner += "::";
|
||||
}
|
||||
inner += &ident.to_string();
|
||||
let instance = inner.replace("::", "$");
|
||||
|
||||
writeln!(out, "#ifndef CXXBRIDGE00_RUST_BOX_{}", instance);
|
||||
writeln!(out, "#define CXXBRIDGE00_RUST_BOX_{}", instance);
|
||||
writeln!(
|
||||
out,
|
||||
"void cxxbridge00$rust_box${}$uninit(cxxbridge::RustBox<{}> *ptr) noexcept;",
|
||||
instance, inner,
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
"void cxxbridge00$rust_box${}$set_raw(cxxbridge::RustBox<{}> *ptr, {} *raw) noexcept;",
|
||||
instance, inner, inner
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
"void cxxbridge00$rust_box${}$drop(cxxbridge::RustBox<{}> *ptr) noexcept;",
|
||||
instance, inner,
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
"const {} *cxxbridge00$rust_box${}$deref(const cxxbridge::RustBox<{}> *ptr) noexcept;",
|
||||
inner, instance, inner,
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
"{} *cxxbridge00$rust_box${}$deref_mut(cxxbridge::RustBox<{}> *ptr) noexcept;",
|
||||
inner, instance, inner,
|
||||
);
|
||||
writeln!(out, "#endif // CXXBRIDGE00_RUST_BOX_{}", instance);
|
||||
}
|
||||
|
||||
fn write_rust_box_impl(out: &mut OutFile, ident: &Ident) {
|
||||
let mut inner = String::new();
|
||||
for name in &out.namespace {
|
||||
inner += name;
|
||||
inner += "::";
|
||||
}
|
||||
inner += &ident.to_string();
|
||||
let instance = inner.replace("::", "$");
|
||||
|
||||
writeln!(out, "template <>");
|
||||
writeln!(out, "void RustBox<{}>::uninit() noexcept {{", inner);
|
||||
writeln!(
|
||||
out,
|
||||
" return cxxbridge00$rust_box${}$uninit(this);",
|
||||
instance
|
||||
);
|
||||
writeln!(out, "}}");
|
||||
|
||||
writeln!(out, "template <>");
|
||||
writeln!(
|
||||
out,
|
||||
"void RustBox<{}>::set_raw({} *raw) noexcept {{",
|
||||
inner, inner,
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
" return cxxbridge00$rust_box${}$set_raw(this, raw);",
|
||||
instance
|
||||
);
|
||||
writeln!(out, "}}");
|
||||
|
||||
writeln!(out, "template <>");
|
||||
writeln!(out, "void RustBox<{}>::drop() noexcept {{", inner);
|
||||
writeln!(
|
||||
out,
|
||||
" return cxxbridge00$rust_box${}$drop(this);",
|
||||
instance
|
||||
);
|
||||
writeln!(out, "}}");
|
||||
|
||||
writeln!(out, "template <>");
|
||||
writeln!(
|
||||
out,
|
||||
"const {} *RustBox<{}>::deref() const noexcept {{",
|
||||
inner, inner,
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
" return cxxbridge00$rust_box${}$deref(this);",
|
||||
instance
|
||||
);
|
||||
writeln!(out, "}}");
|
||||
|
||||
writeln!(out, "template <>");
|
||||
writeln!(
|
||||
out,
|
||||
"{} *RustBox<{}>::deref_mut() noexcept {{",
|
||||
inner, inner,
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
" return cxxbridge00$rust_box${}$deref_mut(this);",
|
||||
instance
|
||||
);
|
||||
writeln!(out, "}}");
|
||||
}
|
||||
|
||||
fn write_unique_ptr(out: &mut OutFile, ident: &Ident) {
|
||||
let mut inner = String::new();
|
||||
for name in &out.namespace {
|
||||
inner += name;
|
||||
inner += "::";
|
||||
}
|
||||
inner += &ident.to_string();
|
||||
let instance = inner.replace("::", "$");
|
||||
|
||||
writeln!(out, "#ifndef CXXBRIDGE00_UNIQUE_PTR_{}", instance);
|
||||
writeln!(out, "#define CXXBRIDGE00_UNIQUE_PTR_{}", instance);
|
||||
writeln!(
|
||||
out,
|
||||
"static_assert(sizeof(std::unique_ptr<{}>) == sizeof(void *), \"\");",
|
||||
inner,
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
"static_assert(alignof(std::unique_ptr<{}>) == alignof(void *), \"\");",
|
||||
inner,
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
"void cxxbridge00$unique_ptr${}$null(std::unique_ptr<{}> *ptr) noexcept {{",
|
||||
instance, inner,
|
||||
);
|
||||
writeln!(out, " new (ptr) std::unique_ptr<{}>();", inner);
|
||||
writeln!(out, "}}");
|
||||
writeln!(
|
||||
out,
|
||||
"void cxxbridge00$unique_ptr${}$new(std::unique_ptr<{}> *ptr, {} *value) noexcept {{",
|
||||
instance, inner, inner,
|
||||
);
|
||||
writeln!(
|
||||
out,
|
||||
" new (ptr) std::unique_ptr<{}>(new {}(std::move(*value)));",
|
||||
inner, inner,
|
||||
);
|
||||
writeln!(out, "}}");
|
||||
writeln!(
|
||||
out,
|
||||
"void cxxbridge00$unique_ptr${}$raw(std::unique_ptr<{}> *ptr, {} *raw) noexcept {{",
|
||||
instance, inner, inner,
|
||||
);
|
||||
writeln!(out, " new (ptr) std::unique_ptr<{}>(raw);", inner);
|
||||
writeln!(out, "}}");
|
||||
writeln!(
|
||||
out,
|
||||
"const {} *cxxbridge00$unique_ptr${}$get(const std::unique_ptr<{}>& ptr) noexcept {{",
|
||||
inner, instance, inner,
|
||||
);
|
||||
writeln!(out, " return ptr.get();");
|
||||
writeln!(out, "}}");
|
||||
writeln!(
|
||||
out,
|
||||
"{} *cxxbridge00$unique_ptr${}$release(std::unique_ptr<{}>& ptr) noexcept {{",
|
||||
inner, instance, inner,
|
||||
);
|
||||
writeln!(out, " return ptr.release();");
|
||||
writeln!(out, "}}");
|
||||
writeln!(
|
||||
out,
|
||||
"void cxxbridge00$unique_ptr${}$drop(std::unique_ptr<{}> *ptr) noexcept {{",
|
||||
instance, inner,
|
||||
);
|
||||
writeln!(out, " ptr->~unique_ptr();");
|
||||
writeln!(out, "}}");
|
||||
writeln!(out, "#endif // CXXBRIDGE00_UNIQUE_PTR_{}", instance);
|
||||
}
|
132
include/cxxbridge.h
Normal file
132
include/cxxbridge.h
Normal file
@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace cxxbridge00 {
|
||||
|
||||
class RustString final {
|
||||
public:
|
||||
RustString() noexcept;
|
||||
RustString(const RustString &other) noexcept;
|
||||
RustString(RustString &&other) noexcept;
|
||||
RustString(const char *s);
|
||||
RustString(const std::string &s);
|
||||
RustString &operator=(const RustString &other) noexcept;
|
||||
RustString &operator=(RustString &&other) noexcept;
|
||||
~RustString() noexcept;
|
||||
operator std::string() const;
|
||||
|
||||
// Note: no null terminator.
|
||||
const char *data() const noexcept;
|
||||
size_t size() const noexcept;
|
||||
size_t length() const noexcept;
|
||||
|
||||
private:
|
||||
// Size and alignment statically verified by rust_string.rs.
|
||||
std::array<uintptr_t, 3> repr;
|
||||
};
|
||||
|
||||
class RustStr final {
|
||||
public:
|
||||
RustStr() noexcept;
|
||||
RustStr(const char *s);
|
||||
RustStr(const std::string &s);
|
||||
RustStr(std::string &&s) = delete;
|
||||
RustStr(const RustStr &other) noexcept;
|
||||
RustStr &operator=(RustStr other) noexcept;
|
||||
operator std::string() const;
|
||||
|
||||
// Note: no null terminator.
|
||||
const char *data() const noexcept;
|
||||
size_t size() const noexcept;
|
||||
size_t length() const noexcept;
|
||||
|
||||
// Repr is PRIVATE; must not be used other than by our generated code.
|
||||
//
|
||||
// Not necessarily ABI compatible with &str. Codegen will translate to
|
||||
// cxx::rust_str::RustStr which matches this layout.
|
||||
struct Repr {
|
||||
const char *ptr;
|
||||
size_t len;
|
||||
};
|
||||
RustStr(Repr repr) noexcept;
|
||||
operator Repr() noexcept;
|
||||
|
||||
private:
|
||||
Repr repr;
|
||||
};
|
||||
|
||||
#ifndef CXXBRIDGE00_RUST_BOX
|
||||
#define CXXBRIDGE00_RUST_BOX
|
||||
template <typename T> class RustBox final {
|
||||
public:
|
||||
RustBox(const RustBox &other) : RustBox(*other) {}
|
||||
RustBox(RustBox &&other) noexcept : repr(other.repr) { other.repr = 0; }
|
||||
RustBox(const T &val) {
|
||||
this->uninit();
|
||||
new (this->deref_mut()) T(val);
|
||||
}
|
||||
RustBox &operator=(const RustBox &other) {
|
||||
if (this != &other) {
|
||||
if (this->repr) {
|
||||
**this = *other;
|
||||
} else {
|
||||
this->uninit();
|
||||
new (this->deref_mut()) T(*other);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
RustBox &operator=(RustBox &&other) noexcept {
|
||||
if (this->repr) {
|
||||
this->drop();
|
||||
}
|
||||
this->repr = other.repr;
|
||||
other.repr = 0;
|
||||
return *this;
|
||||
}
|
||||
~RustBox() noexcept {
|
||||
if (this->repr) {
|
||||
this->drop();
|
||||
}
|
||||
}
|
||||
|
||||
const T *operator->() const noexcept { return this->deref(); }
|
||||
const T &operator*() const noexcept { return *this->deref(); }
|
||||
T *operator->() noexcept { return this->deref_mut(); }
|
||||
T &operator*() noexcept { return *this->deref_mut(); }
|
||||
|
||||
// Important: requires that `raw` came from an into_raw call. Do not pass a
|
||||
// pointer from `new` or any other source.
|
||||
static RustBox from_raw(T *raw) noexcept {
|
||||
RustBox box;
|
||||
box.set_raw(raw);
|
||||
return box;
|
||||
}
|
||||
|
||||
T *into_raw() noexcept {
|
||||
T *raw = this->deref_mut();
|
||||
this->repr = 0;
|
||||
return raw;
|
||||
}
|
||||
|
||||
private:
|
||||
RustBox() noexcept {}
|
||||
void uninit() noexcept;
|
||||
void set_raw(T *) noexcept;
|
||||
T *get_raw() noexcept;
|
||||
void drop() noexcept;
|
||||
const T *deref() const noexcept;
|
||||
T *deref_mut() noexcept;
|
||||
uintptr_t repr;
|
||||
};
|
||||
#endif // CXXBRIDGE00_RUST_BOX
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const RustString &s);
|
||||
std::ostream &operator<<(std::ostream &os, const RustStr &s);
|
||||
|
||||
} // namespace cxxbridge00
|
||||
|
||||
namespace cxxbridge = cxxbridge00;
|
22
macro/Cargo.toml
Normal file
22
macro/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "cxxbridge-macro"
|
||||
version = "0.0.0"
|
||||
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"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "dtolnay/cxx" }
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
cxx = { version = "0.0", path = ".." }
|
447
macro/src/expand.rs
Normal file
447
macro/src/expand.rs
Normal file
@ -0,0 +1,447 @@
|
||||
use crate::namespace::Namespace;
|
||||
use crate::syntax::atom::Atom;
|
||||
use crate::syntax::{self, check, Api, ExternFn, ExternType, Struct, Type, Types, Var};
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{Error, ItemMod, Result, Token};
|
||||
|
||||
pub fn bridge(namespace: &Namespace, ffi: ItemMod) -> Result<TokenStream> {
|
||||
let ident = &ffi.ident;
|
||||
let content = ffi.content.ok_or(Error::new(
|
||||
Span::call_site(),
|
||||
"#[cxx::bridge] module must have inline contents",
|
||||
))?;
|
||||
let apis = syntax::parse_items(content.1)?;
|
||||
let ref types = Types::collect(&apis)?;
|
||||
check::typecheck(&apis, types)?;
|
||||
|
||||
let mut expanded = TokenStream::new();
|
||||
let mut hidden = TokenStream::new();
|
||||
let mut has_rust_type = false;
|
||||
|
||||
for api in &apis {
|
||||
if let Api::RustType(ety) = api {
|
||||
expanded.extend(expand_rust_type(ety));
|
||||
if !has_rust_type {
|
||||
hidden.extend(quote!(const fn __assert_sized<T>() {}));
|
||||
has_rust_type = true;
|
||||
}
|
||||
let ident = &ety.ident;
|
||||
hidden.extend(quote!(__assert_sized::<#ident>();));
|
||||
}
|
||||
}
|
||||
|
||||
for api in &apis {
|
||||
match api {
|
||||
Api::Include(_) | Api::RustType(_) => {}
|
||||
Api::Struct(strct) => expanded.extend(expand_struct(strct)),
|
||||
Api::CxxType(ety) => expanded.extend(expand_cxx_type(ety)),
|
||||
Api::CxxFunction(efn) => {
|
||||
expanded.extend(expand_cxx_function_shim(namespace, efn, types));
|
||||
}
|
||||
Api::RustFunction(efn) => {
|
||||
hidden.extend(expand_rust_function_shim(namespace, efn, types))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ty in types {
|
||||
if let Type::RustBox(ty) = ty {
|
||||
if let Type::Ident(ident) = &ty.inner {
|
||||
if Atom::from(ident).is_none() {
|
||||
hidden.extend(expand_rust_box(namespace, ident));
|
||||
}
|
||||
}
|
||||
} else if let Type::UniquePtr(ptr) = ty {
|
||||
if let Type::Ident(ident) = &ptr.inner {
|
||||
if Atom::from(ident).is_none() {
|
||||
expanded.extend(expand_unique_ptr(namespace, ident));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Work around https://github.com/rust-lang/rust/issues/67851.
|
||||
if !hidden.is_empty() {
|
||||
expanded.extend(quote! {
|
||||
#[doc(hidden)]
|
||||
const _: () = {
|
||||
#hidden
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let attrs = ffi
|
||||
.attrs
|
||||
.into_iter()
|
||||
.filter(|attr| attr.path.is_ident("doc"));
|
||||
let vis = &ffi.vis;
|
||||
|
||||
Ok(quote! {
|
||||
#(#attrs)*
|
||||
#[deny(improper_ctypes)]
|
||||
#[allow(non_snake_case)]
|
||||
#vis mod #ident {
|
||||
#expanded
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn expand_struct(strct: &Struct) -> TokenStream {
|
||||
let ident = &strct.ident;
|
||||
let doc = &strct.doc;
|
||||
let derives = &strct.derives;
|
||||
let fields = strct.fields.iter().map(|field| {
|
||||
// This span on the pub makes "private type in public interface" errors
|
||||
// appear in the right place.
|
||||
let vis = Token![pub](field.ident.span());
|
||||
quote!(#vis #field)
|
||||
});
|
||||
quote! {
|
||||
#doc
|
||||
#[derive(#(#derives),*)]
|
||||
#[repr(C)]
|
||||
pub struct #ident {
|
||||
#(#fields,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_cxx_type(ety: &ExternType) -> TokenStream {
|
||||
let ident = &ety.ident;
|
||||
let doc = &ety.doc;
|
||||
quote! {
|
||||
#doc
|
||||
#[repr(C)]
|
||||
pub struct #ident {
|
||||
_private: ::cxx::private::Opaque,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_cxx_function_decl(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
|
||||
let ident = &efn.ident;
|
||||
let args = efn.args.iter().map(|arg| expand_extern_arg(arg, types));
|
||||
let ret = expand_extern_return_type(&efn.ret, types);
|
||||
let mut outparam = None;
|
||||
if indirect_return(&efn.ret, types) {
|
||||
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
|
||||
outparam = Some(quote!(__return: *mut #ret));
|
||||
}
|
||||
let link_name = format!("{}cxxbridge00${}", namespace, ident);
|
||||
let local_name = format_ident!("__{}", ident);
|
||||
quote! {
|
||||
#[link_name = #link_name]
|
||||
fn #local_name(#(#args,)* #outparam) #ret;
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_cxx_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
|
||||
let ident = &efn.ident;
|
||||
let doc = &efn.doc;
|
||||
let decl = expand_cxx_function_decl(namespace, efn, types);
|
||||
let args = &efn.args;
|
||||
let ret = expand_return_type(&efn.ret);
|
||||
let indirect_return = indirect_return(&efn.ret, types);
|
||||
let vars = efn.args.iter().map(|arg| {
|
||||
let var = &arg.ident;
|
||||
match &arg.ty {
|
||||
Type::Ident(ident) if ident == "String" => {
|
||||
quote!(#var.as_mut_ptr() as *mut ::cxx::private::RustString)
|
||||
}
|
||||
Type::RustBox(_) => quote!(::std::boxed::Box::into_raw(#var)),
|
||||
Type::UniquePtr(_) => quote!(::cxx::UniquePtr::into_raw(#var)),
|
||||
Type::Ref(ty) => match &ty.inner {
|
||||
Type::Ident(ident) if ident == "String" => {
|
||||
quote!(::cxx::private::RustString::from_ref(#var))
|
||||
}
|
||||
_ => quote!(#var),
|
||||
},
|
||||
Type::Str(_) => quote!(::cxx::private::RustStr::from(#var)),
|
||||
ty if types.needs_indirect_abi(ty) => quote!(#var.as_mut_ptr()),
|
||||
_ => quote!(#var),
|
||||
}
|
||||
});
|
||||
let mut setup = efn
|
||||
.args
|
||||
.iter()
|
||||
.filter(|arg| types.needs_indirect_abi(&arg.ty))
|
||||
.map(|arg| {
|
||||
let var = &arg.ident;
|
||||
// These are arguments for which C++ has taken ownership of the data
|
||||
// behind the mut reference it received.
|
||||
quote! {
|
||||
let mut #var = std::mem::MaybeUninit::new(#var);
|
||||
}
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
let local_name = format_ident!("__{}", ident);
|
||||
let call = if indirect_return {
|
||||
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
|
||||
setup.extend(quote! {
|
||||
let mut __return = ::std::mem::MaybeUninit::<#ret>::uninit();
|
||||
#local_name(#(#vars,)* __return.as_mut_ptr());
|
||||
});
|
||||
quote! {
|
||||
__return.assume_init()
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#local_name(#(#vars),*)
|
||||
}
|
||||
};
|
||||
let expr = efn
|
||||
.ret
|
||||
.as_ref()
|
||||
.and_then(|ret| match ret {
|
||||
Type::Ident(ident) if ident == "String" => Some(quote!(#call.into_string())),
|
||||
Type::RustBox(_) => Some(quote!(::std::boxed::Box::from_raw(#call))),
|
||||
Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::from_raw(#call))),
|
||||
Type::Ref(ty) => match &ty.inner {
|
||||
Type::Ident(ident) if ident == "String" => Some(quote!(#call.as_string())),
|
||||
_ => None,
|
||||
},
|
||||
Type::Str(_) => Some(quote!(#call.as_str())),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(call);
|
||||
quote! {
|
||||
#doc
|
||||
pub fn #ident(#(#args),*) #ret {
|
||||
extern "C" {
|
||||
#decl
|
||||
}
|
||||
unsafe {
|
||||
#setup
|
||||
#expr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_rust_type(ety: &ExternType) -> TokenStream {
|
||||
let ident = &ety.ident;
|
||||
quote! {
|
||||
use super::#ident;
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_rust_function_shim(namespace: &Namespace, efn: &ExternFn, types: &Types) -> TokenStream {
|
||||
let ident = &efn.ident;
|
||||
let args = efn.args.iter().map(|arg| expand_extern_arg(arg, types));
|
||||
let vars = efn.args.iter().map(|arg| {
|
||||
let ident = &arg.ident;
|
||||
if types.needs_indirect_abi(&arg.ty) {
|
||||
quote!(::std::ptr::read(#ident))
|
||||
} else {
|
||||
quote!(#ident)
|
||||
}
|
||||
});
|
||||
let mut outparam = None;
|
||||
let call = quote! {
|
||||
::cxx::private::catch_unwind(__fn, move || super::#ident(#(#vars),*))
|
||||
};
|
||||
let mut expr = efn
|
||||
.ret
|
||||
.as_ref()
|
||||
.and_then(|ret| match ret {
|
||||
Type::Ident(ident) if ident == "String" => {
|
||||
Some(quote!(::cxx::private::RustString::from(#call)))
|
||||
}
|
||||
Type::RustBox(_) => Some(quote!(::std::boxed::Box::into_raw(#call))),
|
||||
Type::UniquePtr(_) => Some(quote!(::cxx::UniquePtr::into_raw(#call))),
|
||||
Type::Ref(ty) => match &ty.inner {
|
||||
Type::Ident(ident) if ident == "String" => {
|
||||
Some(quote!(::cxx::private::RustString::from_ref(#call)))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
Type::Str(_) => Some(quote!(::cxx::private::RustStr::from(#call))),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(call);
|
||||
if indirect_return(&efn.ret, types) {
|
||||
let ret = expand_extern_type(efn.ret.as_ref().unwrap());
|
||||
outparam = Some(quote!(__return: *mut #ret));
|
||||
expr = quote!(::std::ptr::write(__return, #expr));
|
||||
}
|
||||
let ret = expand_extern_return_type(&efn.ret, types);
|
||||
let link_name = format!("{}cxxbridge00${}", namespace, ident);
|
||||
let local_name = format_ident!("__{}", ident);
|
||||
let catch_unwind_label = format!("::{}", ident);
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
#[export_name = #link_name]
|
||||
unsafe extern "C" fn #local_name(#(#args,)* #outparam) #ret {
|
||||
let __fn = concat!(module_path!(), #catch_unwind_label);
|
||||
#expr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_rust_box(namespace: &Namespace, ident: &Ident) -> TokenStream {
|
||||
let link_prefix = format!("cxxbridge00$rust_box${}{}$", namespace, ident);
|
||||
let link_uninit = format!("{}uninit", link_prefix);
|
||||
let link_set_raw = format!("{}set_raw", link_prefix);
|
||||
let link_drop = format!("{}drop", link_prefix);
|
||||
let link_deref = format!("{}deref", link_prefix);
|
||||
let link_deref_mut = format!("{}deref_mut", link_prefix);
|
||||
|
||||
let local_prefix = format_ident!("{}__box_", ident);
|
||||
let local_uninit = format_ident!("{}uninit", local_prefix);
|
||||
let local_set_raw = format_ident!("{}set_raw", local_prefix);
|
||||
let local_drop = format_ident!("{}drop", local_prefix);
|
||||
let local_deref = format_ident!("{}deref", local_prefix);
|
||||
let local_deref_mut = format_ident!("{}deref_mut", local_prefix);
|
||||
|
||||
let span = ident.span();
|
||||
quote_spanned! {span=>
|
||||
#[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()),
|
||||
);
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[export_name = #link_set_raw]
|
||||
unsafe extern "C" fn #local_set_raw(
|
||||
this: *mut ::std::boxed::Box<#ident>,
|
||||
raw: *mut #ident,
|
||||
) {
|
||||
::std::ptr::write(this, ::std::boxed::Box::from_raw(raw));
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[export_name = #link_drop]
|
||||
unsafe extern "C" fn #local_drop(this: *mut ::std::boxed::Box<#ident>) {
|
||||
::std::ptr::drop_in_place(this);
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[export_name = #link_deref]
|
||||
unsafe extern "C" fn #local_deref(
|
||||
this: *const ::std::boxed::Box<::std::mem::MaybeUninit<#ident>>,
|
||||
) -> *const ::std::mem::MaybeUninit<#ident> {
|
||||
&**this
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[export_name = #link_deref_mut]
|
||||
unsafe extern "C" fn #local_deref_mut(
|
||||
this: *mut ::std::boxed::Box<::std::mem::MaybeUninit<#ident>>,
|
||||
) -> *mut ::std::mem::MaybeUninit<#ident> {
|
||||
&mut **this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_unique_ptr(namespace: &Namespace, ident: &Ident) -> TokenStream {
|
||||
let prefix = format!("cxxbridge00$unique_ptr${}{}$", namespace, ident);
|
||||
let link_null = format!("{}null", prefix);
|
||||
let link_new = format!("{}new", 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);
|
||||
|
||||
quote! {
|
||||
unsafe impl ::cxx::private::UniquePtrTarget for #ident {
|
||||
fn __null() -> *mut ::std::ffi::c_void {
|
||||
extern "C" {
|
||||
#[link_name = #link_null]
|
||||
fn __null(this: *mut *mut ::std::ffi::c_void);
|
||||
}
|
||||
let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
|
||||
unsafe { __null(&mut repr) }
|
||||
repr
|
||||
}
|
||||
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
|
||||
}
|
||||
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);
|
||||
}
|
||||
let mut repr = ::std::ptr::null_mut::<::std::ffi::c_void>();
|
||||
__raw(&mut repr, raw);
|
||||
repr
|
||||
}
|
||||
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;
|
||||
}
|
||||
__get(&repr)
|
||||
}
|
||||
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;
|
||||
}
|
||||
__release(&mut repr)
|
||||
}
|
||||
unsafe fn __drop(mut repr: *mut ::std::ffi::c_void) {
|
||||
extern "C" {
|
||||
#[link_name = #link_drop]
|
||||
fn __drop(this: *mut *mut ::std::ffi::c_void);
|
||||
}
|
||||
__drop(&mut repr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_return_type(ret: &Option<Type>) -> TokenStream {
|
||||
match ret {
|
||||
Some(ret) => quote!(-> #ret),
|
||||
None => TokenStream::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn indirect_return(ret: &Option<Type>, types: &Types) -> bool {
|
||||
ret.as_ref()
|
||||
.map_or(false, |ret| types.needs_indirect_abi(ret))
|
||||
}
|
||||
|
||||
fn expand_extern_type(ty: &Type) -> TokenStream {
|
||||
match ty {
|
||||
Type::Ident(ident) if ident == "String" => quote!(::cxx::private::RustString),
|
||||
Type::RustBox(ty) | Type::UniquePtr(ty) => {
|
||||
let inner = &ty.inner;
|
||||
quote!(*mut #inner)
|
||||
}
|
||||
Type::Ref(ty) => match &ty.inner {
|
||||
Type::Ident(ident) if ident == "String" => quote!(&::cxx::private::RustString),
|
||||
_ => quote!(#ty),
|
||||
},
|
||||
Type::Str(_) => quote!(::cxx::private::RustStr),
|
||||
_ => quote!(#ty),
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_extern_return_type(ret: &Option<Type>, types: &Types) -> TokenStream {
|
||||
let ret = match ret {
|
||||
Some(ret) if !types.needs_indirect_abi(ret) => ret,
|
||||
_ => return TokenStream::new(),
|
||||
};
|
||||
let ty = expand_extern_type(ret);
|
||||
quote!(-> #ty)
|
||||
}
|
||||
|
||||
fn expand_extern_arg(arg: &Var, types: &Types) -> TokenStream {
|
||||
let ident = &arg.ident;
|
||||
let ty = expand_extern_type(&arg.ty);
|
||||
if types.needs_indirect_abi(&arg.ty) {
|
||||
quote!(#ident: *mut #ty)
|
||||
} else {
|
||||
quote!(#ident: #ty)
|
||||
}
|
||||
}
|
46
macro/src/lib.rs
Normal file
46
macro/src/lib.rs
Normal file
@ -0,0 +1,46 @@
|
||||
#![allow(
|
||||
clippy::large_enum_variant,
|
||||
clippy::new_without_default,
|
||||
clippy::or_fun_call,
|
||||
clippy::toplevel_ref_arg,
|
||||
clippy::useless_let_if_seq
|
||||
)]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
mod expand;
|
||||
mod namespace;
|
||||
mod syntax;
|
||||
|
||||
use crate::namespace::Namespace;
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{parse_macro_input, ItemMod};
|
||||
|
||||
/// `#[cxx::bridge] mod ffi { ... }`
|
||||
///
|
||||
/// Refer to the crate-level documentation for the explanation of how this macro
|
||||
/// 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
|
||||
/// namespace, specify that namespace as an argument of the cxx::bridge
|
||||
/// attribute macro.
|
||||
///
|
||||
/// ```
|
||||
/// #[cxx::bridge(namespace = mycompany::rust)]
|
||||
/// # mod ffi {}
|
||||
/// ```
|
||||
///
|
||||
/// The types and functions from the `extern "Rust"` side of the bridge will be
|
||||
/// placed into that same namespace in the generated C++ code.
|
||||
#[proc_macro_attribute]
|
||||
pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let _ = syntax::error::ERRORS;
|
||||
|
||||
let namespace = parse_macro_input!(args as Namespace);
|
||||
let ffi = parse_macro_input!(input as ItemMod);
|
||||
|
||||
expand::bridge(&namespace, ffi)
|
||||
.unwrap_or_else(|err| err.to_compile_error())
|
||||
.into()
|
||||
}
|
39
macro/src/namespace.rs
Normal file
39
macro/src/namespace.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use crate::syntax::ident;
|
||||
use std::fmt::{self, Display};
|
||||
use syn::parse::{Parse, ParseStream, Result};
|
||||
use syn::{Path, Token};
|
||||
|
||||
mod kw {
|
||||
syn::custom_keyword!(namespace);
|
||||
}
|
||||
|
||||
pub struct Namespace {
|
||||
segments: Vec<String>,
|
||||
}
|
||||
|
||||
impl Parse for Namespace {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut segments = Vec::new();
|
||||
if !input.is_empty() {
|
||||
input.parse::<kw::namespace>()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
let path = input.call(Path::parse_mod_style)?;
|
||||
for segment in path.segments {
|
||||
ident::check(&segment.ident)?;
|
||||
segments.push(segment.ident.to_string());
|
||||
}
|
||||
input.parse::<Option<Token![,]>>()?;
|
||||
}
|
||||
Ok(Namespace { segments })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Namespace {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for segment in &self.segments {
|
||||
f.write_str(segment)?;
|
||||
f.write_str("$")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
1
macro/src/syntax
Symbolic link
1
macro/src/syntax
Symbolic link
@ -0,0 +1 @@
|
||||
../../syntax/
|
91
src/cxx_string.rs
Normal file
91
src/cxx_string.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::slice;
|
||||
use std::str::{self, Utf8Error};
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "cxxbridge00$cxx_string$data"]
|
||||
fn string_data(_: &CxxString) -> *const u8;
|
||||
#[link_name = "cxxbridge00$cxx_string$length"]
|
||||
fn string_length(_: &CxxString) -> usize;
|
||||
}
|
||||
|
||||
/// Binding to C++ `std::string`.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// As an invariant of this API and the static analysis of the cxx::bridge
|
||||
/// macro, in Rust code we 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 `UniquePtr<CxxString>`.
|
||||
#[repr(C)]
|
||||
pub struct CxxString {
|
||||
_private: [u8; 0],
|
||||
}
|
||||
|
||||
impl CxxString {
|
||||
/// Returns the length of the string in bytes.
|
||||
///
|
||||
/// Matches the behavior of C++ [std::string::size][size].
|
||||
///
|
||||
/// [size]: https://en.cppreference.com/w/cpp/string/basic_string/size
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe { string_length(self) }
|
||||
}
|
||||
|
||||
/// Returns true if `self` has a length of zero bytes.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns a byte slice of this string's contents.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
let data = self.as_ptr();
|
||||
let len = self.len();
|
||||
unsafe { slice::from_raw_parts(data, len) }
|
||||
}
|
||||
|
||||
/// Produces a pointer to the first character of the string.
|
||||
///
|
||||
/// Matches the behavior of C++ [std::string::data][data].
|
||||
///
|
||||
/// Note that the return type may look like `const char *` but is not a
|
||||
/// `const char *` in the typical C sense, as C++ strings may contain
|
||||
/// internal null bytes. As such, the returned pointer only makes sense as a
|
||||
/// string in combination with the length returned by [`len()`](#len).
|
||||
///
|
||||
/// [data]: https://en.cppreference.com/w/cpp/string/basic_string/data
|
||||
pub fn as_ptr(&self) -> *const u8 {
|
||||
unsafe { string_data(self) }
|
||||
}
|
||||
|
||||
/// Validates that the C++ string contains UTF-8 data and produces a view of
|
||||
/// it as a Rust &str, otherwise an error.
|
||||
pub fn to_str(&self) -> Result<&str, Utf8Error> {
|
||||
str::from_utf8(self.as_bytes())
|
||||
}
|
||||
|
||||
/// If the contents of the C++ string are valid UTF-8, this function returns
|
||||
/// a view as a Cow::Borrowed &str. Otherwise replaces any invalid UTF-8
|
||||
/// sequences with the U+FFFD [replacement character] and returns a
|
||||
/// Cow::Owned String.
|
||||
///
|
||||
/// [replacement character]: https://doc.rust-lang.org/std/char/constant.REPLACEMENT_CHARACTER.html
|
||||
pub fn to_string_lossy(&self) -> Cow<str> {
|
||||
String::from_utf8_lossy(self.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CxxString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
Display::fmt(&String::from_utf8_lossy(self.as_bytes()), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CxxString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
Debug::fmt(&String::from_utf8_lossy(self.as_bytes()), f)
|
||||
}
|
||||
}
|
168
src/cxxbridge.cc
Normal file
168
src/cxxbridge.cc
Normal file
@ -0,0 +1,168 @@
|
||||
#include "../include/cxxbridge.h"
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace cxxbridge = cxxbridge00;
|
||||
|
||||
extern "C" {
|
||||
const char *cxxbridge00$cxx_string$data(const std::string &s) noexcept {
|
||||
return s.data();
|
||||
}
|
||||
|
||||
size_t cxxbridge00$cxx_string$length(const std::string &s) noexcept {
|
||||
return s.length();
|
||||
}
|
||||
|
||||
// RustString
|
||||
void cxxbridge00$rust_string$new(cxxbridge::RustString *self) noexcept;
|
||||
void cxxbridge00$rust_string$clone(cxxbridge::RustString *self,
|
||||
const cxxbridge::RustString &other) noexcept;
|
||||
bool cxxbridge00$rust_string$from(cxxbridge::RustString *self, const char *ptr,
|
||||
size_t len) noexcept;
|
||||
void cxxbridge00$rust_string$drop(cxxbridge::RustString *self) noexcept;
|
||||
const char *
|
||||
cxxbridge00$rust_string$ptr(const cxxbridge::RustString *self) noexcept;
|
||||
size_t cxxbridge00$rust_string$len(const cxxbridge::RustString *self) noexcept;
|
||||
|
||||
// RustStr
|
||||
bool cxxbridge00$rust_str$valid(const char *ptr, size_t len) noexcept;
|
||||
} // extern "C"
|
||||
|
||||
namespace cxxbridge00 {
|
||||
|
||||
RustString::RustString() noexcept { cxxbridge00$rust_string$new(this); }
|
||||
|
||||
RustString::RustString(const RustString &other) noexcept {
|
||||
cxxbridge00$rust_string$clone(this, other);
|
||||
}
|
||||
|
||||
RustString::RustString(RustString &&other) noexcept {
|
||||
this->repr = other.repr;
|
||||
cxxbridge00$rust_string$new(&other);
|
||||
}
|
||||
|
||||
RustString::RustString(const char *s) {
|
||||
auto len = strlen(s);
|
||||
if (!cxxbridge00$rust_string$from(this, s, len)) {
|
||||
throw std::invalid_argument("data for RustString is not utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
RustString::RustString(const std::string &s) {
|
||||
auto ptr = s.data();
|
||||
auto len = s.length();
|
||||
if (!cxxbridge00$rust_string$from(this, ptr, len)) {
|
||||
throw std::invalid_argument("data for RustString is not utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
RustString::~RustString() noexcept { cxxbridge00$rust_string$drop(this); }
|
||||
|
||||
RustString::operator std::string() const {
|
||||
return std::string(this->data(), this->size());
|
||||
}
|
||||
|
||||
RustString &RustString::operator=(const RustString &other) noexcept {
|
||||
if (this != &other) {
|
||||
cxxbridge00$rust_string$drop(this);
|
||||
cxxbridge00$rust_string$clone(this, other);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
RustString &RustString::operator=(RustString &&other) noexcept {
|
||||
if (this != &other) {
|
||||
cxxbridge00$rust_string$drop(this);
|
||||
this->repr = other.repr;
|
||||
cxxbridge00$rust_string$new(&other);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
const char *RustString::data() const noexcept {
|
||||
return cxxbridge00$rust_string$ptr(this);
|
||||
}
|
||||
|
||||
size_t RustString::size() const noexcept {
|
||||
return cxxbridge00$rust_string$len(this);
|
||||
}
|
||||
|
||||
size_t RustString::length() const noexcept {
|
||||
return cxxbridge00$rust_string$len(this);
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const RustString &s) {
|
||||
os.write(s.data(), s.size());
|
||||
return os;
|
||||
}
|
||||
|
||||
RustStr::RustStr() noexcept
|
||||
: repr(Repr{reinterpret_cast<const char *>(this), 0}) {}
|
||||
|
||||
RustStr::RustStr(const char *s) : repr(Repr{s, strlen(s)}) {
|
||||
if (!cxxbridge00$rust_str$valid(this->repr.ptr, this->repr.len)) {
|
||||
throw std::invalid_argument("data for RustStr is not utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
RustStr::RustStr(const std::string &s) : repr(Repr{s.data(), s.length()}) {
|
||||
if (!cxxbridge00$rust_str$valid(this->repr.ptr, this->repr.len)) {
|
||||
throw std::invalid_argument("data for RustStr is not utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
RustStr::RustStr(const RustStr &) noexcept = default;
|
||||
|
||||
RustStr &RustStr::operator=(RustStr other) noexcept {
|
||||
this->repr = other.repr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
RustStr::operator std::string() const {
|
||||
return std::string(this->data(), this->size());
|
||||
}
|
||||
|
||||
const char *RustStr::data() const noexcept { return this->repr.ptr; }
|
||||
|
||||
size_t RustStr::size() const noexcept { return this->repr.len; }
|
||||
|
||||
size_t RustStr::length() const noexcept { return this->repr.len; }
|
||||
|
||||
RustStr::RustStr(Repr repr_) noexcept : repr(repr_) {}
|
||||
|
||||
RustStr::operator Repr() noexcept { return this->repr; }
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const RustStr &s) {
|
||||
os.write(s.data(), s.size());
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace cxxbridge00
|
||||
|
||||
extern "C" {
|
||||
void cxxbridge00$unique_ptr$std$string$null(
|
||||
std::unique_ptr<std::string> *ptr) noexcept {
|
||||
new (ptr) std::unique_ptr<std::string>();
|
||||
}
|
||||
void cxxbridge00$unique_ptr$std$string$new(std::unique_ptr<std::string> *ptr,
|
||||
std::string *value) noexcept {
|
||||
new (ptr) std::unique_ptr<std::string>(new std::string(std::move(*value)));
|
||||
}
|
||||
void cxxbridge00$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 *cxxbridge00$unique_ptr$std$string$get(
|
||||
const std::unique_ptr<std::string> &ptr) noexcept {
|
||||
return ptr.get();
|
||||
}
|
||||
std::string *cxxbridge00$unique_ptr$std$string$release(
|
||||
std::unique_ptr<std::string> &ptr) noexcept {
|
||||
return ptr.release();
|
||||
}
|
||||
void cxxbridge00$unique_ptr$std$string$drop(
|
||||
std::unique_ptr<std::string> *ptr) noexcept {
|
||||
ptr->~unique_ptr();
|
||||
}
|
||||
} // extern "C"
|
16
src/error.rs
Normal file
16
src/error.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use std::io;
|
||||
use std::path::StripPrefixError;
|
||||
use thiserror::Error;
|
||||
|
||||
pub(super) type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error(transparent)]
|
||||
pub(super) enum Error {
|
||||
#[error("missing OUT_DIR environment variable")]
|
||||
MissingOutDir,
|
||||
#[error("failed to locate target dir")]
|
||||
TargetDir,
|
||||
Io(#[from] io::Error),
|
||||
StripPrefix(#[from] StripPrefixError),
|
||||
}
|
458
src/lib.rs
Normal file
458
src/lib.rs
Normal file
@ -0,0 +1,458 @@
|
||||
//! This library provides a **safe** mechanism for calling C++ code from Rust
|
||||
//! and Rust code from C++, not subject to the many ways that things can go
|
||||
//! wrong when using bindgen or cbindgen to generate unsafe C-style bindings.
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! *Compiler support: requires rustc 1.42+ (beta on January 30, stable on March
|
||||
//! 12)*
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! The idea is that we define the signatures of both sides of our FFI boundary
|
||||
//! embedded together in one Rust module (the next section shows an example).
|
||||
//! From this, CXX receives a complete picture of the boundary to perform static
|
||||
//! analyses against the types and function signatures to uphold both Rust's and
|
||||
//! C++'s invariants and requirements.
|
||||
//!
|
||||
//! If everything checks out statically, then CXX uses a pair of code generators
|
||||
//! to emit the relevant `extern "C"` signatures on both sides together with any
|
||||
//! necessary static assertions for later in the build process to verify
|
||||
//! correctness. On the Rust side this code generator is simply an attribute
|
||||
//! procedural macro. On the C++ side it can be a small Cargo build script if
|
||||
//! your build is managed by Cargo, or for other build systems like Bazel or
|
||||
//! Buck we provide a command line tool which generates the header and source
|
||||
//! file and should be easy to integrate.
|
||||
//!
|
||||
//! 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 types from whichever side they
|
||||
//! please, such as Rust's `String` or C++'s `std::string`, Rust's `Box` or
|
||||
//! C++'s `std::unique_ptr`, Rust's `Vec` or C++'s `std::vector`, etc in any
|
||||
//! combination. CXX guarantees an ABI-compatible signature that both sides
|
||||
//! understand, based on builtin bindings for key standard library types to
|
||||
//! expose an idiomatic API on those types to the other language. For example
|
||||
//! when manipulating a C++ string from Rust, its `len()` method becomes a call
|
||||
//! of the `size()` member function defined by C++; when manipulation a Rust
|
||||
//! string from C++, its `size()` member function calls Rust's `len()`.
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! A runnable version of this example is provided under the *demo-rs* directory
|
||||
//! of https://github.com/dtolnay/cxx (with the C++ side of the implementation
|
||||
//! in the *demo-cxx* directory). To try it out, jump into demo-rs and run
|
||||
//! `cargo run`.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! #[cxx::bridge]
|
||||
//! mod ffi {
|
||||
//! // Any shared structs, whose fields will be visible to both languages.
|
||||
//! struct SharedThing {
|
||||
//! z: i32,
|
||||
//! y: Box<ThingR>,
|
||||
//! x: UniquePtr<ThingC>,
|
||||
//! }
|
||||
//!
|
||||
//! extern "C" {
|
||||
//! // One or more headers with the matching C++ declarations. Our code
|
||||
//! // generators don't read it but it gets #include'd and used in static
|
||||
//! // assertions to ensure our picture of the FFI boundary is accurate.
|
||||
//! include!("demo-cxx/demo.h");
|
||||
//!
|
||||
//! // Zero or more opaque types which both languages can pass around but
|
||||
//! // only C++ can see the fields.
|
||||
//! type ThingC;
|
||||
//!
|
||||
//! // Functions implemented in C++.
|
||||
//! fn make_demo(appname: &str) -> UniquePtr<ThingC>;
|
||||
//! fn get_name(thing: &ThingC) -> &CxxString;
|
||||
//! fn do_thing(state: SharedThing);
|
||||
//! }
|
||||
//!
|
||||
//! extern "Rust" {
|
||||
//! // Zero or more opaque types which both languages can pass around but
|
||||
//! // only Rust can see the fields.
|
||||
//! type ThingR;
|
||||
//!
|
||||
//! // Functions implemented in Rust.
|
||||
//! fn print_r(r: &ThingR);
|
||||
//! }
|
||||
//! }
|
||||
//! #
|
||||
//! # pub struct ThingR(usize);
|
||||
//! #
|
||||
//! # fn print_r(r: &ThingR) {
|
||||
//! # println!("called back with r={}", r.0);
|
||||
//! # }
|
||||
//! #
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! Now we simply provide C++ definitions of all the things in the `extern "C"`
|
||||
//! block and Rust definitions of all the things in the `extern "Rust"` block,
|
||||
//! and get to call back and forth safely.
|
||||
//!
|
||||
//! Here are links to the complete set of source files involved in the demo:
|
||||
//!
|
||||
//! - [demo-rs/src/main.rs](https://github.com/dtolnay/cxx/blob/master/demo-rs/src/main.rs)
|
||||
//! - [demo-rs/build.rs](https://github.com/dtolnay/cxx/blob/master/demo-rs/build.rs)
|
||||
//! - [demo-cxx/demo.h](https://github.com/dtolnay/cxx/blob/master/demo-cxx/demo.h)
|
||||
//! - [demo-cxx/demo.cc](https://github.com/dtolnay/cxx/blob/master/demo-cxx/demo.cc)
|
||||
//!
|
||||
//! To look at the code generated in both languages for the example by the CXX
|
||||
//! code generators:
|
||||
//!
|
||||
//! ```console
|
||||
//! # run Rust code generator and print to stdout
|
||||
//! # (requires https://github.com/dtolnay/cargo-expand)
|
||||
//! $ cargo expand --manifest-path demo-rs/Cargo.toml
|
||||
//!
|
||||
//! # run C++ code generator and print to stdout
|
||||
//! $ cargo run --manifest-path cmd/Cargo.toml -- demo-rs/src/main.rs
|
||||
//! ```
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Details
|
||||
//!
|
||||
//! As seen in the example, the language of the FFI boundary involves 3 kinds of
|
||||
//! items:
|
||||
//!
|
||||
//! - **Shared structs** — their fields are made visible to both
|
||||
//! languages. The definition written within cxx::bridge is the single 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 `UniquePtr`. 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.
|
||||
//!
|
||||
//! 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.
|
||||
//!
|
||||
//! 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 where necessary to make it all work.
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Comparison vs bindgen and cbindgen
|
||||
//!
|
||||
//! Notice that with CXX there is repetition of all the function signatures:
|
||||
//! they are typed out once where the implementation is defined (in C++ or Rust)
|
||||
//! and again inside the cxx::bridge module, though compile-time assertions
|
||||
//! guarantee these are kept in sync. This is different from [bindgen] and
|
||||
//! [cbindgen] where function signatures are typed by a human once and the tool
|
||||
//! consumes them in one language and emits them in the other language.
|
||||
//!
|
||||
//! [bindgen]: https://github.com/rust-lang/rust-bindgen
|
||||
//! [cbindgen]: https://github.com/eqrion/cbindgen/
|
||||
//!
|
||||
//! This is because CXX fills a somewhat different role. It is a lower level
|
||||
//! tool than bindgen or cbindgen in a sense; you can think of it as being a
|
||||
//! replacement for the concept of `extern "C"` signatures as we know them,
|
||||
//! rather than a replacement for a bindgen. It would be reasonable to build a
|
||||
//! higher level bindgen-like tool on top of CXX which consumes a C++ header
|
||||
//! and/or Rust module (and/or IDL like Thrift) as source of truth and generates
|
||||
//! the cxx::bridge, eliminating the repetition while leveraging the static
|
||||
//! analysis safety guarantees of CXX.
|
||||
//!
|
||||
//! But note in other ways CXX is higher level than the bindgens, with rich
|
||||
//! support for common standard library types. Frequently with bindgen when we
|
||||
//! are dealing with an idiomatic C++ API we would end up manually wrapping that
|
||||
//! API in C-style raw pointer functions, applying bindgen to get unsafe raw
|
||||
//! pointer Rust functions, and replicating the API again to expose those
|
||||
//! idiomatically in Rust. That's a much worse form of repetition because it is
|
||||
//! unsafe all the way through.
|
||||
//!
|
||||
//! By using a CXX bridge as the shared understanding between the languages,
|
||||
//! rather than `extern "C"` C-style signatures as the shared understanding,
|
||||
//! common FFI use cases become expressible using 100% safe code.
|
||||
//!
|
||||
//! It would also be reasonable to mix and match, using CXX bridge for the 95%
|
||||
//! of your FFI that is straightforward and doing the remaining few oddball
|
||||
//! signatures the old fashioned way with bindgen and cbindgen, if for some
|
||||
//! reason CXX's static restrictions get in the way. Please file an issue if you
|
||||
//! end up taking this approach so that we know what ways it would be worthwhile
|
||||
//! to make the tool more expressive.
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Cargo-based setup
|
||||
//!
|
||||
//! For builds that are orchestrated by Cargo, you will use a build script that
|
||||
//! runs CXX's C++ code generator and compiles the resulting C++ code along with
|
||||
//! any other C++ code for your crate.
|
||||
//!
|
||||
//! 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
|
||||
//!
|
||||
//! ```no_run
|
||||
//! // build.rs
|
||||
//!
|
||||
//! fn main() {
|
||||
//! cxx::Build::new()
|
||||
//! .bridge("src/main.rs") // returns a cc::Build
|
||||
//! .file("../demo-cxx/demo.cc")
|
||||
//! .flag("-std=c++11")
|
||||
//! .compile("cxxbridge-demo");
|
||||
//!
|
||||
//! println!("cargo:rerun-if-changed=src/main.rs");
|
||||
//! println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
|
||||
//! println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! <br><br>
|
||||
//!
|
||||
//! # Non-Cargo setup
|
||||
//!
|
||||
//! For use in non-Cargo builds like Bazel or Buck, CXX provides an alternate
|
||||
//! way of invoking the C++ code generator as a standalone command line tool.
|
||||
//! The tool is packaged as the `cxxbridge-cmd` crate on crates.io or can be
|
||||
//! built from the *cmd* directory of https://github.com/dtolnay/cxx.
|
||||
//!
|
||||
//! ```bash
|
||||
//! $ cargo install cxxbridge-cmd
|
||||
//!
|
||||
//! $ cxxbridge src/main.rs --header > path/to/mybridge.h
|
||||
//! $ cxxbridge src/main.rs > path/to/mybridge.cc
|
||||
//! ```
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Safety
|
||||
//!
|
||||
//! Be aware that the design of this library is intentionally restrictive and
|
||||
//! opinionated! It isn't a goal to be powerful enough to handle arbitrary
|
||||
//! signatures in either language. Instead this project is about carving out a
|
||||
//! reasonably expressive set of functionality about which we can make useful
|
||||
//! safety guarantees today and maybe 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 are used to.
|
||||
//!
|
||||
//! Some of the considerations that go into ensuring safety are:
|
||||
//!
|
||||
//! - By design, our paired code generators work together to control both sides
|
||||
//! of the FFI boundary. Ordinarily in Rust writing your own `extern "C"`
|
||||
//! blocks is unsafe because the Rust compiler has no way to know whether the
|
||||
//! signatures you've written actually match the signatures implemented in the
|
||||
//! other language. With CXX we achieve that visibility and know what's on the
|
||||
//! other side.
|
||||
//!
|
||||
//! - Our static analysis detects and prevents passing types by value that
|
||||
//! shouldn't be passed by value from C++ to Rust, for example because they
|
||||
//! may contain internal pointers that would be screwed up by Rust's move
|
||||
//! behavior.
|
||||
//!
|
||||
//! - To many people's surprise, it is possible to have a struct in Rust and a
|
||||
//! struct in C++ with exactly the same layout / fields / alignment /
|
||||
//! everything, and still not the same ABI when passed by value. This is a
|
||||
//! longstanding bindgen bug that leads to segfaults in absolutely
|
||||
//! correct-looking code ([rust-lang/rust-bindgen#778]). CXX knows about this
|
||||
//! and can insert the necessary zero-cost workaround transparently where
|
||||
//! needed, so go ahead and pass your structs by value without worries. This
|
||||
//! is made possible by owning both sides of the boundary rather than just
|
||||
//! one.
|
||||
//!
|
||||
//! - Template instantiations: for example in order to expose a UniquePtr\<T\>
|
||||
//! type in Rust backed by a real C++ unique\_ptr, we have a way of using a
|
||||
//! Rust trait to connect the behavior back to the template instantiations
|
||||
//! performed by the other language.
|
||||
//!
|
||||
//! [rust-lang/rust-bindgen#778]: https://github.com/rust-lang/rust-bindgen/issues/778
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Builtin types
|
||||
//!
|
||||
//! 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 functions.
|
||||
//!
|
||||
//! <table>
|
||||
//! <tr><th>name in Rust</th><th>name in C++</th><th>restrictions</th></tr>
|
||||
//! <tr><td>String</td><td>cxxbridge::RustString</td><td></td></tr>
|
||||
//! <tr><td>&str</td><td>cxxbridge::RustStr</td><td></td></tr>
|
||||
//! <tr><td><a href="https://docs.rs/cxx/0.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>cxxbridge::RustBox<T></td><td><sup><i>cannot hold opaque C++ type</i></sup></td></tr>
|
||||
//! <tr><td><a href="https://docs.rs/cxx/0.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></td><td></td><td></td></tr>
|
||||
//! </table>
|
||||
//!
|
||||
//! The C++ API of the `cxxbridge` namespace is defined by the
|
||||
//! *include/cxxbridge.h* file in https://github.com/dtolnay/cxx. You will need
|
||||
//! to include this header in your C++ code when working with those types.
|
||||
//!
|
||||
//! The following types are intended to be supported "soon" but are just not
|
||||
//! 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.
|
||||
//!
|
||||
//! <table>
|
||||
//! <tr><th>name in Rust</th><th>name in C++</th></tr>
|
||||
//! <tr><td>&[T]</td><td></td></tr>
|
||||
//! <tr><td>Vec<T></td><td></td></tr>
|
||||
//! <tr><td>BTreeMap<K, V></td><td></td></tr>
|
||||
//! <tr><td>HashMap<K, V></td><td></td></tr>
|
||||
//! <tr><td></td><td>std::vector<T></td></tr>
|
||||
//! <tr><td></td><td>std::map<K, V></td></tr>
|
||||
//! <tr><td></td><td>std::unordered_map<K, V></td></tr>
|
||||
//! </table>
|
||||
|
||||
#![deny(improper_ctypes)]
|
||||
#![allow(
|
||||
clippy::large_enum_variant,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::module_inception,
|
||||
clippy::new_without_default,
|
||||
clippy::or_fun_call,
|
||||
clippy::ptr_arg,
|
||||
clippy::toplevel_ref_arg,
|
||||
clippy::transmute_ptr_to_ptr,
|
||||
clippy::useless_let_if_seq
|
||||
)]
|
||||
|
||||
mod cxx_string;
|
||||
mod error;
|
||||
mod gen;
|
||||
mod opaque;
|
||||
mod paths;
|
||||
mod rust_str;
|
||||
mod rust_string;
|
||||
mod syntax;
|
||||
mod unique_ptr;
|
||||
mod unwind;
|
||||
|
||||
pub use crate::cxx_string::CxxString;
|
||||
pub use crate::unique_ptr::UniquePtr;
|
||||
pub use cxxbridge_macro::bridge;
|
||||
|
||||
// Not public API.
|
||||
#[doc(hidden)]
|
||||
pub mod private {
|
||||
pub use crate::opaque::Opaque;
|
||||
pub use crate::rust_str::RustStr;
|
||||
pub use crate::rust_string::RustString;
|
||||
pub use crate::unique_ptr::UniquePtrTarget;
|
||||
pub use crate::unwind::catch_unwind;
|
||||
}
|
||||
|
||||
use crate::error::Result;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
|
||||
/// The CXX code generator for constructing and compiling C++ code.
|
||||
///
|
||||
/// This is intended to be used from Cargo build scripts to execute CXX's
|
||||
/// C++ code generator, set up any additional compiler flags depending on
|
||||
/// the use case, and make the C++ compiler invocation.
|
||||
///
|
||||
/// <br>
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Example of a canonical Cargo build script that builds a CXX bridge:
|
||||
///
|
||||
/// ```no_run
|
||||
/// // build.rs
|
||||
///
|
||||
/// fn main() {
|
||||
/// cxx::Build::new()
|
||||
/// .bridge("src/main.rs")
|
||||
/// .file("../demo-cxx/demo.cc")
|
||||
/// .flag("-std=c++11")
|
||||
/// .compile("cxxbridge-demo");
|
||||
///
|
||||
/// println!("cargo:rerun-if-changed=src/main.rs");
|
||||
/// println!("cargo:rerun-if-changed=../demo-cxx/demo.h");
|
||||
/// println!("cargo:rerun-if-changed=../demo-cxx/demo.cc");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// A runnable working setup with this build script is shown in the
|
||||
/// *demo-rs* and *demo-cxx* directories of
|
||||
/// [https://github.com/dtolnay/cxx](https://github.com/dtolnay/cxx).
|
||||
///
|
||||
/// <br>
|
||||
///
|
||||
/// # Alternatives
|
||||
///
|
||||
/// For use in non-Cargo builds like Bazel or Buck, CXX provides an
|
||||
/// alternate way of invoking the C++ code generator as a standalone command
|
||||
/// line tool. The tool is packaged as the `cxxbridge-cmd` crate.
|
||||
///
|
||||
/// ```bash
|
||||
/// $ cargo install cxxbridge-cmd # or build it from the repo
|
||||
///
|
||||
/// $ cxxbridge src/main.rs --header > path/to/mybridge.h
|
||||
/// $ cxxbridge src/main.rs > path/to/mybridge.cc
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub struct Build {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Build {
|
||||
/// Begin with a [`cc::Build`] in its default configuration.
|
||||
pub fn new() -> Self {
|
||||
Build { _private: () }
|
||||
}
|
||||
|
||||
/// This returns a [`cc::Build`] on which you should continue to set up
|
||||
/// any additional source files or compiler flags, and lastly call its
|
||||
/// [`compile`] method to execute the C++ build.
|
||||
///
|
||||
/// [`compile`]: https://docs.rs/cc/1.0.49/cc/struct.Build.html#method.compile
|
||||
#[must_use]
|
||||
pub fn bridge(&self, rust_source_file: impl AsRef<Path>) -> cc::Build {
|
||||
match try_generate_bridge(rust_source_file.as_ref()) {
|
||||
Ok(build) => build,
|
||||
Err(err) => {
|
||||
let _ = writeln!(io::stderr(), "\n\ncxxbridge error: {}\n\n", err);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_generate_bridge(rust_source_file: &Path) -> Result<cc::Build> {
|
||||
let header = gen::do_generate_header(rust_source_file);
|
||||
let header_path = paths::out_with_extension(rust_source_file, ".h")?;
|
||||
fs::create_dir_all(header_path.parent().unwrap())?;
|
||||
fs::write(&header_path, header)?;
|
||||
paths::symlink_header(&header_path, rust_source_file);
|
||||
|
||||
let bridge = gen::do_generate_bridge(rust_source_file);
|
||||
let bridge_path = paths::out_with_extension(rust_source_file, ".cc")?;
|
||||
fs::write(&bridge_path, bridge)?;
|
||||
let mut build = paths::cc_build();
|
||||
build.file(&bridge_path);
|
||||
|
||||
Ok(build)
|
||||
}
|
16
src/opaque.rs
Normal file
16
src/opaque.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use std::mem;
|
||||
|
||||
// . size = 0
|
||||
// . align = 1
|
||||
// . ffi-safe
|
||||
// . !Send
|
||||
// . !Sync
|
||||
#[repr(C, packed)]
|
||||
pub struct Opaque {
|
||||
_private: [*const u8; 0],
|
||||
}
|
||||
|
||||
fn _assert() {
|
||||
let _: [(); 0] = [(); mem::size_of::<Opaque>()];
|
||||
let _: [(); 1] = [(); mem::align_of::<Opaque>()];
|
||||
}
|
72
src/paths.rs
Normal file
72
src/paths.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use crate::error::{Error, Result};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::os;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn out_dir() -> Result<PathBuf> {
|
||||
env::var_os("OUT_DIR")
|
||||
.map(PathBuf::from)
|
||||
.ok_or(Error::MissingOutDir)
|
||||
}
|
||||
|
||||
pub(crate) fn cc_build() -> cc::Build {
|
||||
try_cc_build().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn try_cc_build() -> Result<cc::Build> {
|
||||
let target_dir = target_dir()?;
|
||||
|
||||
let mut build = cc::Build::new();
|
||||
build.include(target_dir.join("cxxbridge"));
|
||||
build.include(target_dir.parent().unwrap());
|
||||
Ok(build)
|
||||
}
|
||||
|
||||
// Symlink the header file into a predictable place. The header generated from
|
||||
// path/to/mod.rs gets linked to targets/cxxbridge/path/to/mod.h.
|
||||
pub(crate) fn symlink_header(path: &Path, original: &Path) {
|
||||
let _ = try_symlink_header(path, original);
|
||||
}
|
||||
|
||||
fn try_symlink_header(path: &Path, original: &Path) -> Result<()> {
|
||||
let suffix = relative_to_parent_of_target_dir(original)?;
|
||||
let dst = target_dir()?.join("cxxbridge").join(suffix);
|
||||
|
||||
fs::create_dir_all(dst.parent().unwrap())?;
|
||||
#[cfg(unix)]
|
||||
os::unix::fs::symlink(path, dst)?;
|
||||
#[cfg(windows)]
|
||||
os::windows::fs::symlink_file(path, dst)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn relative_to_parent_of_target_dir(original: &Path) -> Result<PathBuf> {
|
||||
let target_dir = target_dir()?;
|
||||
let parent_of_target_dir = target_dir.parent().unwrap();
|
||||
let original = original.canonicalize()?;
|
||||
let suffix = original.strip_prefix(parent_of_target_dir)?;
|
||||
Ok(suffix.to_owned())
|
||||
}
|
||||
|
||||
pub(crate) fn out_with_extension(path: &Path, ext: &str) -> Result<PathBuf> {
|
||||
let mut file_name = path.file_name().unwrap().to_owned();
|
||||
file_name.push(ext);
|
||||
|
||||
let out_dir = out_dir()?;
|
||||
let rel = relative_to_parent_of_target_dir(path)?;
|
||||
Ok(out_dir.join(rel).with_file_name(file_name))
|
||||
}
|
||||
|
||||
fn target_dir() -> Result<PathBuf> {
|
||||
let mut dir = out_dir()?.canonicalize()?;
|
||||
loop {
|
||||
if dir.ends_with("target") {
|
||||
return Ok(dir);
|
||||
}
|
||||
if !dir.pop() {
|
||||
return Err(Error::TargetDir);
|
||||
}
|
||||
}
|
||||
}
|
30
src/rust_str.rs
Normal file
30
src/rust_str.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use std::slice;
|
||||
use std::str;
|
||||
|
||||
// Not necessarily ABI compatible with &str. Codegen performs the translation.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct RustStr {
|
||||
ptr: *const u8,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl RustStr {
|
||||
pub fn from(s: &str) -> Self {
|
||||
RustStr {
|
||||
ptr: s.as_ptr(),
|
||||
len: s.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn as_str<'a>(self) -> &'a str {
|
||||
let slice = slice::from_raw_parts(self.ptr, self.len);
|
||||
str::from_utf8_unchecked(slice)
|
||||
}
|
||||
}
|
||||
|
||||
#[export_name = "cxxbridge00$rust_str$valid"]
|
||||
unsafe extern "C" fn str_valid(ptr: *const u8, len: usize) -> bool {
|
||||
let slice = slice::from_raw_parts(ptr, len);
|
||||
str::from_utf8(slice).is_ok()
|
||||
}
|
73
src/rust_string.rs
Normal file
73
src/rust_string.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use std::mem::{self, ManuallyDrop, MaybeUninit};
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RustString {
|
||||
repr: String,
|
||||
}
|
||||
|
||||
impl RustString {
|
||||
pub fn from(s: String) -> Self {
|
||||
RustString { repr: s }
|
||||
}
|
||||
|
||||
pub fn from_ref(s: &String) -> &Self {
|
||||
unsafe { std::mem::transmute::<&String, &RustString>(s) }
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
self.repr
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> &String {
|
||||
&self.repr
|
||||
}
|
||||
}
|
||||
|
||||
#[export_name = "cxxbridge00$rust_string$new"]
|
||||
unsafe extern "C" fn string_new(this: &mut MaybeUninit<String>) {
|
||||
ptr::write(this.as_mut_ptr(), String::new());
|
||||
}
|
||||
|
||||
#[export_name = "cxxbridge00$rust_string$clone"]
|
||||
unsafe extern "C" fn string_clone(this: &mut MaybeUninit<String>, other: &String) {
|
||||
ptr::write(this.as_mut_ptr(), other.clone());
|
||||
}
|
||||
|
||||
#[export_name = "cxxbridge00$rust_string$from"]
|
||||
unsafe extern "C" fn string_from(
|
||||
this: &mut MaybeUninit<String>,
|
||||
ptr: *const u8,
|
||||
len: usize,
|
||||
) -> bool {
|
||||
let slice = slice::from_raw_parts(ptr, len);
|
||||
match str::from_utf8(slice) {
|
||||
Ok(s) => {
|
||||
ptr::write(this.as_mut_ptr(), s.to_owned());
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[export_name = "cxxbridge00$rust_string$drop"]
|
||||
unsafe extern "C" fn string_drop(this: &mut ManuallyDrop<String>) {
|
||||
ManuallyDrop::drop(this);
|
||||
}
|
||||
|
||||
#[export_name = "cxxbridge00$rust_string$ptr"]
|
||||
unsafe extern "C" fn string_ptr(this: &String) -> *const u8 {
|
||||
this.as_ptr()
|
||||
}
|
||||
|
||||
#[export_name = "cxxbridge00$rust_string$len"]
|
||||
unsafe extern "C" fn string_len(this: &String) -> usize {
|
||||
this.len()
|
||||
}
|
||||
|
||||
fn _assert() {
|
||||
let _: [(); mem::size_of::<[usize; 3]>()] = [(); mem::size_of::<String>()];
|
||||
let _: [(); mem::align_of::<usize>()] = [(); mem::align_of::<String>()];
|
||||
}
|
1
src/syntax
Symbolic link
1
src/syntax
Symbolic link
@ -0,0 +1 @@
|
||||
../syntax
|
173
src/unique_ptr.rs
Normal file
173
src/unique_ptr.rs
Normal file
@ -0,0 +1,173 @@
|
||||
use crate::cxx_string::CxxString;
|
||||
use std::ffi::c_void;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::ptr;
|
||||
|
||||
/// Binding to C++ `std::unique_ptr<T, std::default_delete<T>>`.
|
||||
#[repr(C)]
|
||||
pub struct UniquePtr<T>
|
||||
where
|
||||
T: UniquePtrTarget,
|
||||
{
|
||||
repr: *mut c_void,
|
||||
ty: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> UniquePtr<T>
|
||||
where
|
||||
T: UniquePtrTarget,
|
||||
{
|
||||
/// Makes a new UniquePtr wrapping a null pointer.
|
||||
///
|
||||
/// Matches the behavior of default-constructing a std::unique\_ptr.
|
||||
pub fn null() -> Self {
|
||||
UniquePtr {
|
||||
repr: T::__null(),
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates memory on the heap and makes a UniquePtr pointing to it.
|
||||
pub fn new(value: T) -> Self {
|
||||
UniquePtr {
|
||||
repr: T::__new(value),
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether the UniquePtr does not own an object.
|
||||
///
|
||||
/// This is the opposite of [std::unique_ptr\<T\>::operator bool](https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_bool).
|
||||
pub fn is_null(&self) -> bool {
|
||||
let ptr = unsafe { T::__get(self.repr) };
|
||||
ptr.is_null()
|
||||
}
|
||||
|
||||
/// Returns a reference to the object owned by this UniquePtr if any,
|
||||
/// otherwise None.
|
||||
pub fn as_ref(&self) -> Option<&T> {
|
||||
unsafe { T::__get(self.repr).as_ref() }
|
||||
}
|
||||
|
||||
/// Consumes the UniquePtr, releasing its ownership of the heap-allocated T.
|
||||
///
|
||||
/// Matches the behavior of [std::unique_ptr\<T\>::release](https://en.cppreference.com/w/cpp/memory/unique_ptr/release).
|
||||
pub fn into_raw(self) -> *mut T {
|
||||
let ptr = unsafe { T::__release(self.repr) };
|
||||
mem::forget(self);
|
||||
ptr
|
||||
}
|
||||
|
||||
/// Constructs a UniquePtr retaking ownership of a pointer previously
|
||||
/// obtained from `into_raw`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because improper use may lead to memory
|
||||
/// problems. For example a double-free may occur if the function is called
|
||||
/// twice on the same raw pointer.
|
||||
pub unsafe fn from_raw(raw: *mut T) -> Self {
|
||||
UniquePtr {
|
||||
repr: T::__raw(raw),
|
||||
ty: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Send for UniquePtr<T> where T: Send + UniquePtrTarget {}
|
||||
unsafe impl<T> Sync for UniquePtr<T> where T: Sync + UniquePtrTarget {}
|
||||
|
||||
impl<T> Drop for UniquePtr<T>
|
||||
where
|
||||
T: UniquePtrTarget,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
unsafe { T::__drop(self.repr) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for UniquePtr<T>
|
||||
where
|
||||
T: Debug + UniquePtrTarget,
|
||||
{
|
||||
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 UniquePtr<T>
|
||||
where
|
||||
T: Display + UniquePtrTarget,
|
||||
{
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.as_ref() {
|
||||
None => formatter.write_str("nullptr"),
|
||||
Some(value) => Display::fmt(value, formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Methods are private; not intended to be implemented outside of cxxbridge
|
||||
// codebase.
|
||||
pub unsafe trait UniquePtrTarget {
|
||||
#[doc(hidden)]
|
||||
fn __null() -> *mut c_void;
|
||||
#[doc(hidden)]
|
||||
fn __new(value: Self) -> *mut c_void;
|
||||
#[doc(hidden)]
|
||||
unsafe fn __raw(raw: *mut Self) -> *mut c_void;
|
||||
#[doc(hidden)]
|
||||
unsafe fn __get(repr: *mut c_void) -> *const Self;
|
||||
#[doc(hidden)]
|
||||
unsafe fn __release(repr: *mut c_void) -> *mut Self;
|
||||
#[doc(hidden)]
|
||||
unsafe fn __drop(repr: *mut c_void);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
#[link_name = "cxxbridge00$unique_ptr$std$string$null"]
|
||||
fn unique_ptr_std_string_null(this: *mut *mut c_void);
|
||||
#[link_name = "cxxbridge00$unique_ptr$std$string$new"]
|
||||
fn unique_ptr_std_string_new(this: *mut *mut c_void, value: *mut CxxString);
|
||||
#[link_name = "cxxbridge00$unique_ptr$std$string$raw"]
|
||||
fn unique_ptr_std_string_raw(this: *mut *mut c_void, raw: *mut CxxString);
|
||||
#[link_name = "cxxbridge00$unique_ptr$std$string$get"]
|
||||
fn unique_ptr_std_string_get(this: *const *mut c_void) -> *const CxxString;
|
||||
#[link_name = "cxxbridge00$unique_ptr$std$string$release"]
|
||||
fn unique_ptr_std_string_release(this: *mut *mut c_void) -> *mut CxxString;
|
||||
#[link_name = "cxxbridge00$unique_ptr$std$string$drop"]
|
||||
fn unique_ptr_std_string_drop(this: *mut *mut c_void);
|
||||
}
|
||||
|
||||
unsafe impl UniquePtrTarget for CxxString {
|
||||
fn __null() -> *mut c_void {
|
||||
let mut repr = ptr::null_mut::<c_void>();
|
||||
unsafe { unique_ptr_std_string_null(&mut repr) }
|
||||
repr
|
||||
}
|
||||
fn __new(value: Self) -> *mut c_void {
|
||||
let mut repr = ptr::null_mut::<c_void>();
|
||||
let mut value = MaybeUninit::new(value);
|
||||
unsafe { unique_ptr_std_string_new(&mut repr, value.as_mut_ptr() as *mut Self) }
|
||||
repr
|
||||
}
|
||||
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
|
||||
}
|
||||
unsafe fn __get(repr: *mut c_void) -> *const Self {
|
||||
unique_ptr_std_string_get(&repr)
|
||||
}
|
||||
unsafe fn __release(mut repr: *mut c_void) -> *mut Self {
|
||||
unique_ptr_std_string_release(&mut repr)
|
||||
}
|
||||
unsafe fn __drop(mut repr: *mut c_void) {
|
||||
unique_ptr_std_string_drop(&mut repr);
|
||||
}
|
||||
}
|
20
src/unwind.rs
Normal file
20
src/unwind.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use std::io::{self, Write};
|
||||
use std::panic::{self, UnwindSafe};
|
||||
use std::process;
|
||||
|
||||
pub fn catch_unwind<F, R>(label: &'static str, foreign_call: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R + UnwindSafe,
|
||||
{
|
||||
match panic::catch_unwind(foreign_call) {
|
||||
Ok(ret) => ret,
|
||||
Err(_) => abort(label),
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
fn abort(label: &'static str) -> ! {
|
||||
let mut stdout = io::stdout();
|
||||
let _ = writeln!(stdout, "Error: panic in ffi function {}, aborting.", label);
|
||||
process::abort();
|
||||
}
|
40
syntax/atom.rs
Normal file
40
syntax/atom.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use proc_macro2::Ident;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Atom {
|
||||
Bool,
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
Usize,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
Isize,
|
||||
CxxString,
|
||||
RustString,
|
||||
}
|
||||
|
||||
impl Atom {
|
||||
pub fn from(ident: &Ident) -> Option<Self> {
|
||||
use self::Atom::*;
|
||||
match ident.to_string().as_str() {
|
||||
"bool" => Some(Bool),
|
||||
"u8" => Some(U8),
|
||||
"u16" => Some(U16),
|
||||
"u32" => Some(U32),
|
||||
"u64" => Some(U64),
|
||||
"usize" => Some(Usize),
|
||||
"i8" => Some(I8),
|
||||
"i16" => Some(I16),
|
||||
"i32" => Some(I32),
|
||||
"i64" => Some(I64),
|
||||
"isize" => Some(Isize),
|
||||
"CxxString" => Some(CxxString),
|
||||
"String" => Some(RustString),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
68
syntax/attrs.rs
Normal file
68
syntax/attrs.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use crate::syntax::{Derive, Doc};
|
||||
use proc_macro2::Ident;
|
||||
use syn::parse::{ParseStream, Parser};
|
||||
use syn::{Attribute, Error, LitStr, Path, Result, Token};
|
||||
|
||||
pub(super) fn parse_doc(attrs: &[Attribute]) -> Result<Doc> {
|
||||
let mut doc = Doc::new();
|
||||
let derives = None;
|
||||
parse(attrs, &mut doc, derives)?;
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
pub(super) fn parse(
|
||||
attrs: &[Attribute],
|
||||
doc: &mut Doc,
|
||||
mut derives: Option<&mut Vec<Ident>>,
|
||||
) -> Result<()> {
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("doc") {
|
||||
let lit = parse_doc_attribute.parse2(attr.tokens.clone())?;
|
||||
doc.push(lit);
|
||||
continue;
|
||||
} else if attr.path.is_ident("derive") {
|
||||
if let Some(derives) = &mut derives {
|
||||
derives.extend(attr.parse_args_with(parse_derive_attribute)?);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return Err(Error::new_spanned(attr, "unsupported attribute"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_doc_attribute(input: ParseStream) -> Result<LitStr> {
|
||||
input.parse::<Token![=]>()?;
|
||||
let lit: LitStr = input.parse()?;
|
||||
Ok(lit)
|
||||
}
|
||||
|
||||
fn parse_derive_attribute(input: ParseStream) -> Result<Vec<Ident>> {
|
||||
input
|
||||
.parse_terminated::<Path, Token![,]>(Path::parse_mod_style)?
|
||||
.into_iter()
|
||||
.map(|path| match path.get_ident() {
|
||||
Some(ident) if Derive::from(ident).is_some() => Ok(ident.clone()),
|
||||
_ => Err(Error::new_spanned(path, "unsupported derive")),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Derive {
|
||||
pub fn from(ident: &Ident) -> Option<Self> {
|
||||
match ident.to_string().as_str() {
|
||||
"Clone" => Some(Derive::Clone),
|
||||
"Copy" => Some(Derive::Copy),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Derive {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
Derive::Clone => "Clone",
|
||||
Derive::Copy => "Copy",
|
||||
}
|
||||
}
|
||||
}
|
203
syntax/check.rs
Normal file
203
syntax/check.rs
Normal file
@ -0,0 +1,203 @@
|
||||
use crate::syntax::atom::Atom::{self, *};
|
||||
use crate::syntax::{error, ident, Api, ExternFn, Ty1, Type, Types, Var};
|
||||
use proc_macro2::Ident;
|
||||
use syn::{Error, Result};
|
||||
|
||||
pub(crate) fn typecheck(apis: &[Api], types: &Types) -> Result<()> {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for ty in types {
|
||||
match ty {
|
||||
Type::Ident(ident) => {
|
||||
if Atom::from(ident).is_none()
|
||||
&& !types.structs.contains_key(ident)
|
||||
&& !types.cxx.contains(ident)
|
||||
&& !types.rust.contains(ident)
|
||||
{
|
||||
errors.push(unsupported_type(ident));
|
||||
}
|
||||
}
|
||||
Type::RustBox(ptr) => {
|
||||
if let Type::Ident(ident) = &ptr.inner {
|
||||
if types.cxx.contains(ident) {
|
||||
errors.push(unsupported_cxx_type_in_box(ptr));
|
||||
}
|
||||
if Atom::from(ident).is_none() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
errors.push(unsupported_box_target(ptr));
|
||||
}
|
||||
Type::UniquePtr(ptr) => {
|
||||
if let Type::Ident(ident) = &ptr.inner {
|
||||
if types.rust.contains(ident) {
|
||||
errors.push(unsupported_rust_type_in_unique_ptr(ptr));
|
||||
}
|
||||
match Atom::from(ident) {
|
||||
None | Some(CxxString) => continue,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
errors.push(unsupported_unique_ptr_target(ptr));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for api in apis {
|
||||
match api {
|
||||
Api::Struct(strct) => {
|
||||
for field in &strct.fields {
|
||||
if is_unsized(&field.ty, types) {
|
||||
errors.push(field_by_value(field, types));
|
||||
}
|
||||
}
|
||||
}
|
||||
Api::CxxFunction(efn) | Api::RustFunction(efn) => {
|
||||
for arg in &efn.args {
|
||||
if is_unsized(&arg.ty, types) {
|
||||
errors.push(argument_by_value(arg, types));
|
||||
}
|
||||
}
|
||||
if let Some(ty) = &efn.ret {
|
||||
if is_unsized(ty, types) {
|
||||
errors.push(return_by_value(ty, types));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for api in apis {
|
||||
if let Api::CxxFunction(efn) = api {
|
||||
errors.extend(check_mut_return_restriction(efn).err());
|
||||
}
|
||||
if let Api::CxxFunction(efn) | Api::RustFunction(efn) = api {
|
||||
errors.extend(check_multiple_arg_lifetimes(efn).err());
|
||||
}
|
||||
}
|
||||
|
||||
ident::check_all(apis, &mut errors);
|
||||
|
||||
let mut iter = errors.into_iter();
|
||||
let mut all_errors = match iter.next() {
|
||||
Some(err) => err,
|
||||
None => return Ok(()),
|
||||
};
|
||||
for err in iter {
|
||||
all_errors.combine(err);
|
||||
}
|
||||
Err(all_errors)
|
||||
}
|
||||
|
||||
fn is_unsized(ty: &Type, types: &Types) -> bool {
|
||||
let ident = match ty {
|
||||
Type::Ident(ident) => ident,
|
||||
_ => return false,
|
||||
};
|
||||
ident == "CxxString" || types.cxx.contains(ident) || types.rust.contains(ident)
|
||||
}
|
||||
|
||||
fn check_mut_return_restriction(efn: &ExternFn) -> Result<()> {
|
||||
match &efn.ret {
|
||||
Some(Type::Ref(ty)) if ty.mutability.is_some() => {}
|
||||
_ => return Ok(()),
|
||||
}
|
||||
|
||||
for arg in &efn.args {
|
||||
if let Type::Ref(ty) = &arg.ty {
|
||||
if ty.mutability.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::new_spanned(
|
||||
efn,
|
||||
"&mut return type is not allowed unless there is a &mut argument",
|
||||
))
|
||||
}
|
||||
|
||||
fn check_multiple_arg_lifetimes(efn: &ExternFn) -> Result<()> {
|
||||
match &efn.ret {
|
||||
Some(Type::Ref(_)) => {}
|
||||
_ => return Ok(()),
|
||||
}
|
||||
|
||||
let mut reference_args = 0;
|
||||
for arg in &efn.args {
|
||||
if let Type::Ref(_) = &arg.ty {
|
||||
reference_args += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if reference_args == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new_spanned(
|
||||
efn,
|
||||
"functions that return a reference must take exactly one input reference",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(ty: &Type, types: &Types) -> String {
|
||||
match ty {
|
||||
Type::Ident(ident) => {
|
||||
if types.structs.contains_key(ident) {
|
||||
"struct".to_owned()
|
||||
} else if types.cxx.contains(ident) {
|
||||
"C++ type".to_owned()
|
||||
} else if types.rust.contains(ident) {
|
||||
"opaque Rust type".to_owned()
|
||||
} else if Atom::from(ident) == Some(CxxString) {
|
||||
"C++ string".to_owned()
|
||||
} else {
|
||||
ident.to_string()
|
||||
}
|
||||
}
|
||||
Type::RustBox(_) => "Box".to_owned(),
|
||||
Type::UniquePtr(_) => "unique_ptr".to_owned(),
|
||||
Type::Ref(_) => "reference".to_owned(),
|
||||
Type::Str(_) => "&str".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unsupported_type(ident: &Ident) -> Error {
|
||||
Error::new(ident.span(), "unsupported type")
|
||||
}
|
||||
|
||||
fn unsupported_cxx_type_in_box(unique_ptr: &Ty1) -> Error {
|
||||
Error::new_spanned(unique_ptr, error::BOX_CXX_TYPE.msg)
|
||||
}
|
||||
|
||||
fn unsupported_box_target(unique_ptr: &Ty1) -> Error {
|
||||
Error::new_spanned(unique_ptr, "unsupported target type of Box")
|
||||
}
|
||||
|
||||
fn unsupported_rust_type_in_unique_ptr(unique_ptr: &Ty1) -> Error {
|
||||
Error::new_spanned(unique_ptr, "unique_ptr of a Rust type is not supported yet")
|
||||
}
|
||||
|
||||
fn unsupported_unique_ptr_target(unique_ptr: &Ty1) -> Error {
|
||||
Error::new_spanned(unique_ptr, "unsupported unique_ptr target type")
|
||||
}
|
||||
|
||||
fn field_by_value(field: &Var, types: &Types) -> Error {
|
||||
let desc = describe(&field.ty, types);
|
||||
let message = format!("using {} by value is not supported", desc);
|
||||
Error::new_spanned(field, message)
|
||||
}
|
||||
|
||||
fn argument_by_value(arg: &Var, types: &Types) -> Error {
|
||||
let desc = describe(&arg.ty, types);
|
||||
let message = format!("passing {} by value is not supported", desc);
|
||||
Error::new_spanned(arg, message)
|
||||
}
|
||||
|
||||
fn return_by_value(ty: &Type, types: &Types) -> Error {
|
||||
let desc = describe(ty, types);
|
||||
let message = format!("returning {} by value is not supported", desc);
|
||||
Error::new_spanned(ty, message)
|
||||
}
|
37
syntax/doc.rs
Normal file
37
syntax/doc.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::LitStr;
|
||||
|
||||
pub struct Doc {
|
||||
fragments: Vec<LitStr>,
|
||||
}
|
||||
|
||||
impl Doc {
|
||||
pub fn new() -> Self {
|
||||
Doc {
|
||||
fragments: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, lit: LitStr) {
|
||||
self.fragments.push(lit);
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
let mut doc = String::new();
|
||||
for lit in &self.fragments {
|
||||
doc += &lit.value();
|
||||
doc.push('\n');
|
||||
}
|
||||
doc
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Doc {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let fragments = &self.fragments;
|
||||
tokens.extend(quote! {
|
||||
#(#[doc = #fragments])*
|
||||
});
|
||||
}
|
||||
}
|
70
syntax/error.rs
Normal file
70
syntax/error.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Error {
|
||||
pub msg: &'static str,
|
||||
pub label: Option<&'static str>,
|
||||
pub note: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.msg.fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
pub static ERRORS: &[Error] = &[
|
||||
BOX_CXX_TYPE,
|
||||
CXXBRIDGE_RESERVED,
|
||||
CXX_STRING_BY_VALUE,
|
||||
CXX_TYPE_BY_VALUE,
|
||||
DOUBLE_UNDERSCORE,
|
||||
RUST_TYPE_BY_VALUE,
|
||||
USE_NOT_ALLOWED,
|
||||
];
|
||||
|
||||
pub static BOX_CXX_TYPE: Error = Error {
|
||||
msg: "Box of a C++ type is not supported yet",
|
||||
label: None,
|
||||
note: Some("hint: use UniquePtr<>"),
|
||||
};
|
||||
|
||||
pub static CXXBRIDGE_RESERVED: Error = Error {
|
||||
msg: "identifiers starting with cxxbridge are reserved",
|
||||
label: Some("reserved identifier"),
|
||||
note: Some("identifiers starting with cxxbridge are reserved"),
|
||||
};
|
||||
|
||||
pub static CXX_STRING_BY_VALUE: Error = Error {
|
||||
msg: "C++ string by value is not supported",
|
||||
label: None,
|
||||
note: Some("hint: wrap it in a UniquePtr<>"),
|
||||
};
|
||||
|
||||
pub static CXX_TYPE_BY_VALUE: Error = Error {
|
||||
msg: "C++ type by value is not supported",
|
||||
label: None,
|
||||
note: Some("hint: wrap it in a Box<> or UniquePtr<>"),
|
||||
};
|
||||
|
||||
pub static DOUBLE_UNDERSCORE: Error = Error {
|
||||
msg: "identifiers containing double underscore are reserved in C++",
|
||||
label: Some("reserved identifier"),
|
||||
note: Some("identifiers containing double underscore are reserved in C++"),
|
||||
};
|
||||
|
||||
pub static RUST_TYPE_BY_VALUE: Error = Error {
|
||||
msg: "opaque Rust type by value is not supported",
|
||||
label: None,
|
||||
note: Some("hint: wrap it in a Box<>"),
|
||||
};
|
||||
|
||||
pub static USE_NOT_ALLOWED: Error = Error {
|
||||
msg: "`use` items are not allowed within cxx bridge",
|
||||
label: Some("not allowed"),
|
||||
note: Some(
|
||||
"`use` items are not allowed within cxx bridge; only types defined\n\
|
||||
within your bridge, primitive types, or types exported by the cxx\n\
|
||||
crate may be used",
|
||||
),
|
||||
};
|
37
syntax/ident.rs
Normal file
37
syntax/ident.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use crate::syntax::{error, Api};
|
||||
use proc_macro2::Ident;
|
||||
use syn::{Error, Result};
|
||||
|
||||
pub(crate) fn check(ident: &Ident) -> Result<()> {
|
||||
let s = ident.to_string();
|
||||
if s.contains("__") {
|
||||
Err(Error::new(ident.span(), error::DOUBLE_UNDERSCORE.msg))
|
||||
} else if s.starts_with("cxxbridge") {
|
||||
Err(Error::new(ident.span(), error::CXXBRIDGE_RESERVED.msg))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_all(apis: &[Api], errors: &mut Vec<Error>) {
|
||||
for api in apis {
|
||||
match api {
|
||||
Api::Include(_) => {}
|
||||
Api::Struct(strct) => {
|
||||
errors.extend(check(&strct.ident).err());
|
||||
for field in &strct.fields {
|
||||
errors.extend(check(&field.ident).err());
|
||||
}
|
||||
}
|
||||
Api::CxxType(ety) | Api::RustType(ety) => {
|
||||
errors.extend(check(&ety.ident).err());
|
||||
}
|
||||
Api::CxxFunction(efn) | Api::RustFunction(efn) => {
|
||||
errors.extend(check(&efn.ident).err());
|
||||
for arg in &efn.args {
|
||||
errors.extend(check(&arg.ident).err());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
syntax/impls.rs
Normal file
31
syntax/impls.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use crate::syntax::{Ref, Ty1};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
impl Eq for Ty1 {}
|
||||
|
||||
impl PartialEq for Ty1 {
|
||||
fn eq(&self, other: &Ty1) -> bool {
|
||||
self.name == other.name && self.inner == other.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Ty1 {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
self.inner.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Ref {}
|
||||
|
||||
impl PartialEq for Ref {
|
||||
fn eq(&self, other: &Ref) -> bool {
|
||||
self.inner == other.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Ref {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.inner.hash(state);
|
||||
}
|
||||
}
|
92
syntax/mod.rs
Normal file
92
syntax/mod.rs
Normal file
@ -0,0 +1,92 @@
|
||||
// Functionality that is shared between the cxxbridge macro and the cmd.
|
||||
|
||||
pub mod atom;
|
||||
mod attrs;
|
||||
pub mod check;
|
||||
mod doc;
|
||||
pub mod error;
|
||||
pub mod ident;
|
||||
mod impls;
|
||||
mod parse;
|
||||
pub mod set;
|
||||
mod tokens;
|
||||
pub mod types;
|
||||
|
||||
use proc_macro2::Ident;
|
||||
use syn::{LitStr, Token};
|
||||
|
||||
pub use self::atom::Atom;
|
||||
pub use self::doc::Doc;
|
||||
pub use self::parse::parse_items;
|
||||
pub use self::types::Types;
|
||||
|
||||
pub enum Api {
|
||||
Include(LitStr),
|
||||
Struct(Struct),
|
||||
CxxType(ExternType),
|
||||
CxxFunction(ExternFn),
|
||||
RustType(ExternType),
|
||||
RustFunction(ExternFn),
|
||||
}
|
||||
|
||||
pub struct ExternType {
|
||||
pub doc: Doc,
|
||||
pub type_token: Token![type],
|
||||
pub ident: Ident,
|
||||
}
|
||||
|
||||
pub struct Struct {
|
||||
pub doc: Doc,
|
||||
pub derives: Vec<Ident>,
|
||||
pub struct_token: Token![struct],
|
||||
pub ident: Ident,
|
||||
pub fields: Vec<Var>,
|
||||
}
|
||||
|
||||
pub struct ExternFn {
|
||||
pub doc: Doc,
|
||||
pub fn_token: Token![fn],
|
||||
pub ident: Ident,
|
||||
pub receiver: Option<Receiver>,
|
||||
pub args: Vec<Var>,
|
||||
pub ret: Option<Type>,
|
||||
pub semi_token: Token![;],
|
||||
}
|
||||
|
||||
pub struct Var {
|
||||
pub ident: Ident,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
pub struct Receiver {
|
||||
pub mutability: Option<Token![mut]>,
|
||||
pub ident: Ident,
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
pub enum Type {
|
||||
Ident(Ident),
|
||||
RustBox(Box<Ty1>),
|
||||
UniquePtr(Box<Ty1>),
|
||||
Ref(Box<Ref>),
|
||||
Str(Box<Ref>),
|
||||
}
|
||||
|
||||
pub struct Ty1 {
|
||||
pub name: Ident,
|
||||
pub langle: Token![<],
|
||||
pub inner: Type,
|
||||
pub rangle: Token![>],
|
||||
}
|
||||
|
||||
pub struct Ref {
|
||||
pub ampersand: Token![&],
|
||||
pub mutability: Option<Token![mut]>,
|
||||
pub inner: Type,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Derive {
|
||||
Clone,
|
||||
Copy,
|
||||
}
|
265
syntax/parse.rs
Normal file
265
syntax/parse.rs
Normal file
@ -0,0 +1,265 @@
|
||||
use crate::syntax::{
|
||||
attrs, error, Api, Atom, Doc, ExternFn, ExternType, Receiver, Ref, Struct, Ty1, Type, Var,
|
||||
};
|
||||
use proc_macro2::Ident;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
Abi, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType, GenericArgument, Item,
|
||||
ItemForeignMod, ItemStruct, Pat, PathArguments, Result, ReturnType, Type as RustType,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Lang {
|
||||
Cxx,
|
||||
Rust,
|
||||
}
|
||||
|
||||
pub fn parse_items(items: Vec<Item>) -> Result<Vec<Api>> {
|
||||
let mut apis = Vec::new();
|
||||
for item in items {
|
||||
match item {
|
||||
Item::Struct(item) => {
|
||||
let strct = parse_struct(item)?;
|
||||
apis.push(strct);
|
||||
}
|
||||
Item::ForeignMod(foreign_mod) => {
|
||||
let functions = parse_foreign_mod(foreign_mod)?;
|
||||
apis.extend(functions);
|
||||
}
|
||||
Item::Use(item) => return Err(Error::new_spanned(item, error::USE_NOT_ALLOWED)),
|
||||
_ => return Err(Error::new_spanned(item, "unsupported item")),
|
||||
}
|
||||
}
|
||||
Ok(apis)
|
||||
}
|
||||
|
||||
fn parse_struct(item: ItemStruct) -> 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",
|
||||
));
|
||||
}
|
||||
|
||||
let mut doc = Doc::new();
|
||||
let mut derives = Vec::new();
|
||||
attrs::parse(&item.attrs, &mut doc, Some(&mut derives))?;
|
||||
check_reserved_name(&item.ident)?;
|
||||
match item.fields {
|
||||
Fields::Named(fields) => Ok(Api::Struct(Struct {
|
||||
doc,
|
||||
derives,
|
||||
struct_token: item.struct_token,
|
||||
ident: item.ident,
|
||||
fields: fields
|
||||
.named
|
||||
.into_iter()
|
||||
.map(|field| {
|
||||
Ok(Var {
|
||||
ident: field.ident.unwrap(),
|
||||
ty: parse_type(&field.ty)?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
})),
|
||||
Fields::Unit => Err(Error::new_spanned(item, "unit structs are not supported")),
|
||||
Fields::Unnamed(_) => Err(Error::new_spanned(item, "tuple structs are not supported")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_foreign_mod(foreign_mod: ItemForeignMod) -> Result<Vec<Api>> {
|
||||
let lang = parse_lang(foreign_mod.abi)?;
|
||||
let api_type = match lang {
|
||||
Lang::Cxx => Api::CxxType,
|
||||
Lang::Rust => Api::RustType,
|
||||
};
|
||||
let api_function = match lang {
|
||||
Lang::Cxx => Api::CxxFunction,
|
||||
Lang::Rust => Api::RustFunction,
|
||||
};
|
||||
|
||||
let mut items = Vec::new();
|
||||
for foreign in &foreign_mod.items {
|
||||
match foreign {
|
||||
ForeignItem::Type(foreign) => {
|
||||
check_reserved_name(&foreign.ident)?;
|
||||
let ety = parse_extern_type(foreign)?;
|
||||
items.push(api_type(ety));
|
||||
}
|
||||
ForeignItem::Fn(foreign) => {
|
||||
let efn = parse_extern_fn(foreign)?;
|
||||
items.push(api_function(efn));
|
||||
}
|
||||
ForeignItem::Macro(foreign) if foreign.mac.path.is_ident("include") => {
|
||||
let include = foreign.mac.parse_body()?;
|
||||
items.push(Api::Include(include));
|
||||
}
|
||||
_ => return Err(Error::new_spanned(foreign, "unsupported foreign item")),
|
||||
}
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
fn parse_lang(abi: Abi) -> Result<Lang> {
|
||||
let name = match &abi.name {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
return Err(Error::new_spanned(
|
||||
abi,
|
||||
"ABI name is required, extern \"C\" or extern \"Rust\"",
|
||||
));
|
||||
}
|
||||
};
|
||||
match name.value().as_str() {
|
||||
"C" => Ok(Lang::Cxx),
|
||||
"Rust" => Ok(Lang::Rust),
|
||||
_ => Err(Error::new_spanned(abi, "unrecognized ABI")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_extern_type(foreign_type: &ForeignItemType) -> Result<ExternType> {
|
||||
let doc = attrs::parse_doc(&foreign_type.attrs)?;
|
||||
let type_token = foreign_type.type_token;
|
||||
let ident = foreign_type.ident.clone();
|
||||
Ok(ExternType {
|
||||
doc,
|
||||
type_token,
|
||||
ident,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_extern_fn(foreign_fn: &ForeignItemFn) -> Result<ExternFn> {
|
||||
let generics = &foreign_fn.sig.generics;
|
||||
if !generics.params.is_empty() || generics.where_clause.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
foreign_fn,
|
||||
"extern function with generic parameters is not supported yet",
|
||||
));
|
||||
}
|
||||
if let Some(variadic) = &foreign_fn.sig.variadic {
|
||||
return Err(Error::new_spanned(
|
||||
variadic,
|
||||
"variadic function is not supported yet",
|
||||
));
|
||||
}
|
||||
|
||||
let mut receiver = None;
|
||||
let mut args = Vec::new();
|
||||
for arg in &foreign_fn.sig.inputs {
|
||||
match arg {
|
||||
FnArg::Receiver(receiver) => {
|
||||
return Err(Error::new_spanned(receiver, "unsupported signature"))
|
||||
}
|
||||
FnArg::Typed(arg) => {
|
||||
let ident = match arg.pat.as_ref() {
|
||||
Pat::Ident(pat) => pat.ident.clone(),
|
||||
_ => return Err(Error::new_spanned(arg, "unsupported signature")),
|
||||
};
|
||||
let ty = parse_type(&arg.ty)?;
|
||||
if ident != "self" {
|
||||
args.push(Var { ident, ty });
|
||||
continue;
|
||||
}
|
||||
if let Type::Ref(reference) = ty {
|
||||
if let Type::Ident(ident) = reference.inner {
|
||||
receiver = Some(Receiver {
|
||||
mutability: reference.mutability,
|
||||
ident,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return Err(Error::new_spanned(arg, "unsupported method receiver"));
|
||||
}
|
||||
}
|
||||
}
|
||||
let ret = match &foreign_fn.sig.output {
|
||||
ReturnType::Default => None,
|
||||
ReturnType::Type(_, ty) => Some(parse_type(ty)?),
|
||||
};
|
||||
let doc = attrs::parse_doc(&foreign_fn.attrs)?;
|
||||
let fn_token = foreign_fn.sig.fn_token;
|
||||
let ident = foreign_fn.sig.ident.clone();
|
||||
let semi_token = foreign_fn.semi_token;
|
||||
Ok(ExternFn {
|
||||
doc,
|
||||
fn_token,
|
||||
ident,
|
||||
receiver,
|
||||
args,
|
||||
ret,
|
||||
semi_token,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_type(ty: &RustType) -> Result<Type> {
|
||||
match &ty {
|
||||
RustType::Reference(ty) => {
|
||||
let inner = parse_type(&ty.elem)?;
|
||||
let which = match &inner {
|
||||
Type::Ident(ident) if ident == "str" => {
|
||||
if ty.mutability.is_some() {
|
||||
return Err(Error::new_spanned(ty, "unsupported type"));
|
||||
} else {
|
||||
Type::Str
|
||||
}
|
||||
}
|
||||
_ => Type::Ref,
|
||||
};
|
||||
return Ok(which(Box::new(Ref {
|
||||
ampersand: ty.and_token,
|
||||
mutability: ty.mutability,
|
||||
inner,
|
||||
})));
|
||||
}
|
||||
RustType::Path(ty) => {
|
||||
let path = &ty.path;
|
||||
if ty.qself.is_none() && path.leading_colon.is_none() && path.segments.len() == 1 {
|
||||
let segment = &path.segments[0];
|
||||
let ident = segment.ident.clone();
|
||||
match &segment.arguments {
|
||||
PathArguments::None => return Ok(Type::Ident(ident)),
|
||||
PathArguments::AngleBracketed(generic) => {
|
||||
if ident == "UniquePtr" && generic.args.len() == 1 {
|
||||
if let GenericArgument::Type(arg) = &generic.args[0] {
|
||||
let inner = parse_type(arg)?;
|
||||
return Ok(Type::UniquePtr(Box::new(Ty1 {
|
||||
name: ident,
|
||||
langle: generic.lt_token,
|
||||
inner,
|
||||
rangle: generic.gt_token,
|
||||
})));
|
||||
}
|
||||
} else if ident == "Box" && generic.args.len() == 1 {
|
||||
if let GenericArgument::Type(arg) = &generic.args[0] {
|
||||
let inner = parse_type(arg)?;
|
||||
return Ok(Type::RustBox(Box::new(Ty1 {
|
||||
name: ident,
|
||||
langle: generic.lt_token,
|
||||
inner,
|
||||
rangle: generic.gt_token,
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
PathArguments::Parenthesized(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Err(Error::new_spanned(ty, "unsupported type"))
|
||||
}
|
||||
|
||||
fn check_reserved_name(ident: &Ident) -> Result<()> {
|
||||
if ident == "Box" || ident == "UniquePtr" || Atom::from(ident).is_some() {
|
||||
Err(Error::new(ident.span(), "reserved name"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
47
syntax/set.rs
Normal file
47
syntax/set.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
use std::slice;
|
||||
|
||||
pub struct OrderedSet<'a, T> {
|
||||
set: HashSet<&'a T>,
|
||||
vec: Vec<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T> OrderedSet<'a, T>
|
||||
where
|
||||
T: Hash + Eq,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
OrderedSet {
|
||||
set: HashSet::new(),
|
||||
vec: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: &'a T) {
|
||||
if self.set.insert(value) {
|
||||
self.vec.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, value: &T) -> bool {
|
||||
self.set.contains(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, 'a, T> IntoIterator for &'s OrderedSet<'a, T> {
|
||||
type Item = &'a T;
|
||||
type IntoIter = Iter<'s, 'a, T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
Iter(self.vec.iter())
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
67
syntax/tokens.rs
Normal file
67
syntax/tokens.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use crate::syntax::{Derive, ExternFn, Ref, Ty1, Type, Var};
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote_spanned, ToTokens};
|
||||
use syn::Token;
|
||||
|
||||
impl ToTokens for Type {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Type::Ident(ident) => {
|
||||
if ident == "CxxString" {
|
||||
let span = ident.span();
|
||||
tokens.extend(quote_spanned!(span=> ::cxx::));
|
||||
}
|
||||
ident.to_tokens(tokens);
|
||||
}
|
||||
Type::RustBox(ty) | Type::UniquePtr(ty) => ty.to_tokens(tokens),
|
||||
Type::Ref(r) | Type::Str(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);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Ty1 {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
if self.name == "UniquePtr" {
|
||||
let span = self.name.span();
|
||||
tokens.extend(quote_spanned!(span=> ::cxx::));
|
||||
}
|
||||
self.name.to_tokens(tokens);
|
||||
self.langle.to_tokens(tokens);
|
||||
self.inner.to_tokens(tokens);
|
||||
self.rangle.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Ref {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.ampersand.to_tokens(tokens);
|
||||
self.mutability.to_tokens(tokens);
|
||||
self.inner.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Derive {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let name = match self {
|
||||
Derive::Clone => "Clone",
|
||||
Derive::Copy => "Copy",
|
||||
};
|
||||
Ident::new(name, Span::call_site()).to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ExternFn {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.fn_token.to_tokens(tokens);
|
||||
self.ident.to_tokens(tokens);
|
||||
self.semi_token.to_tokens(tokens);
|
||||
}
|
||||
}
|
121
syntax/types.rs
Normal file
121
syntax/types.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use crate::syntax::atom::Atom::{self, *};
|
||||
use crate::syntax::set::OrderedSet as Set;
|
||||
use crate::syntax::{Api, Derive, ExternType, Struct, Type};
|
||||
use proc_macro2::Ident;
|
||||
use quote::quote;
|
||||
use std::collections::BTreeMap as Map;
|
||||
use syn::{Error, Result};
|
||||
|
||||
pub struct Types<'a> {
|
||||
pub all: Set<'a, Type>,
|
||||
pub structs: Map<Ident, &'a Struct>,
|
||||
pub cxx: Set<'a, Ident>,
|
||||
pub rust: Set<'a, Ident>,
|
||||
}
|
||||
|
||||
impl<'a> Types<'a> {
|
||||
pub fn collect(apis: &'a [Api]) -> Result<Self> {
|
||||
let mut all = Set::new();
|
||||
let mut structs = Map::new();
|
||||
let mut cxx = Set::new();
|
||||
let mut rust = Set::new();
|
||||
|
||||
fn visit<'a>(all: &mut Set<'a, Type>, ty: &'a Type) {
|
||||
all.insert(ty);
|
||||
match ty {
|
||||
Type::Ident(_) | Type::Str(_) => {}
|
||||
Type::RustBox(ty) | Type::UniquePtr(ty) => visit(all, &ty.inner),
|
||||
Type::Ref(r) => visit(all, &r.inner),
|
||||
}
|
||||
}
|
||||
|
||||
for api in apis {
|
||||
match api {
|
||||
Api::Include(_) => {}
|
||||
Api::Struct(strct) => {
|
||||
let ident = &strct.ident;
|
||||
if structs.contains_key(ident) || cxx.contains(ident) || rust.contains(ident) {
|
||||
return Err(duplicate_struct(strct));
|
||||
}
|
||||
structs.insert(strct.ident.clone(), strct);
|
||||
for field in &strct.fields {
|
||||
visit(&mut all, &field.ty);
|
||||
}
|
||||
}
|
||||
Api::CxxType(ety) => {
|
||||
let ident = &ety.ident;
|
||||
if structs.contains_key(ident) || cxx.contains(ident) || rust.contains(ident) {
|
||||
return Err(duplicate_type(ety));
|
||||
}
|
||||
cxx.insert(ident);
|
||||
}
|
||||
Api::RustType(ety) => {
|
||||
let ident = &ety.ident;
|
||||
if structs.contains_key(ident) || cxx.contains(ident) || rust.contains(ident) {
|
||||
return Err(duplicate_type(ety));
|
||||
}
|
||||
rust.insert(ident);
|
||||
}
|
||||
Api::CxxFunction(efn) | Api::RustFunction(efn) => {
|
||||
for arg in &efn.args {
|
||||
visit(&mut all, &arg.ty);
|
||||
}
|
||||
if let Some(ret) = &efn.ret {
|
||||
visit(&mut all, ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Types {
|
||||
all,
|
||||
structs,
|
||||
cxx,
|
||||
rust,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn needs_indirect_abi(&self, ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Ident(ident) => {
|
||||
if let Some(strct) = self.structs.get(ident) {
|
||||
!self.is_pod(strct)
|
||||
} else {
|
||||
Atom::from(ident) == Some(RustString)
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_pod(&self, strct: &Struct) -> bool {
|
||||
for derive in &strct.derives {
|
||||
if *derive == Derive::Copy {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t, 'a> IntoIterator for &'t Types<'a> {
|
||||
type Item = &'a Type;
|
||||
type IntoIter = crate::syntax::set::Iter<'t, 'a, Type>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.all.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
fn duplicate_struct(strct: &Struct) -> Error {
|
||||
let struct_token = strct.struct_token;
|
||||
let ident = &strct.ident;
|
||||
let range = quote!(#struct_token #ident);
|
||||
Error::new_spanned(range, "duplicate type")
|
||||
}
|
||||
|
||||
fn duplicate_type(ety: &ExternType) -> Error {
|
||||
let type_token = ety.type_token;
|
||||
let ident = &ety.ident;
|
||||
let range = quote!(#type_token #ident);
|
||||
Error::new_spanned(range, "duplicate type")
|
||||
}
|
6
tests/compiletest.rs
Normal file
6
tests/compiletest.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#[rustversion::attr(not(nightly), ignore)]
|
||||
#[test]
|
||||
fn ui() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/*.rs");
|
||||
}
|
10
tests/ui/opaque_not_sized.rs
Normal file
10
tests/ui/opaque_not_sized.rs
Normal file
@ -0,0 +1,10 @@
|
||||
#[cxx::bridge]
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
type TypeR;
|
||||
}
|
||||
}
|
||||
|
||||
struct TypeR(str);
|
||||
|
||||
fn main() {}
|
12
tests/ui/opaque_not_sized.stderr
Normal file
12
tests/ui/opaque_not_sized.stderr
Normal file
@ -0,0 +1,12 @@
|
||||
error[E0277]: the size for values of type `str` cannot be known at compilation time
|
||||
--> $DIR/opaque_not_sized.rs:1:1
|
||||
|
|
||||
1 | #[cxx::bridge]
|
||||
| ^^^^^^^^^^^^^^
|
||||
| |
|
||||
| doesn't have a size known at compile-time
|
||||
| required by this bound in `ffi::_::__assert_sized`
|
||||
|
|
||||
= help: within `TypeR`, the trait `std::marker::Sized` is not implemented for `str`
|
||||
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
|
||||
= note: required because it appears within the type `TypeR`
|
Loading…
Reference in New Issue
Block a user