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 ab509f8ac..a61cfb854 100644 --- a/gen/src/namespace.rs +++ b/gen/src/namespace.rs @@ -5,8 +5,9 @@ pub fn namespace(api: &Api) -> &Namespace { match api { 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 6d7ab79fa..337aefe52 100644 --- a/gen/src/write.rs +++ b/gen/src/write.rs @@ -9,10 +9,9 @@ use syntax::map::UnorderedMap as Map; use syntax::set::UnorderedSet; use syntax::symbol::{self, Symbol}; use syntax::trivial::{self, TrivialReason}; -use syntax::Lang; use 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, }; pub fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec { @@ -36,6 +35,7 @@ pub fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec { fn write_forward_declarations(out: &mut OutFile, apis: &[Api]) { let needs_forward_declaration = |api: &&Api| match api { Api::Struct(_) | Api::CxxType(_) | Api::RustType(_) => true, + Api::TypeAlias(ety) => ety.lang == Lang::Rust, Api::Enum(enm) => !out.types.cxx.contains(&enm.name.rust), _ => false, }; @@ -55,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!(), } } @@ -129,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 fd17880c4..6fd059d1d 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -95,10 +95,16 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types) } 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)); + } + }, } } @@ -1336,6 +1342,21 @@ 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 ident = &alias.name.rust; + 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 attrs = &alias.attrs; let ident = &alias.name.rust; @@ -1361,6 +1382,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 8d1d0d3cc..319f4f30f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -501,7 +501,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::{verify_rust_type, ImplBox, ImplVec, RustType}; #[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 2376bac3d..8d2ff35be 100644 --- a/syntax/check.rs +++ b/syntax/check.rs @@ -3,7 +3,8 @@ use crate::report::Errors; use crate::visit::{self, Visit}; use crate::{ error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Future, 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}; @@ -537,6 +538,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 2399f679d..9b93cb3cd 100644 --- a/syntax/mod.rs +++ b/syntax/mod.rs @@ -202,6 +202,7 @@ pub struct TypeAlias { pub visibility: Token![pub], pub type_token: Token![type], pub name: Pair, + pub lang: Lang, pub generics: Lifetimes, #[allow(dead_code)] // only used by cxxbridge-macro, not cxx-build pub eq_token: Token![=], diff --git a/syntax/parse.rs b/syntax/parse.rs index db3f002e1..6576ccd65 100644 --- a/syntax/parse.rs +++ b/syntax/parse.rs @@ -871,12 +871,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); @@ -888,6 +882,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 538856d38..ea863271c 100644 --- a/tests/ffi/module.rs +++ b/tests/ffi/module.rs @@ -19,6 +19,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"); @@ -71,6 +75,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 a94a52e9c..669faf0fb 100644 --- a/tests/ffi/tests.cc +++ b/tests/ffi/tests.cc @@ -76,6 +76,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 4118b5a85..b0573005f 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, *unsafe { 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; + | ^^^^^^^^^^^^