diff --git a/gen/src/write.rs b/gen/src/write.rs index 77e1da0b2..b1adf5e40 100644 --- a/gen/src/write.rs +++ b/gen/src/write.rs @@ -76,6 +76,12 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) { .or_insert_with(Vec::new) .push(efn); } + if let Some(self_type) = &efn.self_type { + methods_for_type + .entry(&out.types.resolve(self_type).name.rust) + .or_insert_with(Vec::new) + .push(efn); + } } } @@ -265,6 +271,13 @@ fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&Extern out.next_section(); + // default move constructors/assignments for trivially copyable types + // used for indirect return + if out.types.structs_with_constructors.contains(&strct.name.rust) { + writeln!(out, " {0}({0}&&) noexcept = default;", strct.name.cxx); + writeln!(out, " {0}& operator=({0}&&) noexcept = default;", strct.name.cxx); + } + for method in methods { if !method.doc.is_empty() { out.next_section(); @@ -272,9 +285,15 @@ fn write_struct<'a>(out: &mut OutFile<'a>, strct: &'a Struct, methods: &[&Extern write_doc(out, " ", &method.doc); write!(out, " "); let sig = &method.sig; - let local_name = method.name.cxx.to_string(); + let local_name = match (&method.self_type, sig.constructor) { + (Some(self_type), true) => out.types.resolve(self_type).name.cxx.to_string(), + _ => method.name.cxx.to_string(), + }; let indirect_call = false; - write_rust_function_shim_decl(out, &local_name, sig, indirect_call); + if method.self_type.is_some() && !method.sig.constructor { + write!(out, "static "); + } + write_rust_function_shim_decl(out, &local_name, sig, &None, indirect_call); writeln!(out, ";"); if !method.doc.is_empty() { out.next_section(); @@ -366,7 +385,10 @@ fn write_opaque_type<'a>(out: &mut OutFile<'a>, ety: &'a ExternType, methods: &[ let sig = &method.sig; let local_name = method.name.cxx.to_string(); let indirect_call = false; - write_rust_function_shim_decl(out, &local_name, sig, indirect_call); + if method.self_type.is_some() && !method.sig.constructor { + write!(out, "static "); + } + write_rust_function_shim_decl(out, &local_name, sig, &None, indirect_call); writeln!(out, ";"); if !method.doc.is_empty() { out.next_section(); @@ -742,44 +764,61 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { if !efn.args.is_empty() || efn.receiver.is_some() { write!(out, ", "); } - write_indirect_return_type_space(out, efn.ret.as_ref().unwrap()); + if efn.sig.constructor { + write!( + out, + "{} ", + out.types.resolve(efn.self_type.as_ref().unwrap()).name.cxx + ); + } else { + write_indirect_return_type_space(out, efn.ret.as_ref().unwrap()); + } write!(out, "*return$"); } writeln!(out, ") noexcept {{"); - write!(out, " "); - write_return_type(out, &efn.ret); - match &efn.receiver { - None => write!(out, "(*{}$)(", efn.name.rust), - Some(receiver) => write!( - out, - "({}::*{}$)(", - out.types.resolve(&receiver.ty).name.to_fully_qualified(), - efn.name.rust, - ), - } - for (i, arg) in efn.args.iter().enumerate() { - if i > 0 { - write!(out, ", "); + if !efn.sig.constructor { + write!(out, " "); + write_return_type(out, &efn.ret); + match &efn.receiver { + None => write!(out, "(*{}$)(", efn.name.rust), + Some(receiver) => write!( + out, + "({}::*{}$)(", + out.types.resolve(&receiver.ty).name.to_fully_qualified(), + efn.name.rust, + ), } - write_type(out, &arg.ty); - } - write!(out, ")"); - if let Some(receiver) = &efn.receiver { - if !receiver.mutable { - write!(out, " const"); + for (i, arg) in efn.args.iter().enumerate() { + if i > 0 { + write!(out, ", "); + } + write_type(out, &arg.ty); } + write!(out, ")"); + if let Some(receiver) = &efn.receiver { + if !receiver.mutable { + write!(out, " const"); + } + } + write!(out, " = "); + match (&efn.receiver, &efn.self_type) { + (None, None) => write!(out, "{}", efn.name.to_fully_qualified()), + (Some(receiver), None) => write!( + out, + "&{}::{}", + out.types.resolve(&receiver.ty).name.to_fully_qualified(), + efn.name.cxx, + ), + (None, Some(self_type)) => write!( + out, + "&{}::{}", + out.types.resolve(self_type).name.to_fully_qualified(), + efn.name.cxx, + ), + _ => unreachable!("receiver and self_type are mutually exclusive"), + } + writeln!(out, ";"); } - write!(out, " = "); - match &efn.receiver { - None => write!(out, "{}", efn.name.to_fully_qualified()), - Some(receiver) => write!( - out, - "&{}::{}", - out.types.resolve(&receiver.ty).name.to_fully_qualified(), - efn.name.cxx, - ), - } - writeln!(out, ";"); write!(out, " "); if efn.throws { out.builtin.ptr_len = true; @@ -792,28 +831,38 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { if indirect_return { out.include.new = true; write!(out, "new (return$) "); - write_indirect_return_type(out, efn.ret.as_ref().unwrap()); + if efn.sig.constructor { + write!( + out, + "{}", + out.types.resolve(efn.self_type.as_ref().unwrap()).name.cxx + ); + } else { + write_indirect_return_type(out, efn.ret.as_ref().unwrap()); + } write!(out, "("); } else if efn.ret.is_some() { write!(out, "return "); } - match &efn.ret { - Some(Type::Ref(_)) => write!(out, "&"), - Some(Type::Str(_)) if !indirect_return => { - out.builtin.rust_str_repr = true; - write!(out, "::rust::impl<::rust::Str>::repr("); + if !efn.sig.constructor { + match &efn.ret { + Some(Type::Ref(_)) => write!(out, "&"), + Some(Type::Str(_)) if !indirect_return => { + out.builtin.rust_str_repr = true; + write!(out, "::rust::impl<::rust::Str>::repr("); + } + Some(ty @ Type::SliceRef(_)) if !indirect_return => { + out.builtin.rust_slice_repr = true; + write!(out, "::rust::impl<"); + write_type(out, ty); + write!(out, ">::repr("); + } + _ => {} } - Some(ty @ Type::SliceRef(_)) if !indirect_return => { - out.builtin.rust_slice_repr = true; - write!(out, "::rust::impl<"); - write_type(out, ty); - write!(out, ">::repr("); + match &efn.receiver { + None => write!(out, "{}$(", efn.name.rust), + Some(_) => write!(out, "(self.*{}$)(", efn.name.rust), } - _ => {} - } - match &efn.receiver { - None => write!(out, "{}$(", efn.name.rust), - Some(_) => write!(out, "(self.*{}$)(", efn.name.rust), } for (i, arg) in efn.args.iter().enumerate() { if i > 0 { @@ -843,7 +892,9 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { write!(out, "{}", arg.name.cxx); } } - write!(out, ")"); + if !efn.sig.constructor { + write!(out, ")"); + } match &efn.ret { Some(Type::RustBox(_)) => write!(out, ".into_raw()"), Some(Type::UniquePtr(_)) => write!(out, ".release()"), @@ -873,12 +924,20 @@ fn write_cxx_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { fn write_function_pointer_trampoline(out: &mut OutFile, efn: &ExternFn, var: &Pair, f: &Signature) { let r_trampoline = mangle::r_trampoline(efn, var, out.types); let indirect_call = true; - write_rust_function_decl_impl(out, &r_trampoline, f, indirect_call); + write_rust_function_decl_impl(out, &r_trampoline, f, &efn.self_type, indirect_call); out.next_section(); let c_trampoline = mangle::c_trampoline(efn, var, out.types).to_string(); let doc = Doc::new(); - write_rust_function_shim_impl(out, &c_trampoline, f, &doc, &r_trampoline, indirect_call); + write_rust_function_shim_impl( + out, + &c_trampoline, + f, + &efn.self_type, + &doc, + &r_trampoline, + indirect_call, + ); } fn write_rust_function_decl<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { @@ -886,7 +945,7 @@ fn write_rust_function_decl<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { out.begin_block(Block::ExternC); let link_name = mangle::extern_fn(efn, out.types); let indirect_call = false; - write_rust_function_decl_impl(out, &link_name, efn, indirect_call); + write_rust_function_decl_impl(out, &link_name, efn, &efn.self_type, indirect_call); out.end_block(Block::ExternC); } @@ -894,6 +953,7 @@ fn write_rust_function_decl_impl( out: &mut OutFile, link_name: &Symbol, sig: &Signature, + self_type: &Option, indirect_call: bool, ) { out.next_section(); @@ -928,15 +988,23 @@ fn write_rust_function_decl_impl( if needs_comma { write!(out, ", "); } - match sig.ret.as_ref().unwrap() { - Type::Ref(ret) => { - write_type_space(out, &ret.inner); - if !ret.mutable { - write!(out, "const "); + if sig.constructor { + write!( + out, + "{} ", + out.types.resolve(self_type.as_ref().unwrap()).name.cxx + ); + } else { + match sig.ret.as_ref().unwrap() { + Type::Ref(ret) => { + write_type_space(out, &ret.inner); + if !ret.mutable { + write!(out, "const "); + } + write!(out, "*"); } - write!(out, "*"); + ret => write_type_space(out, ret), } - ret => write_type_space(out, ret), } write!(out, "*return$"); needs_comma = true; @@ -963,18 +1031,38 @@ fn write_rust_function_shim<'a>(out: &mut OutFile<'a>, efn: &'a ExternFn) { let doc = &efn.doc; let invoke = mangle::extern_fn(efn, out.types); let indirect_call = false; - write_rust_function_shim_impl(out, &local_name, efn, doc, &invoke, indirect_call); + write_rust_function_shim_impl( + out, + &local_name, + efn, + &efn.self_type, + doc, + &invoke, + indirect_call, + ); } fn write_rust_function_shim_decl( out: &mut OutFile, local_name: &str, sig: &Signature, + self_type: &Option, indirect_call: bool, ) { begin_function_definition(out); - write_return_type(out, &sig.ret); - write!(out, "{}(", local_name); + if !sig.constructor { + write_return_type(out, &sig.ret); + } + if let Some(self_type) = self_type { + let cxx_name = &out.types.resolve(self_type).name.cxx; + if sig.constructor { + write!(out, "{}::{}(", cxx_name, cxx_name); + } else { + write!(out, "{}::{}(", cxx_name, local_name); + } + } else { + write!(out, "{}(", local_name); + } for (i, arg) in sig.args.iter().enumerate() { if i > 0 { write!(out, ", "); @@ -1003,11 +1091,12 @@ fn write_rust_function_shim_impl( out: &mut OutFile, local_name: &str, sig: &Signature, + self_type: &Option, doc: &Doc, invoke: &Symbol, indirect_call: bool, ) { - if out.header && sig.receiver.is_some() { + if out.header && (sig.receiver.is_some() || self_type.is_some()) { // We've already defined this inside the struct. return; } @@ -1015,7 +1104,7 @@ fn write_rust_function_shim_impl( // Member functions already documented at their declaration. write_doc(out, "", doc); } - write_rust_function_shim_decl(out, local_name, sig, indirect_call); + write_rust_function_shim_decl(out, local_name, sig, self_type, indirect_call); if out.header { writeln!(out, ";"); return; @@ -1033,20 +1122,22 @@ fn write_rust_function_shim_impl( write!(out, " "); let indirect_return = indirect_return(sig, out.types); if indirect_return { - out.builtin.maybe_uninit = true; - write!(out, "::rust::MaybeUninit<"); - match sig.ret.as_ref().unwrap() { - Type::Ref(ret) => { - write_type_space(out, &ret.inner); - if !ret.mutable { - write!(out, "const "); + if !sig.constructor { + out.builtin.maybe_uninit = true; + write!(out, "::rust::MaybeUninit<"); + match sig.ret.as_ref().unwrap() { + Type::Ref(ret) => { + write_type_space(out, &ret.inner); + if !ret.mutable { + write!(out, "const "); + } + write!(out, "*"); } - write!(out, "*"); + ret => write_type(out, ret), } - ret => write_type(out, ret), + writeln!(out, "> return$;"); + write!(out, " "); } - writeln!(out, "> return$;"); - write!(out, " "); } else if let Some(ret) = &sig.ret { write!(out, "return "); match ret { @@ -1102,7 +1193,11 @@ fn write_rust_function_shim_impl( if needs_comma { write!(out, ", "); } - write!(out, "&return$.value"); + if sig.constructor { + write!(out, "this"); + } else { + write!(out, "&return$.value"); + } needs_comma = true; } if indirect_call { @@ -1126,7 +1221,7 @@ fn write_rust_function_shim_impl( writeln!(out, " throw ::rust::impl<::rust::Error>::error(error$);"); writeln!(out, " }}"); } - if indirect_return { + if indirect_return && !sig.constructor { write!(out, " return "); match sig.ret.as_ref().unwrap() { Type::Ref(_) => write!(out, "*return$.value"), @@ -1148,9 +1243,11 @@ fn write_return_type(out: &mut OutFile, ty: &Option) { } fn indirect_return(sig: &Signature, types: &Types) -> bool { - sig.ret - .as_ref() - .is_some_and(|ret| sig.throws || types.needs_indirect_abi(ret)) + sig.constructor + || sig + .ret + .as_ref() + .is_some_and(|ret| sig.throws || types.needs_indirect_abi(ret)) } fn write_indirect_return_type(out: &mut OutFile, ty: &Type) { diff --git a/macro/src/expand.rs b/macro/src/expand.rs index 4fcf3e00f..a8c557a7a 100644 --- a/macro/src/expand.rs +++ b/macro/src/expand.rs @@ -487,8 +487,13 @@ fn expand_cxx_function_decl(efn: &ExternFn, types: &Types) -> TokenStream { }; let mut outparam = None; if indirect_return(efn, types) { - let ret = expand_extern_type(efn.ret.as_ref().unwrap(), types, true); - outparam = Some(quote!(__return: *mut #ret)); + if efn.sig.constructor { + let self_type = expand_extern_type(&types.resolve(efn.self_type.as_ref().unwrap()).into(), types, true); + outparam = Some(quote!(__return: *mut #self_type)); + } else { + let ret = expand_extern_type(efn.ret.as_ref().unwrap(), types, true); + outparam = Some(quote!(__return: *mut #ret)); + } } let link_name = mangle::extern_fn(efn, types); let local_name = format_ident!("__{}", efn.name.rust); @@ -523,6 +528,8 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { None => quote!(()), }; quote!(-> ::cxx::core::result::Result<#ok, ::cxx::Exception>) + } else if efn.sig.constructor { + quote!(-> Self) } else { expand_return_type(&efn.ret) }; @@ -626,10 +633,17 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { let local_name = format_ident!("__{}", efn.name.rust); let span = efn.semi_token.span; let call = if indirect_return { - let ret = expand_extern_type(efn.ret.as_ref().unwrap(), types, true); - setup.extend(quote_spanned! {span=> - let mut __return = ::cxx::core::mem::MaybeUninit::<#ret>::uninit(); - }); + if efn.sig.constructor { + let self_type = expand_extern_type(&types.resolve(efn.self_type.as_ref().unwrap()).into(), types, true); + setup.extend(quote_spanned! {span=> + let mut __return = ::cxx::core::mem::MaybeUninit::<#self_type>::uninit(); + }); + } else { + let ret = expand_extern_type(efn.ret.as_ref().unwrap(), types, true); + setup.extend(quote_spanned! {span=> + let mut __return = ::cxx::core::mem::MaybeUninit::<#ret>::uninit(); + }); + } setup.extend(if efn.throws { quote_spanned! {span=> #local_name(#(#vars,)* __return.as_mut_ptr()).exception()?; @@ -741,15 +755,15 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { #trampolines #dispatch }); - match &efn.receiver { - None => { + match (&efn.receiver, &efn.self_type) { + (None, None) => { quote! { #doc #attrs #visibility #unsafety #fn_token #ident #generics #arg_list #ret #fn_body } } - Some(receiver) => { + (Some(receiver), None) => { let elided_generics; let receiver_ident = &receiver.ty.rust; let resolve = types.resolve(&receiver.ty); @@ -781,6 +795,39 @@ fn expand_cxx_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { } } } + (None, Some(self_type)) => { + let elided_generics; + let resolve = types.resolve(self_type); + let self_type_ident = &resolve.name.rust; + let self_type_generics = if resolve.generics.lt_token.is_some() { + &resolve.generics + } else { + elided_generics = Lifetimes { + lt_token: resolve.generics.lt_token, + lifetimes: resolve + .generics + .lifetimes + .pairs() + .map(|pair| { + let lifetime = Lifetime::new("'_", pair.value().apostrophe); + let punct = pair.punct().map(|&&comma| comma); + punctuated::Pair::new(lifetime, punct) + }) + .collect(), + gt_token: resolve.generics.gt_token, + }; + &elided_generics + }; + quote_spanned! {ident.span()=> + #[automatically_derived] + impl #generics #self_type_ident #self_type_generics { + #doc + #attrs + #visibility #unsafety #fn_token #ident #arg_list #ret #fn_body + } + } + } + _ => unreachable!("receiver and self_type are mutually exclusive"), } } @@ -797,6 +844,7 @@ fn expand_function_pointer_trampoline( let body_span = efn.semi_token.span; let shim = expand_rust_function_shim_impl( sig, + &efn.self_type, types, &r_trampoline, local_name, @@ -940,18 +988,33 @@ fn expand_forbid(impls: TokenStream) -> TokenStream { fn expand_rust_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { let link_name = mangle::extern_fn(efn, types); - let local_name = match &efn.receiver { - None => format_ident!("__{}", efn.name.rust), - Some(receiver) => format_ident!("__{}__{}", receiver.ty.rust, efn.name.rust), + let local_name = match (&efn.receiver, &efn.self_type) { + (None, None) => format_ident!("__{}", efn.name.rust), + (Some(receiver), None) => format_ident!("__{}__{}", receiver.ty.rust, efn.name.rust), + (None, Some(self_type)) => format_ident!( + "__{}__{}", + types.resolve(self_type).name.rust, + efn.name.rust + ), + _ => unreachable!("receiver and self_type are mutually exclusive"), }; - let prevent_unwind_label = match &efn.receiver { - None => format!("::{}", efn.name.rust), - Some(receiver) => format!("::{}::{}", receiver.ty.rust, efn.name.rust), + let prevent_unwind_label = match (&efn.receiver, &efn.self_type) { + (None, None) => format!("::{}", efn.name.rust), + (Some(receiver), None) => format!("::{}::{}", receiver.ty.rust, efn.name.rust), + (None, Some(self_type)) => { + format!( + "::{}::{}", + types.resolve(self_type).name.rust, + efn.name.rust + ) + } + _ => unreachable!("receiver and self_type are mutually exclusive"), }; let invoke = Some(&efn.name.rust); let body_span = efn.semi_token.span; expand_rust_function_shim_impl( efn, + &efn.self_type, types, &link_name, local_name, @@ -965,6 +1028,7 @@ fn expand_rust_function_shim(efn: &ExternFn, types: &Types) -> TokenStream { fn expand_rust_function_shim_impl( sig: &Signature, + self_type: &Option, types: &Types, link_name: &Symbol, local_name: Ident, @@ -1057,7 +1121,8 @@ fn expand_rust_function_shim_impl( }); let vars: Vec<_> = receiver_var.into_iter().chain(arg_vars).collect(); - let wrap_super = invoke.map(|invoke| expand_rust_function_shim_super(sig, &local_name, invoke)); + let wrap_super = invoke + .map(|invoke| expand_rust_function_shim_super(sig, self_type, types, &local_name, invoke)); let mut requires_closure; let mut call = match invoke { @@ -1126,8 +1191,13 @@ fn expand_rust_function_shim_impl( let mut outparam = None; let indirect_return = indirect_return(sig, types); if indirect_return { - let ret = expand_extern_type(sig.ret.as_ref().unwrap(), types, false); - outparam = Some(quote_spanned!(span=> __return: *mut #ret,)); + if sig.constructor { + let self_type = expand_extern_type(&types.resolve(self_type.as_ref().unwrap()).into(), types, false); + outparam = Some(quote_spanned!(span=> __return: *mut #self_type,)); + } else { + let ret = expand_extern_type(sig.ret.as_ref().unwrap(), types, false); + outparam = Some(quote_spanned!(span=> __return: *mut #ret,)); + } } if sig.throws { let out = match sig.ret { @@ -1182,6 +1252,8 @@ fn expand_rust_function_shim_impl( // accurate unsafety declaration and no problematic elided lifetimes. fn expand_rust_function_shim_super( sig: &Signature, + self_type: &Option, + types: &Types, local_name: &Ident, invoke: &Ident, ) -> TokenStream { @@ -1214,6 +1286,8 @@ fn expand_rust_function_shim_super( quote_spanned!(rangle.span=> ::cxx::core::fmt::Display>) }; quote!(-> #result_begin #result_end) + } else if sig.constructor { + expand_return_type(&Some(types.resolve(self_type.as_ref().unwrap()).into())) } else { expand_return_type(&sig.ret) }; @@ -1222,12 +1296,17 @@ fn expand_rust_function_shim_super( let vars = receiver_var.iter().chain(arg_vars); let span = invoke.span(); - let call = match &sig.receiver { - None => quote_spanned!(span=> super::#invoke), - Some(receiver) => { + let call = match (&sig.receiver, &self_type) { + (None, None) => quote_spanned!(span=> super::#invoke), + (Some(receiver), None) => { let receiver_type = &receiver.ty.rust; quote_spanned!(span=> #receiver_type::#invoke) } + (None, Some(self_type)) => { + let self_type = &types.resolve(self_type).name.rust; + quote_spanned!(span=> #self_type::#invoke) + } + _ => unreachable!("receiver and self_type are mutually exclusive"), }; let mut body = quote_spanned!(span=> #call(#(#vars,)*)); @@ -1854,9 +1933,11 @@ fn expand_return_type(ret: &Option) -> TokenStream { } fn indirect_return(sig: &Signature, types: &Types) -> bool { - sig.ret - .as_ref() - .is_some_and(|ret| sig.throws || types.needs_indirect_abi(ret)) + sig.constructor + || sig + .ret + .as_ref() + .is_some_and(|ret| sig.throws || types.needs_indirect_abi(ret)) } fn expand_extern_type(ty: &Type, types: &Types, proper: bool) -> TokenStream { diff --git a/syntax/attrs.rs b/syntax/attrs.rs index 894b82b83..7fd4a824b 100644 --- a/syntax/attrs.rs +++ b/syntax/attrs.rs @@ -35,6 +35,7 @@ pub(crate) struct Parser<'a> { pub namespace: Option<&'a mut Namespace>, pub cxx_name: Option<&'a mut Option>, pub rust_name: Option<&'a mut Option>, + pub self_type: Option<&'a mut Option>, pub variants_from_header: Option<&'a mut Option>, pub ignore_unrecognized: bool, @@ -129,6 +130,19 @@ pub(crate) fn parse(cx: &mut Errors, attrs: Vec, mut parser: Parser) break; } } + } else if attr_path.is_ident("Self") { + match parse_rust_name_attribute(&attr.meta) { + Ok(attr) => { + if let Some(namespace) = &mut parser.self_type { + **namespace = Some(attr); + continue; + } + } + Err(err) => { + cx.push(err); + break; + } + } } else if attr_path.is_ident("cfg") { match cfg::parse_attribute(&attr) { Ok(cfg_expr) => { diff --git a/syntax/check.rs b/syntax/check.rs index 39ee0b0a4..06f1b69b0 100644 --- a/syntax/check.rs +++ b/syntax/check.rs @@ -498,6 +498,23 @@ fn check_api_fn(cx: &mut Check, efn: &ExternFn) { if efn.lang == Lang::Cxx { check_mut_return_restriction(cx, efn); } + + if let Some(self_type) = &efn.self_type { + if !cx.types.structs.contains_key(self_type) + && !cx.types.cxx.contains(self_type) + && !cx.types.rust.contains(self_type) + { + let msg = format!("unrecognized self type: {}", self_type); + cx.error(self_type, msg); + } + if efn.receiver.is_some() { + cx.error(efn, "self type and receiver are mutually exclusive"); + } + + if efn.sig.constructor && !cx.types.structs.contains_key(self_type) { + cx.error(efn, "constructor is only allowed for shared structure"); + } + } } fn check_api_type_alias(cx: &mut Check, alias: &TypeAlias) { diff --git a/syntax/impls.rs b/syntax/impls.rs index 36e1f322a..bb94ea136 100644 --- a/syntax/impls.rs +++ b/syntax/impls.rs @@ -319,6 +319,7 @@ impl PartialEq for Signature { throws, paren_token: _, throws_tokens: _, + constructor: _, } = self; let Signature { asyncness: asyncness2, @@ -331,6 +332,7 @@ impl PartialEq for Signature { throws: throws2, paren_token: _, throws_tokens: _, + constructor: _, } = other; asyncness.is_some() == asyncness2.is_some() && unsafety.is_some() == unsafety2.is_some() @@ -375,6 +377,7 @@ impl Hash for Signature { throws, paren_token: _, throws_tokens: _, + constructor: _, } = self; asyncness.is_some().hash(state); unsafety.is_some().hash(state); diff --git a/syntax/mangle.rs b/syntax/mangle.rs index 6f019657b..69139e96c 100644 --- a/syntax/mangle.rs +++ b/syntax/mangle.rs @@ -85,8 +85,8 @@ macro_rules! join { } pub(crate) fn extern_fn(efn: &ExternFn, types: &Types) -> Symbol { - match &efn.receiver { - Some(receiver) => { + match (&efn.receiver, &efn.self_type) { + (Some(receiver), None) => { let receiver_ident = types.resolve(&receiver.ty); join!( efn.name.namespace, @@ -95,7 +95,17 @@ pub(crate) fn extern_fn(efn: &ExternFn, types: &Types) -> Symbol { efn.name.rust, ) } - None => join!(efn.name.namespace, CXXBRIDGE, efn.name.rust), + (None, Some(self_type)) => { + let self_type_ident = types.resolve(self_type); + join!( + efn.name.namespace, + CXXBRIDGE, + self_type_ident.name.cxx, + efn.name.rust, + ) + } + (None, None) => join!(efn.name.namespace, CXXBRIDGE, efn.name.rust), + _ => unreachable!("receiver and self_type are mutually exclusive"), } } diff --git a/syntax/mod.rs b/syntax/mod.rs index eacba5541..059303d0f 100644 --- a/syntax/mod.rs +++ b/syntax/mod.rs @@ -162,6 +162,7 @@ pub(crate) struct ExternFn { pub sig: Signature, pub semi_token: Token![;], pub trusted: bool, + pub self_type: Option, } pub(crate) struct TypeAlias { @@ -217,6 +218,7 @@ pub(crate) struct Signature { pub throws: bool, pub paren_token: Paren, pub throws_tokens: Option<(kw::Result, Token![<], Token![>])>, + pub constructor: bool, } pub(crate) struct Var { diff --git a/syntax/parse.rs b/syntax/parse.rs index 875e1d38a..1a55eed8d 100644 --- a/syntax/parse.rs +++ b/syntax/parse.rs @@ -525,6 +525,7 @@ fn parse_extern_fn( let mut namespace = namespace.clone(); let mut cxx_name = None; let mut rust_name = None; + let mut self_type = None; let mut attrs = attrs.clone(); attrs.extend(attrs::parse( cx, @@ -535,6 +536,7 @@ fn parse_extern_fn( namespace: Some(&mut namespace), cxx_name: Some(&mut cxx_name), rust_name: Some(&mut rust_name), + self_type: Some(&mut self_type), ..Default::default() }, )); @@ -658,7 +660,19 @@ fn parse_extern_fn( } let mut throws_tokens = None; - let ret = parse_return_type(&foreign_fn.sig.output, &mut throws_tokens)?; + let mut constructor = false; + let ret = match parse_return_type(&foreign_fn.sig.output, &mut throws_tokens)? { + Some(Type::Ident(ident)) => { + if ident.rust == "Self" { + constructor = true; + None + } else { + Some(Type::Ident(ident)) + } + } + Some(ty) => Some(ty), + None => None, + }; let throws = throws_tokens.is_some(); let asyncness = foreign_fn.sig.asyncness; let unsafety = foreign_fn.sig.unsafety; @@ -691,9 +705,11 @@ fn parse_extern_fn( throws, paren_token, throws_tokens, + constructor, }, semi_token, trusted, + self_type, })) } @@ -1430,6 +1446,7 @@ fn parse_type_fn(ty: &TypeBareFn) -> Result { throws, paren_token, throws_tokens, + constructor: false, }))) } diff --git a/syntax/pod.rs b/syntax/pod.rs index 506e53cb5..ba89ae818 100644 --- a/syntax/pod.rs +++ b/syntax/pod.rs @@ -13,11 +13,12 @@ impl<'a> Types<'a> { CxxString | RustString => false, } } else if let Some(strct) = self.structs.get(ident) { - derive::contains(&strct.derives, Trait::Copy) + (derive::contains(&strct.derives, Trait::Copy) || strct .fields .iter() - .all(|field| self.is_guaranteed_pod(&field.ty)) + .all(|field| self.is_guaranteed_pod(&field.ty))) + && !self.structs_with_constructors.contains(ident) } else { self.enums.contains_key(ident) } diff --git a/syntax/resolve.rs b/syntax/resolve.rs index b0a4782c3..70dd6d2e6 100644 --- a/syntax/resolve.rs +++ b/syntax/resolve.rs @@ -1,5 +1,5 @@ use crate::syntax::instantiate::NamedImplKey; -use crate::syntax::{Lifetimes, NamedType, Pair, Types}; +use crate::syntax::{Lifetimes, NamedType, Pair, Type, Types}; use proc_macro2::Ident; #[derive(Copy, Clone)] @@ -23,6 +23,15 @@ impl<'a> Types<'a> { } } +impl Into for Resolution<'_> { + fn into(self) -> Type { + Type::Ident(NamedType { + rust: self.name.rust.clone(), + generics: self.generics.clone(), + }) + } +} + pub(crate) trait UnresolvedName { fn ident(&self) -> &Ident; } diff --git a/syntax/tokens.rs b/syntax/tokens.rs index fea85150d..015876a41 100644 --- a/syntax/tokens.rs +++ b/syntax/tokens.rs @@ -264,6 +264,7 @@ impl ToTokens for Signature { throws: _, paren_token, throws_tokens, + constructor: _, } = self; fn_token.to_tokens(tokens); paren_token.surround(tokens, |tokens| { diff --git a/syntax/types.rs b/syntax/types.rs index bc11eb00c..e296af87e 100644 --- a/syntax/types.rs +++ b/syntax/types.rs @@ -25,6 +25,7 @@ pub(crate) struct Types<'a> { pub resolutions: UnorderedMap<&'a Ident, Resolution<'a>>, pub struct_improper_ctypes: UnorderedSet<&'a Ident>, pub toposorted_structs: Vec<&'a Struct>, + pub structs_with_constructors: UnorderedSet<&'a Ident>, } impl<'a> Types<'a> { @@ -38,6 +39,7 @@ impl<'a> Types<'a> { let mut untrusted = UnorderedMap::new(); let mut impls = OrderedMap::new(); let mut resolutions = UnorderedMap::new(); + let mut structs_with_constructors = UnorderedSet::new(); let struct_improper_ctypes = UnorderedSet::new(); let toposorted_structs = Vec::new(); @@ -151,6 +153,9 @@ impl<'a> Types<'a> { if let Some(ret) = &efn.ret { visit(&mut all, ret); } + if efn.sig.constructor { + structs_with_constructors.insert(efn.self_type.as_ref().unwrap()); + } } Api::TypeAlias(alias) => { let ident = &alias.name.rust; @@ -209,6 +214,7 @@ impl<'a> Types<'a> { resolutions, struct_improper_ctypes, toposorted_structs, + structs_with_constructors, }; types.toposorted_structs = toposort::sort(cx, apis, &types); diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs index 9e060d3ee..dcf045a80 100644 --- a/tests/ffi/lib.rs +++ b/tests/ffi/lib.rs @@ -29,6 +29,10 @@ pub mod ffi { z: usize, } + struct SharedWithConstructor { + z: usize, + } + #[derive(PartialEq, PartialOrd)] struct SharedString { msg: String, @@ -203,6 +207,8 @@ pub mod ffi { fn c_method_on_shared(self: &Shared) -> usize; fn c_method_ref_on_shared(self: &Shared) -> &usize; fn c_method_mut_on_shared(self: &mut Shared) -> &mut usize; + #[Self = "Shared"] + fn c_static_method_on_shared() -> usize; fn c_set_array(self: &mut Array, value: i32); fn c_get_use_count(weak: &WeakPtr) -> usize; @@ -218,6 +224,14 @@ pub mod ffi { #[namespace = "other"] fn ns_c_take_ns_shared(shared: AShared); + + #[Self = "C"] + fn c_static_method() -> usize; + + #[Self = "SharedWithConstructor"] + fn c_new(x: usize, y: usize) -> Self; + + fn c_return_shared_with_constructor() -> SharedWithConstructor; } extern "C++" { @@ -315,6 +329,17 @@ pub mod ffi { #[cxx_name = "rAliasedFunction"] fn r_aliased_function(x: i32) -> String; + + #[Self = "Shared"] + fn r_static_method_on_shared() -> usize; + + #[Self = "R"] + fn r_static_method() -> usize; + + #[Self = "SharedWithConstructor"] + fn new(x: usize, y: usize, z: usize) -> Self; + + fn r_return_shared_with_constructor() -> SharedWithConstructor; } struct Dag0 { @@ -406,6 +431,10 @@ impl R { self.0 = n; n } + + fn r_static_method() -> usize { + 2024 + } } pub struct Reference<'a>(pub &'a String); @@ -414,6 +443,15 @@ impl ffi::Shared { fn r_method_on_shared(&self) -> String { "2020".to_owned() } + fn r_static_method_on_shared() -> usize { + 2023 + } +} + +impl ffi::SharedWithConstructor { + fn new(x: usize, y: usize, z: usize) -> Self { + Self { z: x + y - z } + } } impl ffi::Array { @@ -450,6 +488,12 @@ fn r_return_shared() -> ffi::Shared { ffi::Shared { z: 2020 } } +fn r_return_shared_with_constructor() -> ffi::SharedWithConstructor { + ffi::SharedWithConstructor { + z: 2020, + } +} + fn r_return_box() -> Box { Box::new(R(2020)) } diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc index 2292914cd..c130eeb73 100644 --- a/tests/ffi/tests.cc +++ b/tests/ffi/tests.cc @@ -38,12 +38,16 @@ size_t C::get_fail() { throw std::runtime_error("unimplemented"); } size_t Shared::c_method_on_shared() const noexcept { return 2021; } +SharedWithConstructor::SharedWithConstructor(size_t x, size_t y) noexcept : z(x + y) {} + const size_t &Shared::c_method_ref_on_shared() const noexcept { return this->z; } size_t &Shared::c_method_mut_on_shared() noexcept { return this->z; } +size_t Shared::c_static_method_on_shared() noexcept { return 2025; } + void Array::c_set_array(int32_t val) noexcept { this->a = {val, val, val, val}; } @@ -56,6 +60,8 @@ size_t c_return_primitive() { return 2020; } Shared c_return_shared() { return Shared{2020}; } +SharedWithConstructor c_return_shared_with_constructor() { return SharedWithConstructor{2019, 1}; } + ::A::AShared c_return_ns_shared() { return ::A::AShared{2020}; } ::A::B::ABShared c_return_nested_ns_shared() { return ::A::B::ABShared{2020}; } @@ -627,6 +633,8 @@ rust::String cOverloadedFunction(rust::Str x) { return rust::String(std::string(x)); } +size_t C::c_static_method() { return 2026; } + void c_take_trivial_ptr(std::unique_ptr d) { if (d->d == 30) { cxx_test_suite_set_correct(); @@ -786,6 +794,9 @@ extern "C" const char *cxx_run_test() noexcept { ASSERT(r_return_enum(0) == Enum::AVal); ASSERT(r_return_enum(1) == Enum::BVal); ASSERT(r_return_enum(2021) == Enum::CVal); + ASSERT(Shared::r_static_method_on_shared() == 2023); + ASSERT(R::r_static_method() == 2024); + ASSERT(SharedWithConstructor(2023, 2, 1).z == 2024); r_take_primitive(2020); r_take_shared(Shared{2020}); diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h index dc02e4ff8..bbe39f3ad 100644 --- a/tests/ffi/tests.h +++ b/tests/ffi/tests.h @@ -36,6 +36,7 @@ namespace tests { struct R; struct Shared; +struct SharedWithConstructor; struct SharedString; enum class Enum : uint16_t; @@ -54,6 +55,7 @@ class C { rust::String cOverloadedMethod(int32_t x) const; rust::String cOverloadedMethod(rust::Str x) const; + static size_t c_static_method(); private: size_t n; std::vector v; @@ -88,6 +90,7 @@ typedef char Buffer[12]; size_t c_return_primitive(); Shared c_return_shared(); +SharedWithConstructor c_return_shared_with_constructor(); ::A::AShared c_return_ns_shared(); ::A::B::ABShared c_return_nested_ns_shared(); rust::Box c_return_box(); diff --git a/tests/test.rs b/tests/test.rs index 3fe4ea5f8..93cced721 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -264,6 +264,9 @@ fn test_c_method_calls() { assert_eq!(2021, ffi::Shared { z: 0 }.c_method_on_shared()); assert_eq!(2022, *ffi::Shared { z: 2022 }.c_method_ref_on_shared()); assert_eq!(2023, *ffi::Shared { z: 2023 }.c_method_mut_on_shared()); + assert_eq!(2025, ffi::Shared::c_static_method_on_shared()); + assert_eq!(2026, ffi::C::c_static_method()); + assert_eq!(2027, ffi::SharedWithConstructor::c_new(2026, 1).z); let val = 42; let mut array = ffi::Array {