Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable sharing a consistent Rust type across multiple FFI blocks #190

Merged
merged 16 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ cc = "1.0.49"
cxx-build = { version = "=0.3.0", path = "gen/build" }
cxx-test-suite = { version = "0", path = "tests/ffi" }
rustversion = "1.0"
trybuild = { version = "1.0.21", features = ["diff"] }
trybuild = { version = "1.0.27", features = ["diff"] }

[workspace]
members = ["demo-rs", "gen/build", "gen/cmd", "macro", "tests/ffi"]
Expand Down
2 changes: 1 addition & 1 deletion gen/build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ cc = "1.0.49"
codespan-reporting = "0.9"
proc-macro2 = { version = "1.0.12", features = ["span-locations"] }
quote = "1.0"
syn = { version = "1.0", features = ["full"] }
syn = { version = "1.0.19", features = ["full"] }

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
2 changes: 1 addition & 1 deletion gen/cmd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ codespan-reporting = "0.9"
proc-macro2 = { version = "1.0.12", features = ["span-locations"] }
quote = "1.0"
structopt = "0.3"
syn = { version = "1.0", features = ["full"] }
syn = { version = "1.0.19", features = ["full"] }

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
8 changes: 2 additions & 6 deletions gen/src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -971,10 +971,6 @@ fn to_mangled(namespace: &Namespace, ty: &Type) -> String {
}

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 {
Expand All @@ -991,14 +987,14 @@ fn write_generic_instantiations(out: &mut OutFile, types: &Types) {
}
} else if let Type::UniquePtr(ptr) = ty {
if let Type::Ident(inner) = &ptr.inner {
if allow_unique_ptr(inner) {
if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
out.next_section();
write_unique_ptr(out, inner, types);
}
}
} else if let Type::CxxVector(ptr) = ty {
if let Type::Ident(inner) = &ptr.inner {
if Atom::from(inner).is_none() {
if Atom::from(inner).is_none() && !types.aliases.contains_key(inner) {
out.next_section();
write_cxx_vector(out, ty, inner, types);
}
Expand Down
4 changes: 2 additions & 2 deletions macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ proc-macro = true

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", features = ["full"] }
quote = "1.0.4"
syn = { version = "1.0.19", features = ["full"] }

[dev-dependencies]
cxx = { version = "0.3", path = ".." }
Expand Down
54 changes: 49 additions & 5 deletions macro/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::symbol::Symbol;
use crate::syntax::{
self, check, mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, Types,
self, check, mangle, Api, Enum, ExternFn, ExternType, Signature, Struct, Type, TypeAlias, Types,
};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
Expand Down Expand Up @@ -46,7 +46,7 @@ fn expand(namespace: &Namespace, ffi: ItemMod, apis: &[Api], types: &Types) -> T
Api::Enum(enm) => expanded.extend(expand_enum(enm)),
Api::CxxType(ety) => {
if !types.enums.contains_key(&ety.ident) {
expanded.extend(expand_cxx_type(ety));
expanded.extend(expand_cxx_type(namespace, ety));
}
}
Api::CxxFunction(efn) => {
Expand All @@ -55,6 +55,10 @@ fn expand(namespace: &Namespace, ffi: ItemMod, apis: &[Api], types: &Types) -> T
Api::RustFunction(efn) => {
hidden.extend(expand_rust_function_shim(namespace, efn, types))
}
Api::TypeAlias(alias) => {
expanded.extend(expand_type_alias(alias));
hidden.extend(expand_type_alias_verify(namespace, alias));
}
}
}

Expand All @@ -73,13 +77,13 @@ fn expand(namespace: &Namespace, ffi: ItemMod, apis: &[Api], types: &Types) -> T
}
} else if let Type::UniquePtr(ptr) = ty {
if let Type::Ident(ident) = &ptr.inner {
if Atom::from(ident).is_none() {
if Atom::from(ident).is_none() && !types.aliases.contains_key(ident) {
expanded.extend(expand_unique_ptr(namespace, ident, types));
}
}
} else if let Type::CxxVector(ptr) = ty {
if let Type::Ident(ident) = &ptr.inner {
if Atom::from(ident).is_none() {
if Atom::from(ident).is_none() && !types.aliases.contains_key(ident) {
// Generate impl for CxxVector<T> if T is a struct or opaque
// C++ type. Impl for primitives is already provided by cxx
// crate.
Expand Down Expand Up @@ -161,15 +165,21 @@ fn expand_enum(enm: &Enum) -> TokenStream {
}
}

fn expand_cxx_type(ety: &ExternType) -> TokenStream {
fn expand_cxx_type(namespace: &Namespace, ety: &ExternType) -> TokenStream {
let ident = &ety.ident;
let doc = &ety.doc;
let type_id = type_id(namespace, ident);

quote! {
#doc
#[repr(C)]
pub struct #ident {
_private: ::cxx::private::Opaque,
}

unsafe impl ::cxx::ExternType for #ident {
type Id = #type_id;
}
}
}

Expand Down Expand Up @@ -554,6 +564,40 @@ fn expand_rust_function_shim_impl(
}
}

fn expand_type_alias(alias: &TypeAlias) -> TokenStream {
let ident = &alias.ident;
let ty = &alias.ty;
quote! {
pub type #ident = #ty;
}
}

fn expand_type_alias_verify(namespace: &Namespace, alias: &TypeAlias) -> TokenStream {
let ident = &alias.ident;
let type_id = type_id(namespace, ident);
let begin_span = alias.type_token.span;
let end_span = alias.semi_token.span;
let begin = quote_spanned!(begin_span=> ::cxx::private::verify_extern_type::<);
let end = quote_spanned!(end_span=> >);

quote! {
const _: fn() = #begin #ident, #type_id #end;
}
}

fn type_id(namespace: &Namespace, ident: &Ident) -> TokenStream {
let mut path = String::new();
for name in namespace {
path += &name.to_string();
path += "::";
}
path += &ident.to_string();

quote! {
::cxx::type_id!(#path)
}
}

fn expand_rust_box(namespace: &Namespace, ident: &Ident) -> TokenStream {
let link_prefix = format!("cxxbridge03$box${}{}$", namespace, ident);
let link_uninit = format!("{}uninit", link_prefix);
Expand Down
9 changes: 8 additions & 1 deletion macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ extern crate proc_macro;

mod expand;
mod syntax;
mod type_id;

use crate::syntax::namespace::Namespace;
use proc_macro::TokenStream;
use syn::{parse_macro_input, ItemMod};
use syn::{parse_macro_input, ItemMod, LitStr};

/// `#[cxx::bridge] mod ffi { ... }`
///
Expand Down Expand Up @@ -44,3 +45,9 @@ pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream {
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

#[proc_macro]
pub fn type_id(input: TokenStream) -> TokenStream {
let arg = parse_macro_input!(input as LitStr);
type_id::expand(arg).into()
}
29 changes: 29 additions & 0 deletions macro/src/type_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::LitStr;

// "folly::File" => `(f, o, l, l, y, (), F, i, l, e)`
pub fn expand(arg: LitStr) -> TokenStream {
let mut ids = Vec::new();

for word in arg.value().split("::") {
if !ids.is_empty() {
ids.push(quote!(()));
}
for ch in word.chars() {
ids.push(match ch {
'A'..='Z' | 'a'..='z' => {
let t = format_ident!("{}", ch);
quote!(::cxx::#t)
}
'0'..='9' | '_' => {
let t = format_ident!("_{}", ch);
quote!(::cxx::#t)
}
_ => quote!([(); #ch as _]),
});
}
}

quote! { (#(#ids,)*) }
}
110 changes: 110 additions & 0 deletions src/extern_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/// A type for which the layout is determined by its C++ definition.
///
/// This trait serves the following two related purposes.
///
/// <br>
///
/// ## Safely unifying occurrences of the same extern type
///
/// `ExternType` makes it possible for CXX to safely share a consistent Rust
/// type across multiple #\[cxx::bridge\] invocations that refer to a common
/// extern C++ type.
///
/// In the following snippet, two #\[cxx::bridge\] invocations in different
/// files (possibly different crates) both contain function signatures involving
/// the same C++ type `example::Demo`. If both were written just containing
/// `type Demo;`, then both macro expansions would produce their own separate
/// Rust type called `Demo` and thus the compiler wouldn't allow us to take the
/// `Demo` returned by `file1::ffi::create_demo` and pass it as the `Demo`
/// argument accepted by `file2::ffi::take_ref_demo`. Instead, one of the two
/// `Demo`s has been defined as an extern type alias of the other, making them
/// the same type in Rust. The CXX code generator will use an automatically
/// generated `ExternType` impl emitted in file1 to statically verify that in
/// file2 `crate::file1::ffi::Demo` really does refer to the C++ type
/// `example::Demo` as expected in file2.
///
/// ```no_run
/// // file1.rs
/// # mod file1 {
/// #[cxx::bridge(namespace = example)]
/// pub mod ffi {
/// extern "C" {
/// type Demo;
///
/// fn create_demo() -> UniquePtr<Demo>;
/// }
/// }
/// # }
///
/// // file2.rs
/// #[cxx::bridge(namespace = example)]
/// pub mod ffi {
/// extern "C" {
/// type Demo = crate::file1::ffi::Demo;
///
/// fn take_ref_demo(demo: &Demo);
/// }
/// }
/// #
/// # fn main() {}
/// ```
///
/// <br><br>
///
/// ## Integrating with bindgen-generated types
///
/// Handwritten `ExternType` impls make it possible to plug in a data structure
/// emitted by bindgen as the definition of an opaque C++ type emitted by CXX.
///
/// By writing the unsafe `ExternType` impl, the programmer asserts that the C++
/// namespace and type name given in the type id refers to a C++ type that is
/// equivalent to Rust type that is the `Self` type of the impl.
///
/// ```no_run
/// # const _: &str = stringify! {
/// mod folly_sys; // the bindgen-generated bindings
/// # };
/// # mod folly_sys {
/// # #[repr(transparent)]
/// # pub struct StringPiece([usize; 2]);
/// # }
///
/// use cxx::{type_id, ExternType};
///
/// unsafe impl ExternType for folly_sys::StringPiece {
/// type Id = type_id!("folly::StringPiece");
/// }
///
/// #[cxx::bridge(namespace = folly)]
/// pub mod ffi {
/// extern "C" {
/// include!("rust_cxx_bindings.h");
///
/// type StringPiece = crate::folly_sys::StringPiece;
///
/// fn print_string_piece(s: &StringPiece);
/// }
/// }
///
/// // Now if we construct a StringPiece or obtain one through one
/// // of the bindgen-generated signatures, we are able to pass it
/// // along to ffi::print_string_piece.
/// #
/// # fn main() {}
/// ```
pub unsafe trait ExternType {
/// A type-level representation of the type's C++ namespace and type name.
///
/// This will always be defined using `type_id!` in the following form:
///
/// ```
/// # struct TypeName;
/// # unsafe impl cxx::ExternType for TypeName {
/// type Id = cxx::type_id!("name::space::of::TypeName");
/// # }
/// ```
type Id;
}

#[doc(hidden)]
pub fn verify_extern_type<T: ExternType<Id = Id>, Id>() {}
25 changes: 24 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@

#![doc(html_root_url = "https://docs.rs/cxx/0.3.0")]
#![deny(improper_ctypes)]
#![allow(non_camel_case_types)]
#![allow(
clippy::cognitive_complexity,
clippy::declare_interior_mutable_const,
Expand All @@ -369,6 +370,7 @@ mod macros;
mod cxx_string;
mod cxx_vector;
mod exception;
mod extern_type;
mod function;
mod opaque;
mod result;
Expand All @@ -385,13 +387,18 @@ mod symbols;
pub use crate::cxx_string::CxxString;
pub use crate::cxx_vector::CxxVector;
pub use crate::exception::Exception;
pub use crate::extern_type::ExternType;
pub use crate::unique_ptr::UniquePtr;
pub use cxxbridge_macro::bridge;
pub use cxxbridge_macro::{bridge};

/// For use in impls of the `ExternType` trait. See [`ExternType`].
pub use cxxbridge_macro::type_id;

// Not public API.
#[doc(hidden)]
pub mod private {
pub use crate::cxx_vector::VectorElement;
pub use crate::extern_type::verify_extern_type;
pub use crate::function::FatFunction;
pub use crate::opaque::Opaque;
pub use crate::result::{r#try, Result};
Expand All @@ -402,3 +409,19 @@ pub mod private {
pub use crate::unique_ptr::UniquePtrTarget;
pub use crate::unwind::catch_unwind;
}

macro_rules! chars {
($($ch:ident)*) => {
$(
#[doc(hidden)]
pub enum $ch {}
)*
};
}

chars! {
_0 _1 _2 _3 _4 _5 _6 _7 _8 _9
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
__ // underscore
}
Loading