diff --git a/gen/src/write.rs b/gen/src/write.rs index 982fb3caf..caeddf29a 100644 --- a/gen/src/write.rs +++ b/gen/src/write.rs @@ -70,9 +70,9 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) { let mut methods_for_type = Map::new(); for api in apis { if let Api::CxxFunction(efn) | Api::RustFunction(efn) = api { - if let Some(receiver) = &efn.sig.receiver { + if let Some(class) = &efn.sig.class { methods_for_type - .entry(&receiver.ty.rust) + .entry(&class.rust) .or_insert_with(Vec::new) .push(efn); } @@ -762,12 +762,12 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { } } write!(out, " = "); - match &efn.receiver { + match &efn.class { None => write!(out, "{}", efn.name.to_fully_qualified()), - Some(receiver) => write!( + Some(class) => write!( out, "&{}::{}", - out.types.resolve(&receiver.ty).name.to_fully_qualified(), + out.types.resolve(class).name.to_fully_qualified(), efn.name.cxx, ), } @@ -948,13 +948,9 @@ fn write_rust_function_decl_impl( fn write_rust_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { out.set_namespace(&efn.name.namespace); - let local_name = match &efn.sig.receiver { + let local_name = match &efn.sig.class { None => efn.name.cxx.to_string(), - Some(receiver) => format!( - "{}::{}", - out.types.resolve(&receiver.ty).name.cxx, - efn.name.cxx, - ), + Some(class) => format!("{}::{}", out.types.resolve(class).name.cxx, efn.name.cxx,), }; let doc = &efn.doc; let invoke = mangle::extern_fn(efn, out.types); @@ -969,6 +965,9 @@ fn write_rust_function_shim_decl( indirect_call: bool, ) { begin_function_definition(out); + if sig.receiver.is_none() && sig.class.is_some() && out.header { + write!(out, "static "); + } write_return_type(out, &sig.ret); write!(out, "{}(", local_name); for (i, arg) in sig.args.iter().enumerate() { @@ -1003,11 +1002,11 @@ fn write_rust_function_shim_impl( invoke: &Symbol, indirect_call: bool, ) { - if out.header && sig.receiver.is_some() { + if out.header && sig.class.is_some() { // We've already defined this inside the struct. return; } - if sig.receiver.is_none() { + if sig.class.is_none() { // Member functions already documented at their declaration. for line in doc.to_string().lines() { writeln!(out, "//{}", line); diff --git a/macro/src/expand.rs b/macro/src/expand.rs index 8f5836a6b..41d6b964d 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -726,7 +726,7 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { #trampolines #dispatch }); - match &efn.receiver { + match &efn.class { None => { quote! { #doc @@ -734,12 +734,12 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { #visibility #unsafety #fn_token #ident #generics #arg_list #ret #fn_body } } - Some(receiver) => { + Some(class) => { let elided_generics; - let receiver_ident = &receiver.ty.rust; - let resolve = types.resolve(&receiver.ty); - let receiver_generics = if receiver.ty.generics.lt_token.is_some() { - &receiver.ty.generics + let class_ident = &class.rust; + let resolve = types.resolve(class); + let class_generics = if class.generics.lt_token.is_some() { + &class.generics } else { elided_generics = Lifetimes { lt_token: resolve.generics.lt_token, @@ -758,7 +758,7 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { &elided_generics }; quote_spanned! {ident.span()=> - impl #generics #receiver_ident #receiver_generics { + impl #generics #class_ident #class_generics { #doc #attrs #visibility #unsafety #fn_token #ident #arg_list #ret #fn_body @@ -1176,10 +1176,10 @@ fn expand_rust_function_shim_super( let vars = receiver_var.iter().chain(arg_vars); let span = invoke.span(); - let call = match &sig.receiver { + let call = match &sig.class { None => quote_spanned!(span=> super::#invoke), - Some(receiver) => { - let receiver_type = &receiver.ty.rust; + Some(class) => { + let receiver_type = &class.rust; quote_spanned!(span=> #receiver_type::#invoke) } }; diff --git a/syntax/file.rs b/syntax/file.rs index 71f11eec8..912da1b3e 100644 --- a/syntax/file.rs +++ b/syntax/file.rs @@ -1,10 +1,13 @@ use crate::syntax::cfg::CfgExpr; use crate::syntax::namespace::Namespace; +use proc_macro2::TokenStream; use quote::quote; use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::spanned::Spanned; use syn::{ - braced, token, Abi, Attribute, ForeignItem, Ident, Item as RustItem, ItemEnum, ItemImpl, - ItemStruct, ItemUse, LitStr, Token, Visibility, + braced, token, Abi, Attribute, ForeignItem as RustForeignItem, ForeignItemFn, ForeignItemMacro, + ForeignItemType, Ident, Item as RustItem, ItemEnum, ItemImpl, ItemStruct, ItemUse, LitStr, + Token, TypePath, Visibility, }; pub struct Module { @@ -36,6 +39,28 @@ pub struct ItemForeignMod { pub items: Vec, } +pub enum ForeignItem { + Type(ForeignItemType), + Fn(ForeignItemFn), + Macro(ForeignItemMacro), + Verbatim(TokenStream), + Impl(ForeignItemImpl), + Other(RustForeignItem), +} + +pub struct ForeignItemImpl { + pub attrs: Vec, + pub unsafety: Option, + pub impl_token: Token![impl], + pub self_ty: TypePath, + pub brace_token: token::Brace, + pub items: Vec, +} + +pub enum ForeignImplItem { + Fn(ForeignItemFn), +} + impl Parse for Module { fn parse(input: ParseStream) -> Result { let cfg = CfgExpr::Unconditional; @@ -83,14 +108,16 @@ impl Parse for Item { let attrs = input.call(Attribute::parse_outer)?; let ahead = input.fork(); - let unsafety = if ahead.parse::>()?.is_some() - && ahead.parse::>()?.is_some() + ahead.parse::>()?; + if ahead.parse::>()?.is_some() && ahead.parse::>().is_ok() && ahead.peek(token::Brace) { - Some(input.parse()?) - } else { - None + let unsafety = input.parse()?; + let mut foreign_mod = ItemForeignMod::parse(input)?; + foreign_mod.attrs.splice(..0, attrs); + foreign_mod.unsafety = unsafety; + return Ok(Item::ForeignMod(foreign_mod)); }; let item = input.parse()?; @@ -103,16 +130,10 @@ impl Parse for Item { item.attrs.splice(..0, attrs); Ok(Item::Enum(item)) } - RustItem::ForeignMod(mut item) => { - item.attrs.splice(..0, attrs); - Ok(Item::ForeignMod(ItemForeignMod { - attrs: item.attrs, - unsafety, - abi: item.abi, - brace_token: item.brace_token, - items: item.items, - })) - } + RustItem::ForeignMod(item) => Err(syn::parse::Error::new( + item.span(), + "Reached generic ForeignMod code instead of custom ItemForeignMod, this is a bug", + )), RustItem::Impl(mut item) => { item.attrs.splice(..0, attrs); Ok(Item::Impl(item)) @@ -125,3 +146,70 @@ impl Parse for Item { } } } + +impl Parse for ItemForeignMod { + fn parse(input: ParseStream) -> Result { + let mut attrs = input.call(Attribute::parse_outer)?; + let abi: Abi = input.parse()?; + + let content; + let brace_token = braced!(content in input); + attrs.extend(content.call(Attribute::parse_inner)?); + let mut items = Vec::new(); + while !content.is_empty() { + items.push(content.parse()?); + } + Ok(ItemForeignMod { + attrs, + unsafety: None, + abi, + brace_token, + items, + }) + } +} + +impl Parse for ForeignItem { + fn parse(input: ParseStream) -> Result { + if input.peek(Token![impl]) { + return Ok(ForeignItem::Impl(ForeignItemImpl::parse(input)?)); + } + Ok(match RustForeignItem::parse(input)? { + RustForeignItem::Type(ty) => ForeignItem::Type(ty), + RustForeignItem::Fn(f) => ForeignItem::Fn(f), + RustForeignItem::Macro(m) => ForeignItem::Macro(m), + RustForeignItem::Verbatim(t) => ForeignItem::Verbatim(t), + i => ForeignItem::Other(i), + }) + } +} + +impl Parse for ForeignItemImpl { + fn parse(input: ParseStream) -> Result { + let mut attrs = input.call(Attribute::parse_outer)?; + let unsafety: Option = input.parse()?; + let impl_token: Token![impl] = input.parse()?; + let self_ty: TypePath = input.parse()?; + let content; + let brace_token = braced!(content in input); + attrs.extend(content.call(Attribute::parse_inner)?); + let mut items = Vec::new(); + while !content.is_empty() { + items.push(content.parse()?); + } + Ok(ForeignItemImpl { + attrs, + unsafety, + impl_token, + self_ty, + brace_token, + items, + }) + } +} + +impl Parse for ForeignImplItem { + fn parse(input: ParseStream) -> Result { + Ok(ForeignImplItem::Fn(input.parse()?)) + } +} diff --git a/syntax/impls.rs b/syntax/impls.rs index 36e1f322a..f416498d0 100644 --- a/syntax/impls.rs +++ b/syntax/impls.rs @@ -319,6 +319,7 @@ impl PartialEq for Signature { throws, paren_token: _, throws_tokens: _, + class, } = self; let Signature { asyncness: asyncness2, @@ -331,10 +332,12 @@ impl PartialEq for Signature { throws: throws2, paren_token: _, throws_tokens: _, + class: class2, } = other; asyncness.is_some() == asyncness2.is_some() && unsafety.is_some() == unsafety2.is_some() && receiver == receiver2 + && class == class2 && ret == ret2 && throws == throws2 && args.len() == args2.len() @@ -375,6 +378,7 @@ impl Hash for Signature { throws, paren_token: _, throws_tokens: _, + class, } = self; asyncness.is_some().hash(state); unsafety.is_some().hash(state); @@ -393,6 +397,7 @@ impl Hash for Signature { } ret.hash(state); throws.hash(state); + class.hash(state); } } diff --git a/syntax/mangle.rs b/syntax/mangle.rs index 287b44341..a247099aa 100644 --- a/syntax/mangle.rs +++ b/syntax/mangle.rs @@ -85,13 +85,13 @@ macro_rules! join { } pub fn extern_fn(efn: &ExternFn, types: &Types) -> Symbol { - match &efn.receiver { - Some(receiver) => { - let receiver_ident = types.resolve(&receiver.ty); + match &efn.class { + Some(class) => { + let class_ident = types.resolve(class); join!( efn.name.namespace, CXXBRIDGE, - receiver_ident.name.cxx, + class_ident.name.cxx, efn.name.rust, ) } diff --git a/syntax/mod.rs b/syntax/mod.rs index 4f19d9641..e186116b5 100644 --- a/syntax/mod.rs +++ b/syntax/mod.rs @@ -184,6 +184,7 @@ pub struct Signature { pub fn_token: Token![fn], pub generics: Generics, pub receiver: Option, + pub class: Option, pub args: Punctuated, pub ret: Option, pub throws: bool, @@ -299,7 +300,7 @@ pub struct Pair { // Wrapper for a type which needs to be resolved before it can be printed in // C++. -#[derive(PartialEq, Eq, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct NamedType { pub rust: Ident, pub generics: Lifetimes, diff --git a/syntax/parse.rs b/syntax/parse.rs index 1754c6006..06d70e595 100644 --- a/syntax/parse.rs +++ b/syntax/parse.rs @@ -1,7 +1,7 @@ use crate::syntax::attrs::OtherAttrs; use crate::syntax::cfg::CfgExpr; use crate::syntax::discriminant::DiscriminantSet; -use crate::syntax::file::{Item, ItemForeignMod}; +use crate::syntax::file::{ForeignImplItem, ForeignItem, ForeignItemImpl, Item, ItemForeignMod}; use crate::syntax::report::Errors; use crate::syntax::Atom::*; use crate::syntax::{ @@ -15,11 +15,11 @@ use std::mem; use syn::parse::{ParseStream, Parser}; use syn::punctuated::Punctuated; use syn::{ - Abi, Attribute, Error, Expr, Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemType, - GenericArgument, GenericParam, Generics, Ident, ItemEnum, ItemImpl, ItemStruct, Lit, LitStr, - Pat, PathArguments, Result, ReturnType, Signature as RustSignature, Token, TraitBound, - TraitBoundModifier, Type as RustType, TypeArray, TypeBareFn, TypeParamBound, TypePath, TypePtr, - TypeReference, Variant as RustVariant, Visibility, + Abi, Attribute, Error, Expr, Fields, FnArg, ForeignItemFn, ForeignItemType, GenericArgument, + GenericParam, Generics, Ident, ItemEnum, ItemImpl, ItemStruct, Lit, LitStr, Pat, PathArguments, + Result, ReturnType, Signature as RustSignature, Token, TraitBound, TraitBoundModifier, + Type as RustType, TypeArray, TypeBareFn, TypeParamBound, TypePath, TypePtr, TypeReference, + Variant as RustVariant, Visibility, }; pub mod kw { @@ -378,7 +378,7 @@ fn parse_foreign_mod( items.push(ety); } ForeignItem::Fn(foreign) => { - match parse_extern_fn(cx, foreign, lang, trusted, &cfg, &namespace, &attrs) { + match parse_extern_fn(cx, foreign, lang, trusted, &cfg, &namespace, &attrs, None) { Ok(efn) => items.push(efn), Err(err) => cx.push(err), } @@ -392,13 +392,20 @@ fn parse_foreign_mod( Err(err) => cx.push(err), } } + ForeignItem::Macro(foreign) => cx.error(foreign, "macro invocation is not supported"), ForeignItem::Verbatim(tokens) => { match parse_extern_verbatim(cx, tokens, lang, trusted, &cfg, &namespace, &attrs) { Ok(api) => items.push(api), Err(err) => cx.push(err), } } - _ => cx.error(foreign, "unsupported foreign item"), + ForeignItem::Impl(foreign) => { + match parse_extern_impl(cx, foreign, lang, trusted, &cfg, &namespace, &attrs) { + Ok(apis) => items.extend(apis), + Err(err) => cx.push(err), + } + } + ForeignItem::Other(foreign) => cx.error(foreign, "unsupported foreign item"), } } @@ -428,6 +435,9 @@ fn parse_foreign_mod( receiver.ty.rust = single_type.rust.clone(); } } + if efn.class.is_none() && efn.receiver.is_some() { + efn.class = Some(efn.receiver.as_ref().unwrap().ty.clone()); + } } } } @@ -526,6 +536,7 @@ fn parse_extern_fn( extern_block_cfg: &CfgExpr, namespace: &Namespace, attrs: &OtherAttrs, + mut class: Option, ) -> Result { let mut cfg = extern_block_cfg.clone(); let mut doc = Doc::new(); @@ -645,6 +656,7 @@ fn parse_extern_fn( } if let Type::Ref(reference) = ty { if let Type::Ident(ident) = reference.inner { + class = Some(ident.clone()); receiver = Some(Receiver { pinned: reference.pinned, ampersand: reference.ampersand, @@ -699,12 +711,77 @@ fn parse_extern_fn( throws, paren_token, throws_tokens, + class, }, semi_token, trusted, })) } +fn parse_extern_impl( + cx: &mut Errors, + foreign_impl: ForeignItemImpl, + lang: Lang, + trusted: bool, + extern_block_cfg: &CfgExpr, + namespace: &Namespace, + attrs: &OtherAttrs, +) -> Result> { + if foreign_impl.self_ty.qself.is_some() { + cx.error( + foreign_impl.self_ty, + "explicit self types not supported in foreign impl blocks", + ); + return Ok(Vec::new()); + } + let self_ty_ident = if let Some(ident) = foreign_impl.self_ty.path.get_ident() { + ident + } else { + cx.error( + foreign_impl.self_ty, + "foreign impl blocks must be over a type convertible to an identifier", + ); + return Ok(Vec::new()); + }; + let mut apis = Vec::new(); + let self_ty = NamedType { + rust: self_ty_ident.clone(), + generics: Default::default(), + }; + for ForeignImplItem::Fn(f) in foreign_impl.items { + let mut api = parse_extern_fn( + cx, + f, + lang, + trusted, + extern_block_cfg, + namespace, + attrs, + Some(self_ty.clone()), + )?; + let f = match api { + Api::CxxFunction(ref mut f) | Api::RustFunction(ref mut f) => f, + _ => panic!("parse_extern_fn yielded non-method API"), + }; + if let Some(ref unsafety) = foreign_impl.unsafety { + // If the impl is marked unsafe, mark the api unsafe + f.unsafety = Some(*unsafety); + } + + if let Some(ref mut receiver) = f.receiver { + if receiver.ty.rust == "Self" { + receiver.ty.rust.clone_from(self_ty_ident); + } + if &receiver.ty.rust != self_ty_ident { + cx.error(&receiver.ty, "functions in foreign impl blocks must have their receiver type equal to the impl block"); + continue; + } + } + apis.push(api); + } + Ok(apis) +} + fn parse_extern_verbatim( cx: &mut Errors, tokens: TokenStream, @@ -1421,6 +1498,7 @@ fn parse_type_fn(ty: &TypeBareFn) -> Result { let generics = Generics::default(); let receiver = None; let paren_token = ty.paren_token; + let class = None; Ok(Type::Fn(Box::new(Signature { asyncness, @@ -1433,6 +1511,7 @@ fn parse_type_fn(ty: &TypeBareFn) -> Result { throws, paren_token, throws_tokens, + class, }))) } diff --git a/syntax/tokens.rs b/syntax/tokens.rs index 62d37c23d..4519556d3 100644 --- a/syntax/tokens.rs +++ b/syntax/tokens.rs @@ -258,6 +258,7 @@ impl ToTokens for Signature { throws: _, paren_token, throws_tokens, + class: _, } = self; fn_token.to_tokens(tokens); paren_token.surround(tokens, |tokens| { diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs index c3174bbca..0255f9caa 100644 --- a/tests/ffi/lib.rs +++ b/tests/ffi/lib.rs @@ -89,6 +89,16 @@ pub mod ffi { s: &'a str, } + unsafe extern "C++" { + include!("tests/ffi/tests.h"); + type L; + impl L { + fn build() -> UniquePtr; + fn impl_method(&self); + fn static_method(x: u32) -> u32; + } + } + unsafe extern "C++" { include!("tests/ffi/tests.h"); @@ -311,6 +321,11 @@ pub mod ffi { #[cxx_name = "rAliasedFunction"] fn r_aliased_function(x: i32) -> String; + + impl R { + fn get2(&self) -> usize; + fn assoc() -> usize; + } } struct Dag0 { @@ -402,6 +417,14 @@ impl R { self.0 = n; n } + + fn get2(&self) -> usize { + self.0 + } + + fn assoc() -> usize { + 42 + } } pub struct Reference<'a>(&'a String); diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc index 4a94f4a7f..2508b766c 100644 --- a/tests/ffi/tests.cc +++ b/tests/ffi/tests.cc @@ -707,6 +707,19 @@ void E::c_take_opaque_mut_ref_method() { } } +void L::impl_method() const { + cxx_test_suite_set_correct(); +} + +uint32_t L::static_method(uint32_t arg) { + cxx_test_suite_set_correct(); + return arg + 1; +} + +std::unique_ptr L::build() { + return std::unique_ptr(new L()); +} + void c_take_opaque_ns_ref(const ::F::F &f) { if (f.f == 40 && f.f_str == "hello") { cxx_test_suite_set_correct(); @@ -894,6 +907,10 @@ extern "C" const char *cxx_run_test() noexcept { (void)rust::Vec(); (void)rust::Vec(); + // Test impl-based methods and functions + ASSERT(R::assoc() == 42); + ASSERT(r->get() == r->get2()); + cxx_test_suite_set_correct(); return nullptr; } diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h index 10cb37ae5..e98ae498d 100644 --- a/tests/ffi/tests.h +++ b/tests/ffi/tests.h @@ -72,6 +72,12 @@ struct E { void c_take_opaque_mut_ref_method(); }; +struct L { + void impl_method() const; + static uint32_t static_method(uint32_t arg); + static std::unique_ptr build(); +}; + enum COwnedEnum { CVAL1, CVAL2, diff --git a/tests/test.rs b/tests/test.rs index 3c6ffa5e5..7be8cb6f3 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -376,3 +376,14 @@ fn test_raw_ptr() { assert_eq!(2025, unsafe { ffi::c_take_const_ptr(c3) }); assert_eq!(2025, unsafe { ffi::c_take_mut_ptr(c3 as *mut ffi::C) }); // deletes c3 } + +#[test] +fn test_impl_methods() { + let l = ffi::L::build(); + check!(l.impl_method()); +} + +#[test] +fn test_static_methods() { + check!(ffi::L::static_method(7)); +}