Skip to content

Commit

Permalink
Merge pull request #190 from dtolnay/id
Browse files Browse the repository at this point in the history
Enable sharing a consistent Rust type across multiple FFI blocks
  • Loading branch information
dtolnay authored May 8, 2020
2 parents 6e80833 + ae4deda commit 2429c6b
Show file tree
Hide file tree
Showing 28 changed files with 418 additions and 52 deletions.
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

0 comments on commit 2429c6b

Please sign in to comment.