mirror of
https://gitee.com/openharmony/third_party_rust_cxx
synced 2024-11-23 07:10:29 +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