diff --git a/book/src/extern-rust.md b/book/src/extern-rust.md index 40f223759..db6888508 100644 --- a/book/src/extern-rust.md +++ b/book/src/extern-rust.md @@ -163,3 +163,48 @@ mod ffi { Bounds on a lifetime (like `<'a, 'b: 'a>`) are not currently supported. Nor are type parameters or where-clauses. + +## Type equivalence across bridges + +Similar to type aliases for C++ types, it is possible to create type aliases for +previously-exported types via the `extern "Rust"` block in another bridge. +However, current support is very limited: + +- The type name must be the same as that of the target type. +- If the target is in a different C++ namespace, then the namespace must be + explicitly specified on the alias, otherwise C++ won't consider the types as + equivalent. + +Basically, this is enough to import the type from another bridge and nothing more. + +In the first module `crate::mod1`, you export the type: + +```rust,noplayground +pub struct MyType { + ... +} + +#[cxx::bridge(namespace = "mod1")] +mod ffi { + extern "Rust" { + type MyType; + } +} +``` + +And in another crate/module `mod2`, you can now import the type and use it as +a parameter or a return value in C++ and Rust functions: + +```rust,noplayground +#[cxx::bridge(namespace = "mod2")] +mod ffi { + extern "Rust" { + #[namespace = "mod1"] + type MyType = crate::mod1::MyType; + } + + extern "C++" { + fn c_func(param: &MyType); + } +} +``` diff --git a/gen/src/namespace.rs b/gen/src/namespace.rs index b79c38f90..aa2c6267e 100644 --- a/gen/src/namespace.rs +++ b/gen/src/namespace.rs @@ -6,9 +6,10 @@ impl Api { match self { Api::CxxFunction(efn) | Api::RustFunction(efn) => &efn.name.namespace, Api::CxxType(ety) | Api::RustType(ety) => &ety.name.namespace, + Api::TypeAlias(ety) => &ety.name.namespace, Api::Enum(enm) => &enm.name.namespace, Api::Struct(strct) => &strct.name.namespace, - Api::Impl(_) | Api::Include(_) | Api::TypeAlias(_) => Default::default(), + Api::Impl(_) | Api::Include(_) => Default::default(), } } } diff --git a/gen/src/write.rs b/gen/src/write.rs index 6f535ccb9..ec2544379 100644 --- a/gen/src/write.rs +++ b/gen/src/write.rs @@ -9,8 +9,8 @@ use crate::syntax::set::UnorderedSet; use crate::syntax::symbol::{self, Symbol}; use crate::syntax::trivial::{self, TrivialReason}; use crate::syntax::{ - derive, mangle, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Pair, Signature, Struct, Trait, - Type, TypeAlias, Types, Var, + derive, mangle, Api, Doc, Enum, EnumRepr, ExternFn, ExternType, Lang, Pair, Signature, Struct, + Trait, Type, TypeAlias, Types, Var, }; use proc_macro2::Ident; @@ -35,6 +35,7 @@ pub(super) fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec true, + Api::TypeAlias(ety) => ety.lang == Lang::Rust, Api::Enum(enm) => !out.types.cxx.contains(&enm.name.rust), _ => false, }; @@ -54,6 +55,7 @@ fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) { Api::Enum(enm) => write_enum_decl(out, enm), Api::CxxType(ety) => write_struct_using(out, &ety.name), Api::RustType(ety) => write_struct_decl(out, &ety.name), + Api::TypeAlias(ety) => write_struct_decl(out, &ety.name), _ => unreachable!(), } } @@ -128,8 +130,17 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) { out.next_section(); for api in apis { if let Api::TypeAlias(ety) = api { - if let Some(reasons) = out.types.required_trivial.get(&ety.name.rust) { - check_trivial_extern_type(out, ety, reasons) + match ety.lang { + Lang::Cxx => { + if let Some(reasons) = out.types.required_trivial.get(&ety.name.rust) { + check_trivial_extern_type(out, ety, reasons) + } + } + Lang::Rust => { + // nothing to write here, the alias is only used to generate + // forward declaration in C++ (so C++ shims for Rust functions + // using the type compile correctly). + } } } } diff --git a/macro/src/expand.rs b/macro/src/expand.rs index ea5af66a4..c96a9b037 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -15,6 +15,7 @@ use crate::{derive, generics}; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use std::mem; +use syn::spanned::Spanned; use syn::{parse_quote, punctuated, Generics, Lifetime, Result, Token}; pub fn bridge(mut ffi: Module) -> Result { @@ -84,10 +85,16 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types) hidden.extend(expand_rust_type_layout(ety, types)); } Api::RustFunction(efn) => hidden.extend(expand_rust_function_shim(efn, types)), - Api::TypeAlias(alias) => { - expanded.extend(expand_type_alias(alias)); - hidden.extend(expand_type_alias_verify(alias, types)); - } + Api::TypeAlias(alias) => match alias.lang { + syntax::Lang::Cxx => { + expanded.extend(expand_type_alias(alias)); + hidden.extend(expand_type_alias_verify(alias, types)); + } + syntax::Lang::Rust => { + expanded.extend(expand_type_alias_rust(alias)); + hidden.extend(expand_type_alias_verify_rust(alias)); + } + }, } } @@ -1217,6 +1224,24 @@ fn expand_type_alias(alias: &TypeAlias) -> TokenStream { } } +fn expand_type_alias_rust(alias: &TypeAlias) -> TokenStream { + let doc = &alias.doc; + let attrs = &alias.attrs; + let visibility = alias.visibility; + let _type_token = alias.type_token; + let ident = &alias.name.rust; + let _generics = &alias.generics; + let _eq_token = alias.eq_token; + let ty = &alias.ty; + let semi_token = alias.semi_token; + + quote! { + #doc + #attrs + #visibility use #ty as #ident #semi_token + } +} + fn expand_type_alias_verify(alias: &TypeAlias, types: &Types) -> TokenStream { let ident = &alias.name.rust; let type_id = type_id(&alias.name); @@ -1239,6 +1264,15 @@ fn expand_type_alias_verify(alias: &TypeAlias, types: &Types) -> TokenStream { verify } +fn expand_type_alias_verify_rust(alias: &TypeAlias) -> TokenStream { + let mut ident = alias.name.rust.clone(); + let span = alias.ty.span(); + ident.set_span(span); + quote_spanned! {span=> + const _: fn() = ::cxx::private::verify_rust_type::< #ident >; + } +} + fn type_id(name: &Pair) -> TokenStream { let namespace_segments = name.namespace.iter(); let mut segments = Vec::with_capacity(namespace_segments.len() + 1); diff --git a/src/lib.rs b/src/lib.rs index 77ec7cff0..83fd7c2e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -506,7 +506,7 @@ pub mod private { pub use crate::rust_str::RustStr; #[cfg(feature = "alloc")] pub use crate::rust_string::RustString; - pub use crate::rust_type::{ImplBox, ImplVec, RustType}; + pub use crate::rust_type::{ImplBox, ImplVec, RustType, verify_rust_type}; #[cfg(feature = "alloc")] pub use crate::rust_vec::RustVec; pub use crate::shared_ptr::SharedPtrTarget; diff --git a/src/rust_type.rs b/src/rust_type.rs index eacb5309f..188522550 100644 --- a/src/rust_type.rs +++ b/src/rust_type.rs @@ -3,3 +3,6 @@ pub unsafe trait RustType {} pub unsafe trait ImplBox {} pub unsafe trait ImplVec {} + +#[doc(hidden)] +pub fn verify_rust_type() {} diff --git a/syntax/check.rs b/syntax/check.rs index 66883be03..211fbed6a 100644 --- a/syntax/check.rs +++ b/syntax/check.rs @@ -3,7 +3,8 @@ use crate::syntax::report::Errors; use crate::syntax::visit::{self, Visit}; use crate::syntax::{ error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, Lifetimes, - NamedType, Ptr, Receiver, Ref, Signature, SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types, + NamedType, Ptr, Receiver, Ref, RustType, Signature, SliceRef, Struct, Trait, Ty1, Type, + TypeAlias, Types, }; use proc_macro2::{Delimiter, Group, Ident, TokenStream}; use quote::{quote, ToTokens}; @@ -500,6 +501,25 @@ fn check_api_type_alias(cx: &mut Check, alias: &TypeAlias) { let msg = format!("derive({}) on extern type alias is not supported", derive); cx.error(derive, msg); } + + if alias.lang == Lang::Rust { + let ty = &alias.ty; + if let RustType::Path(path) = &ty { + // OK, we support path + if let Some(last) = path.path.segments.last() { + if last.ident != alias.name.rust { + cx.error( + &alias.name.rust, + "`extern \"Rust\"` alias must have the same name as the target type", + ); + } + } else { + cx.error(ty, "unsupported `extern \"Rust\"` alias target type"); + } + } else { + cx.error(ty, "unsupported `extern \"Rust\"` alias target"); + } + } } fn check_api_impl(cx: &mut Check, imp: &Impl) { diff --git a/syntax/mod.rs b/syntax/mod.rs index 4f19d9641..70e779aef 100644 --- a/syntax/mod.rs +++ b/syntax/mod.rs @@ -154,6 +154,7 @@ pub struct TypeAlias { pub visibility: Token![pub], pub type_token: Token![type], pub name: Pair, + pub lang: Lang, pub generics: Lifetimes, pub eq_token: Token![=], pub ty: RustType, diff --git a/syntax/parse.rs b/syntax/parse.rs index 1754c6006..7d5e5331c 100644 --- a/syntax/parse.rs +++ b/syntax/parse.rs @@ -874,12 +874,6 @@ fn parse_type_alias( }, )); - if lang == Lang::Rust { - let span = quote!(#type_token #semi_token); - let msg = "type alias in extern \"Rust\" block is not supported"; - return Err(Error::new_spanned(span, msg)); - } - let visibility = visibility_pub(&visibility, type_token.span); let name = pair(namespace, &ident, cxx_name, rust_name); @@ -891,6 +885,7 @@ fn parse_type_alias( visibility, type_token, name, + lang, generics, eq_token, ty, diff --git a/tests/ffi/module.rs b/tests/ffi/module.rs index 21a86206d..10da6fba3 100644 --- a/tests/ffi/module.rs +++ b/tests/ffi/module.rs @@ -17,6 +17,10 @@ pub mod ffi { #[cxx::bridge(namespace = "tests")] pub mod ffi2 { + extern "Rust" { + type R = crate::R; + } + unsafe extern "C++" { include!("tests/ffi/tests.h"); @@ -69,6 +73,8 @@ pub mod ffi2 { #[namespace = "I"] fn ns_c_return_unique_ptr_ns() -> UniquePtr; + + fn c_return_box_from_aliased_rust_type() -> Box; } impl UniquePtr {} diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc index 8cf74bebb..41716a67e 100644 --- a/tests/ffi/tests.cc +++ b/tests/ffi/tests.cc @@ -72,6 +72,8 @@ rust::Box c_return_box() { return rust::Box::from_raw(cxx_test_suite_get_box()); } +rust::Box c_return_box_from_aliased_rust_type() { return r_return_box(); } + std::unique_ptr c_return_unique_ptr() { return std::unique_ptr(new C{2020}); } diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h index dc02e4ff8..e9aac3976 100644 --- a/tests/ffi/tests.h +++ b/tests/ffi/tests.h @@ -91,6 +91,7 @@ Shared c_return_shared(); ::A::AShared c_return_ns_shared(); ::A::B::ABShared c_return_nested_ns_shared(); rust::Box c_return_box(); +rust::Box c_return_box_from_aliased_rust_type(); std::unique_ptr c_return_unique_ptr(); std::shared_ptr c_return_shared_ptr(); std::unique_ptr<::H::H> c_return_ns_unique_ptr(); diff --git a/tests/test.rs b/tests/test.rs index bcf0a2cd1..68ffc8e61 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -41,6 +41,7 @@ fn test_c_return() { assert_eq!(2020, ffi::c_return_primitive()); assert_eq!(2020, ffi::c_return_shared().z); assert_eq!(2020, ffi::c_return_box().0); + assert_eq!(2020, ffi2::c_return_box_from_aliased_rust_type().0); ffi::c_return_unique_ptr(); ffi2::c_return_ns_unique_ptr(); assert_eq!(2020, *ffi::c_return_ref(&shared)); diff --git a/tests/ui/type_alias_rust.stderr b/tests/ui/type_alias_rust.stderr index 8cf9a56fb..e2605abc2 100644 --- a/tests/ui/type_alias_rust.stderr +++ b/tests/ui/type_alias_rust.stderr @@ -1,5 +1,5 @@ -error: type alias in extern "Rust" block is not supported - --> tests/ui/type_alias_rust.rs:5:9 +error: `extern "Rust"` alias must have the same name as the target type + --> tests/ui/type_alias_rust.rs:5:14 | 5 | type Alias = crate::Type; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ diff --git a/tests/ui/type_alias_rust2.rs b/tests/ui/type_alias_rust2.rs new file mode 100644 index 000000000..45fccc3cf --- /dev/null +++ b/tests/ui/type_alias_rust2.rs @@ -0,0 +1,15 @@ +pub mod other_module { + pub struct Source { + member: u32, + } +} + +#[cxx::bridge] +mod ffi { + extern "Rust" { + // Not allowed - the target is not `extern "Rust"`. + type Source = crate::other_module::Source; + } +} + +fn main() {} diff --git a/tests/ui/type_alias_rust2.stderr b/tests/ui/type_alias_rust2.stderr new file mode 100644 index 000000000..7c4d4b798 --- /dev/null +++ b/tests/ui/type_alias_rust2.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `other_module::Source: RustType` is not satisfied + --> tests/ui/type_alias_rust2.rs:11:23 + | +11 | type Source = crate::other_module::Source; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `RustType` is not implemented for `other_module::Source` + | +note: required by a bound in `verify_rust_type` + --> src/rust_type.rs + | + | pub fn verify_rust_type() {} + | ^^^^^^^^ required by this bound in `verify_rust_type` diff --git a/tests/ui/type_alias_rust3.rs b/tests/ui/type_alias_rust3.rs new file mode 100644 index 000000000..9bfec5ddb --- /dev/null +++ b/tests/ui/type_alias_rust3.rs @@ -0,0 +1,11 @@ +struct Type; + +#[cxx::bridge] +mod ffi { + extern "Rust" { + // Not allowed - the target is not a path. + type Source = &crate::Type; + } +} + +fn main() {} diff --git a/tests/ui/type_alias_rust3.stderr b/tests/ui/type_alias_rust3.stderr new file mode 100644 index 000000000..991d61b03 --- /dev/null +++ b/tests/ui/type_alias_rust3.stderr @@ -0,0 +1,5 @@ +error: unsupported `extern "Rust"` alias target + --> tests/ui/type_alias_rust3.rs:7:23 + | +7 | type Source = &crate::Type; + | ^^^^^^^^^^^^