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

Minimal support for Rust type aliases #1181

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
45 changes: 45 additions & 0 deletions book/src/extern-rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
```
3 changes: 2 additions & 1 deletion gen/src/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge the branch with the above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, that's not possible, since those have different inner types.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged.

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(),
}
}
}
19 changes: 15 additions & 4 deletions gen/src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -35,6 +35,7 @@ pub(super) fn gen(apis: &[Api], types: &Types, opt: &Opt, header: bool) -> Vec<u
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,
};
Expand All @@ -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!(),
}
}
Expand Down Expand Up @@ -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).
}
}
}
}
Expand Down
42 changes: 38 additions & 4 deletions macro/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TokenStream> {
Expand Down Expand Up @@ -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));
}
},
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/rust_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
pub unsafe trait RustType {}
pub unsafe trait ImplBox {}
pub unsafe trait ImplVec {}

#[doc(hidden)]
pub fn verify_rust_type<T: RustType>() {}
22 changes: 21 additions & 1 deletion syntax/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we really need to prevent this - some may really want to use different names for the same Rust type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is the C++ side. Effectively, what we want to do is to publish a forward declaration of the struct in question in the C++ code in the proper namespace (that's why also the namespace should match, otherwise the types are different on C++ side - currently, this is not checked, but probably should be).

If we'd like to support renaming the object, that would be possible, but again, that would require materializing both the forward declaration and some kind of type alias (e.g., using statement), so we would have both. The name would be accessible by both names and the original name taken anyway.

I.e., someone trying to rename the type to prevent name clashes would still end up with name clashes. Therefore, better to simply not allow renames.

The only way to do this properly would be to expose the original type in the original namespace and the alias name in a new namespace. However, due to the restriction on the Rust side, we only have access to tokens within our bridge, so there is no access to the original namespace (that's also the reason why the namespace must be re-specified).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks - I misunderstood the intention.

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) {
Expand Down
1 change: 1 addition & 0 deletions syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 1 addition & 6 deletions syntax/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -891,6 +885,7 @@ fn parse_type_alias(
visibility,
type_token,
name,
lang,
generics,
eq_token,
ty,
Expand Down
6 changes: 6 additions & 0 deletions tests/ffi/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -69,6 +73,8 @@ pub mod ffi2 {

#[namespace = "I"]
fn ns_c_return_unique_ptr_ns() -> UniquePtr<I>;

fn c_return_box_from_aliased_rust_type() -> Box<R>;
}

impl UniquePtr<D> {}
Expand Down
2 changes: 2 additions & 0 deletions tests/ffi/tests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ rust::Box<R> c_return_box() {
return rust::Box<R>::from_raw(cxx_test_suite_get_box());
}

rust::Box<R> c_return_box_from_aliased_rust_type() { return r_return_box(); }

std::unique_ptr<C> c_return_unique_ptr() {
return std::unique_ptr<C>(new C{2020});
}
Expand Down
1 change: 1 addition & 0 deletions tests/ffi/tests.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Shared c_return_shared();
::A::AShared c_return_ns_shared();
::A::B::ABShared c_return_nested_ns_shared();
rust::Box<R> c_return_box();
rust::Box<R> c_return_box_from_aliased_rust_type();
std::unique_ptr<C> c_return_unique_ptr();
std::shared_ptr<C> c_return_shared_ptr();
std::unique_ptr<::H::H> c_return_ns_unique_ptr();
Expand Down
1 change: 1 addition & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/type_alias_rust.stderr
Original file line number Diff line number Diff line change
@@ -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;
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^
15 changes: 15 additions & 0 deletions tests/ui/type_alias_rust2.rs
Original file line number Diff line number Diff line change
@@ -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() {}
11 changes: 11 additions & 0 deletions tests/ui/type_alias_rust2.stderr
Original file line number Diff line number Diff line change
@@ -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<T: RustType>() {}
| ^^^^^^^^ required by this bound in `verify_rust_type`
11 changes: 11 additions & 0 deletions tests/ui/type_alias_rust3.rs
Original file line number Diff line number Diff line change
@@ -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() {}
5 changes: 5 additions & 0 deletions tests/ui/type_alias_rust3.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
error: unsupported `extern "Rust"` alias target
--> tests/ui/type_alias_rust3.rs:7:23
|
7 | type Source = &crate::Type;
| ^^^^^^^^^^^^