Skip to content

Support type aliases across bridges using different C++ namespaces #298

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

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 6 additions & 4 deletions gen/src/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use self::error::{format_err, Result};
use self::file::File;
use crate::syntax::report::Errors;
use crate::syntax::{self, check, Types};
use std::mem;
use std::path::Path;

/// Options for C++ code generation.
Expand Down Expand Up @@ -104,14 +105,15 @@ fn generate_from_string(source: &str, opt: &Opt) -> Result<GeneratedCode> {
pub(super) fn generate(syntax: File, opt: &Opt) -> Result<GeneratedCode> {
proc_macro2::fallback::force();
let ref mut errors = Errors::new();
let bridge = syntax
let mut bridge = syntax
.modules
.into_iter()
.next()
.ok_or(Error::NoBridgeMod)?;
let ref namespace = bridge.namespace;
let content = mem::take(&mut bridge.content);
let trusted = bridge.unsafety.is_some();
let ref apis = syntax::parse_items(errors, bridge.content, trusted);
let ref apis = syntax::parse_items(errors, content, trusted);
let ref types = Types::collect(errors, apis);
errors.propagate()?;
check::typecheck(errors, namespace, apis, types);
Expand All @@ -121,12 +123,12 @@ pub(super) fn generate(syntax: File, opt: &Opt) -> Result<GeneratedCode> {
// only need to generate one or the other.
Ok(GeneratedCode {
header: if opt.gen_header {
write::gen(namespace, apis, types, opt, true).content()
write::gen(&bridge, apis, types, opt, true).content()
} else {
Vec::new()
},
implementation: if opt.gen_implementation {
write::gen(namespace, apis, types, opt, false).content()
write::gen(&bridge, apis, types, opt, false).content()
} else {
Vec::new()
},
Expand Down
16 changes: 8 additions & 8 deletions gen/src/write.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::gen::out::OutFile;
use crate::gen::{include, Opt};
use crate::syntax::atom::Atom::{self, *};
use crate::syntax::file::Module;
use crate::syntax::namespace::Namespace;
use crate::syntax::symbol::Symbol;
use crate::syntax::{
Expand All @@ -10,12 +11,13 @@ use proc_macro2::Ident;
use std::collections::HashMap;

pub(super) fn gen(
namespace: &Namespace,
bridge: &Module,
apis: &[Api],
types: &Types,
opt: &Opt,
header: bool,
) -> OutFile {
let namespace = &bridge.namespace;
let mut out_file = OutFile::new(namespace.clone(), header);
let out = &mut out_file;

Expand Down Expand Up @@ -44,7 +46,7 @@ pub(super) fn gen(
Api::Struct(strct) => write_struct_decl(out, &strct.ident),
Api::CxxType(ety) => write_struct_using(out, &ety.ident),
Api::RustType(ety) => write_struct_decl(out, &ety.ident),
Api::TypeAlias(alias) => write_alias(out, alias),
Api::TypeAlias(alias) => write_alias(out, bridge, alias),
_ => {}
}
}
Expand Down Expand Up @@ -934,12 +936,10 @@ fn write_type(out: &mut OutFile, ty: &Type) {
}
}

fn write_alias(out: &mut OutFile, alias: &TypeAlias) {
if let Some(namespace) = &alias.namespace {
// Review TODO: Is this unwrap fine? i.e. is it ok to assume that, if
// the TypePath parsed, that it has at least one segment?
let remote_type = &alias.ty.path.segments.last().unwrap().ident;
let path = namespace.path_for_type(remote_type);
fn write_alias(out: &mut OutFile, bridge: &Module, alias: &TypeAlias) {
let namespace = bridge.namespace_for_alias(alias);
if namespace != &bridge.namespace {
let path = namespace.path_for_type(&alias.ty_ident);
writeln!(out, "using {} = {};", alias.ident, path)
}
}
Expand Down
11 changes: 4 additions & 7 deletions macro/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn expand(ffi: Module, apis: &[Api], types: &Types) -> TokenStream {
}
Api::TypeAlias(alias) => {
expanded.extend(expand_type_alias(alias));
hidden.extend(expand_type_alias_verify(namespace, alias));
hidden.extend(expand_type_alias_verify(&ffi, alias));
}
}
}
Expand Down Expand Up @@ -663,12 +663,9 @@ fn expand_type_alias(alias: &TypeAlias) -> TokenStream {
}
}

fn expand_type_alias_verify(namespace: &Namespace, alias: &TypeAlias) -> TokenStream {
let namespace = alias.namespace.as_ref().unwrap_or(namespace);
// Review TODO: Is this unwrap fine? i.e. is it ok to assume that, if the
// TypePath parsed, that it has at least one segment?
let remote_type = &alias.ty.path.segments.last().unwrap().ident;
let type_id = type_id(namespace, remote_type);
fn expand_type_alias_verify(ffi: &Module, alias: &TypeAlias) -> TokenStream {
let namespace = ffi.namespace_for_alias(alias);
let type_id = type_id(namespace, &alias.ty_ident);
let ident = &alias.ident;
let begin_span = alias.type_token.span;
let end_span = alias.semi_token.span;
Expand Down
34 changes: 26 additions & 8 deletions syntax/attrs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::syntax::qualified::QualifiedName;
use crate::syntax::report::Errors;
use crate::syntax::Atom::{self, *};
use crate::syntax::{Derive, Doc, Namespace};
use proc_macro2::Ident;
use std::collections::HashMap;
use syn::parse::{ParseStream, Parser as _};
use syn::{Attribute, Error, LitStr, Path, Result, Token};

Expand All @@ -10,7 +12,7 @@ pub struct Parser<'a> {
pub doc: Option<&'a mut Doc>,
pub derives: Option<&'a mut Vec<Derive>>,
pub repr: Option<&'a mut Option<Atom>>,
pub namespace: Option<&'a mut Option<Namespace>>,
pub alias_namespaces: Option<&'a mut HashMap<QualifiedName, Namespace>>,
pub ignore_unsupported: bool,
}

Expand Down Expand Up @@ -59,11 +61,20 @@ pub(super) fn parse(cx: &mut Errors, attrs: &[Attribute], mut parser: Parser) {
}
Err(err) => return cx.push(err),
}
} else if attr.path.is_ident("namespace") {
match parse_namespace_attribute.parse2(attr.tokens.clone()) {
Ok(namespace) => {
if let Some(ns) = &mut parser.namespace {
**ns = Some(Namespace::from(namespace));
} else if is_cxx_alias_namespace_attr(attr) {
match attr.parse_args_with(parse_namespace_attribute) {
Ok((name, namespace)) => {
if let Some(map) = &mut parser.alias_namespaces {
if let Some(existing) = map.get(&name) {
return cx.error(
attr,
format!(
"conflicting cxx::alias_namespace attributes for {}: {}, {}",
name, existing, namespace
),
);
}
map.insert(name, namespace);
continue;
}
}
Expand All @@ -76,6 +87,11 @@ pub(super) fn parse(cx: &mut Errors, attrs: &[Attribute], mut parser: Parser) {
}
}

fn is_cxx_alias_namespace_attr(attr: &Attribute) -> bool {
let path = &attr.path.segments;
path.len() == 2 && path[0].ident == "cxx" && path[1].ident == "alias_namespace"
}

fn parse_doc_attribute(input: ParseStream) -> Result<LitStr> {
input.parse::<Token![=]>()?;
let lit: LitStr = input.parse()?;
Expand Down Expand Up @@ -114,7 +130,9 @@ fn parse_repr_attribute(input: ParseStream) -> Result<Atom> {
))
}

fn parse_namespace_attribute(input: ParseStream) -> Result<Namespace> {
fn parse_namespace_attribute(input: ParseStream) -> Result<(QualifiedName, Namespace)> {
let name = QualifiedName::parse_quoted_or_unquoted(input)?;
input.parse::<Token![=]>()?;
input.parse::<Namespace>()
let namespace = input.parse::<Namespace>()?;
Ok((name, namespace))
}
17 changes: 13 additions & 4 deletions syntax/file.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::syntax::namespace::Namespace;
use crate::syntax::qualified::QualifiedName;
use crate::syntax::report::Errors;
use crate::syntax::{attrs, Doc};
use crate::syntax::{attrs, Doc, TypeAlias};
use quote::quote;
use std::collections::HashMap;
use syn::parse::{Error, Parse, ParseStream, Result};
use syn::{
braced, token, Abi, Attribute, ForeignItem, Ident, Item as RustItem, ItemEnum, ItemStruct,
Expand All @@ -10,6 +12,7 @@ use syn::{

pub struct Module {
pub namespace: Namespace,
pub alias_namespaces: HashMap<QualifiedName, Namespace>,
pub doc: Doc,
pub vis: Visibility,
pub unsafety: Option<Token![unsafe]>,
Expand Down Expand Up @@ -38,19 +41,24 @@ pub struct ItemForeignMod {
impl Module {
pub fn add_attributes(&mut self, attrs: &[Attribute]) -> Result<()> {
let ref mut errors = Errors::new();
let mut doc = Doc::new();
attrs::parse(
errors,
attrs,
attrs::Parser {
doc: Some(&mut doc),
doc: Some(&mut self.doc),
alias_namespaces: Some(&mut self.alias_namespaces),
ignore_unsupported: true,
..attrs::Parser::default()
},
);
self.doc.extend(doc);
errors.propagate()
}

pub fn namespace_for_alias(&self, alias: &TypeAlias) -> &Namespace {
self.alias_namespaces
.get(&alias.ty_path)
.unwrap_or(&self.namespace)
}
}

impl Parse for Module {
Expand Down Expand Up @@ -82,6 +90,7 @@ impl Parse for Module {

let mut module = Module {
namespace,
alias_namespaces: HashMap::new(),
doc: Doc::new(),
vis,
unsafety,
Expand Down
4 changes: 3 additions & 1 deletion syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub use self::derive::Derive;
pub use self::doc::Doc;
pub use self::namespace::Namespace;
pub use self::parse::parse_items;
pub use self::qualified::QualifiedName;
pub use self::types::Types;

pub enum Api {
Expand Down Expand Up @@ -81,11 +82,12 @@ pub struct ExternFn {

pub struct TypeAlias {
pub doc: Doc,
pub namespace: Option<Namespace>,
pub type_token: Token![type],
pub ident: Ident,
pub eq_token: Token![=],
pub ty: TypePath,
pub ty_path: QualifiedName,
pub ty_ident: Ident,
pub semi_token: Token![;],
}

Expand Down
2 changes: 1 addition & 1 deletion syntax/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod kw {
syn::custom_keyword!(namespace);
}

#[derive(Clone)]
#[derive(Clone, PartialEq, Eq)]
pub struct Namespace {
segments: Vec<Ident>,
}
Expand Down
27 changes: 14 additions & 13 deletions syntax/parse.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::syntax::discriminant::DiscriminantSet;
use crate::syntax::file::{Item, ItemForeignMod};
use crate::syntax::qualified::QualifiedName;
use crate::syntax::report::Errors;
use crate::syntax::Atom::*;
use crate::syntax::{
Expand All @@ -10,6 +11,7 @@ use proc_macro2::{TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use syn::parse::{ParseStream, Parser};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
Abi, Attribute, Error, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType,
GenericArgument, Ident, ItemEnum, ItemStruct, LitStr, Pat, PathArguments, Result, ReturnType,
Expand Down Expand Up @@ -385,6 +387,7 @@ fn parse_extern_verbatim(cx: &mut Errors, tokens: &TokenStream, lang: Lang) -> R
// type Alias = crate::path::to::Type;
let parse = |input: ParseStream| -> Result<TypeAlias> {
let attrs = input.call(Attribute::parse_outer)?;
let doc = attrs::parse_doc(cx, &attrs);
let type_token: Token![type] = match input.parse()? {
Some(type_token) => type_token,
None => {
Expand All @@ -395,27 +398,25 @@ fn parse_extern_verbatim(cx: &mut Errors, tokens: &TokenStream, lang: Lang) -> R
let ident: Ident = input.parse()?;
let eq_token: Token![=] = input.parse()?;
let ty: TypePath = input.parse()?;
let semi_token: Token![;] = input.parse()?;

let mut doc = Doc::new();
let mut namespace = None;
attrs::parse(
cx,
&attrs,
attrs::Parser {
doc: Some(&mut doc),
namespace: Some(&mut namespace),
..Default::default()
},
);
let segments = &ty.path.segments;
if segments.len() <= 1 {
return Err(Error::new(ty.span(), "invalid type alias"));
}
let iter = segments.iter().take(segments.len() - 1).cloned();
let ty_path = QualifiedName::from_path_segments(iter, ty.span())?;
let ty_ident = segments.last().unwrap().ident.clone();

let semi_token: Token![;] = input.parse()?;

Ok(TypeAlias {
doc,
namespace,
type_token,
ident,
eq_token,
ty,
ty_path,
ty_ident,
semi_token,
})
};
Expand Down
27 changes: 26 additions & 1 deletion syntax/qualified.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use proc_macro2::Span;
use std::fmt::{self, Display};
use syn::ext::IdentExt;
use syn::parse::{ParseStream, Result};
use syn::{Ident, LitStr, Token};
use syn::{Error, Ident, LitStr, PathArguments, PathSegment, Token};

#[derive(Hash, PartialEq, Eq)]
pub struct QualifiedName {
pub segments: Vec<Ident>,
}
Expand Down Expand Up @@ -32,4 +35,26 @@ impl QualifiedName {
Self::parse_unquoted(input)
}
}

pub fn from_path_segments<T: IntoIterator<Item = PathSegment>>(
iter: T,
span: Span,
) -> Result<Self> {
let mut segments = Vec::new();
for segment in iter {
match segment.arguments {
PathArguments::None => {}
_ => return Err(Error::new(span, "unexpected path arguments")),
};
segments.push(segment.ident);
}
Ok(QualifiedName { segments })
}
}

impl Display for QualifiedName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let segments: Vec<String> = self.segments.iter().map(ToString::to_string).collect();
write!(f, "{}", segments.join("::"))
}
}
6 changes: 1 addition & 5 deletions tests/ffi/alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@
// https://github.com/rust-lang/rustfmt/issues/4159
#[rustfmt::skip]
#[cxx::bridge(namespace = alias_tests)]
#[cxx::alias_namespace(crate::ffi = tests)]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needs documentation but please let me know what you think first.

pub mod ffi {
extern "C" {
include!("cxx-test-suite/tests.h");

// Review TODO: Unquoted namespace here doesn't work, is that expected or a bug
// in my parsing?
#[namespace = "tests"]
type C = crate::ffi::C;

#[namespace = "tests"]
type SameC = crate::ffi::C;

fn c_return_unique_ptr() -> UniquePtr<C>;
Expand Down