Skip to content

Commit

Permalink
Allow procmacro exported items to be renamed. (#1990)
Browse files Browse the repository at this point in the history
Fixes #1753
  • Loading branch information
mhammond authored Feb 26, 2024
1 parent c1dd07f commit afbc6f1
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 42 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

### What's new?

- Functions, methods and constructors exported by procmacros can be renamed for the forgeign bindings. See the procmaco manual section.

- Rust trait interfaces can now have async functions. See the futures manual section for details.

### What's fixed?
Expand Down
25 changes: 25 additions & 0 deletions docs/manual/src/proc_macro/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,31 @@ fn do_something(foo: MyFooRef) {
}
```

### Renaming functions, methods and constructors

A single exported function can specify an alternate name to be used by the bindings by specifying a `name` attribute.

```rust
#[uniffi::export(name = "something")]
fn do_something() {
}
```
will be exposed to foreign bindings as a namespace function `something()`

You can also rename constructors and methods:
```rust
#[uniffi::export]
impl Something {
// Set this as the default constructor by naming it `new`
#[uniffi::constructor(name = "new")]
fn make_new() -> Arc<Self> { ... }

// Expose this as `obj.something()`
#[uniffi::method(name = "something")]
fn do_something(&self) { }
}
```

## The `uniffi::Record` derive

The `Record` derive macro exposes a `struct` with named fields over FFI. All types that are
Expand Down
23 changes: 23 additions & 0 deletions fixtures/proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,27 @@ pub fn join(parts: &[String], sep: &str) -> String {
parts.join(sep)
}

// Custom names
#[derive(uniffi::Object)]
pub struct Renamed;

// `renamed_new` becomes the default constructor because it's named `new`
#[uniffi::export]
impl Renamed {
#[uniffi::constructor(name = "new")]
fn renamed_new() -> Arc<Self> {
Arc::new(Self)
}

#[uniffi::method(name = "func")]
fn renamed_func(&self) -> bool {
true
}
}

#[uniffi::export(name = "rename_test")]
fn renamed_rename_test() -> bool {
true
}

uniffi::include_scaffolding!("proc-macro");
4 changes: 4 additions & 0 deletions fixtures/proc-macro/tests/bindings/test_proc_macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
obj2 = Object()
assert obj.is_other_heavy(obj2) == MaybeBool.UNCERTAIN

robj = Renamed()
assert(robj.func())
assert(rename_test())

trait_impl = obj.get_trait(None)
assert trait_impl.concat_strings("foo", "bar") == "foobar"
assert obj.get_trait(trait_impl).concat_strings("foo", "bar") == "foobar"
Expand Down
16 changes: 16 additions & 0 deletions fixtures/uitests/tests/ui/export_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,20 @@ pub enum Error {
Oops,
}

// ctor and method attribute confusion.
#[derive(uniffi::Object)]
struct OtherAttrs;

#[uniffi::export]
impl OtherAttrs {
#[uniffi::constructor(foo = bar)]
fn one() {}
}

#[uniffi::export]
impl OtherAttrs {
#[uniffi::method(foo)]
fn two() {}
}

uniffi_macros::setup_scaffolding!();
12 changes: 12 additions & 0 deletions fixtures/uitests/tests/ui/export_attrs.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,15 @@ error: attribute arguments are not currently recognized in this position
|
34 | #[uniffi(flat_error)]
| ^^^^^^^^^^

error: uniffi::constructor/method attribute `foo = bar` is not supported here.
--> tests/ui/export_attrs.rs:44:27
|
44 | #[uniffi::constructor(foo = bar)]
| ^^^

error: uniffi::constructor/method attribute `foo` is not supported here.
--> tests/ui/export_attrs.rs:50:22
|
50 | #[uniffi::method(foo)]
| ^^^
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def __init__(self, {% call py::arg_list_decl(cons) -%}):
{%- call py::setup_args_extra_indent(cons) %}
self._pointer = {% call py::to_ffi_call(cons) %}
{%- when None %}
{# no __init__ means simple construction without a pointer works, which can confuse #}
def __init__(self, *args, **kwargs):
raise ValueError("This class has no default constructor")
{%- endmatch %}

def __del__(self):
Expand Down
12 changes: 9 additions & 3 deletions uniffi_macros/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ pub(crate) fn expand_export(
let metadata = ExportItem::new(item, all_args)?;

match metadata {
ExportItem::Function { sig, args } => gen_fn_scaffolding(sig, &args, udl_mode),
ExportItem::Function { sig, args } => {
gen_fn_scaffolding(sig, &args.async_runtime, udl_mode)
}
ExportItem::Impl {
items,
self_ident,
Expand All @@ -61,8 +63,12 @@ pub(crate) fn expand_export(
let item_tokens: TokenStream = items
.into_iter()
.map(|item| match item {
ImplItem::Constructor(sig) => gen_constructor_scaffolding(sig, &args, udl_mode),
ImplItem::Method(sig) => gen_method_scaffolding(sig, &args, udl_mode),
ImplItem::Constructor(sig) => {
gen_constructor_scaffolding(sig, &args.async_runtime, udl_mode)
}
ImplItem::Method(sig) => {
gen_method_scaffolding(sig, &args.async_runtime, udl_mode)
}
})
.collect::<syn::Result<_>>()?;
Ok(quote_spanned! { self_ident.span() => #item_tokens })
Expand Down
101 changes: 93 additions & 8 deletions uniffi_macros/src/export/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ impl UniffiAttributeArgs for ExportTraitArgs {
#[derive(Default)]
pub struct ExportFnArgs {
pub(crate) async_runtime: Option<AsyncRuntime>,
pub(crate) name: Option<String>,
}

impl Parse for ExportFnArgs {
Expand All @@ -86,6 +87,15 @@ impl UniffiAttributeArgs for ExportFnArgs {
let _: Token![=] = input.parse()?;
Ok(Self {
async_runtime: Some(input.parse()?),
name: None,
})
} else if lookahead.peek(kw::name) {
let _: kw::name = input.parse()?;
let _: Token![=] = input.parse()?;
let name = Some(input.parse::<LitStr>()?.value());
Ok(Self {
async_runtime: None,
name,
})
} else {
Err(syn::Error::new(
Expand All @@ -98,12 +108,45 @@ impl UniffiAttributeArgs for ExportFnArgs {
fn merge(self, other: Self) -> syn::Result<Self> {
Ok(Self {
async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?,
name: either_attribute_arg(self.name, other.name)?,
})
}
}

// for now, `impl` blocks are identical to `fn` blocks.
pub type ExportImplArgs = ExportFnArgs;
#[derive(Default)]
pub struct ExportImplArgs {
pub(crate) async_runtime: Option<AsyncRuntime>,
}

impl Parse for ExportImplArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
parse_comma_separated(input)
}
}

impl UniffiAttributeArgs for ExportImplArgs {
fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::async_runtime) {
let _: kw::async_runtime = input.parse()?;
let _: Token![=] = input.parse()?;
Ok(Self {
async_runtime: Some(input.parse()?),
})
} else {
Err(syn::Error::new(
input.span(),
format!("uniffi::export attribute `{input}` is not supported here."),
))
}
}

fn merge(self, other: Self) -> syn::Result<Self> {
Ok(Self {
async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?,
})
}
}

#[derive(Default)]
pub struct ExportStructArgs {
Expand Down Expand Up @@ -181,9 +224,44 @@ impl ToTokens for AsyncRuntime {
}
}

#[derive(Default)]
pub struct NonExportArgs {
pub(crate) name: Option<String>,
}

impl Parse for NonExportArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
parse_comma_separated(input)
}
}

impl UniffiAttributeArgs for NonExportArgs {
fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::name) {
let _: kw::name = input.parse()?;
let _: Token![=] = input.parse()?;
let name = Some(input.parse::<LitStr>()?.value());
Ok(Self { name })
} else {
Err(syn::Error::new(
input.span(),
format!("uniffi::constructor/method attribute `{input}` is not supported here."),
))
}
}

fn merge(self, other: Self) -> syn::Result<Self> {
Ok(Self {
name: either_attribute_arg(self.name, other.name)?,
})
}
}

#[derive(Default)]
pub(super) struct ExportedImplFnAttributes {
pub constructor: bool,
pub name: Option<String>,
}

impl ExportedImplFnAttributes {
Expand All @@ -200,12 +278,11 @@ impl ExportedImplFnAttributes {
}
ensure_no_path_args(fst)?;

if let Meta::List(_) | Meta::NameValue(_) = &attr.meta {
return Err(syn::Error::new_spanned(
&attr.meta,
"attribute arguments are not currently recognized in this position",
));
}
let args = match &attr.meta {
Meta::List(_) => attr.parse_args::<NonExportArgs>()?,
_ => Default::default(),
};
this.name = args.name;

if segs.len() != 2 {
return Err(syn::Error::new_spanned(
Expand All @@ -226,6 +303,14 @@ impl ExportedImplFnAttributes {
}
this.constructor = true;
}
"method" => {
if this.constructor {
return Err(syn::Error::new_spanned(
attr,
"confused constructor/method attributes",
));
}
}
_ => return Err(syn::Error::new_spanned(snd, "unknown uniffi attribute")),
}
}
Expand Down
5 changes: 4 additions & 1 deletion uniffi_macros/src/export/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl ExportItem {
syn::Item::Fn(item) => {
let args: ExportFnArgs = syn::parse(attr_args)?;
let docstring = extract_docstring(&item.attrs)?;
let sig = FnSignature::new_function(item.sig, docstring)?;
let sig = FnSignature::new_function(item.sig, args.name.clone(), docstring)?;
Ok(Self::Function { sig, args })
}
syn::Item::Impl(item) => Self::from_impl(item, attr_args),
Expand Down Expand Up @@ -106,12 +106,14 @@ impl ExportItem {
ImplItem::Constructor(FnSignature::new_constructor(
self_ident.clone(),
impl_fn.sig,
attrs.name,
docstring,
)?)
} else {
ImplItem::Method(FnSignature::new_method(
self_ident.clone(),
impl_fn.sig,
attrs.name,
docstring,
)?)
};
Expand Down Expand Up @@ -167,6 +169,7 @@ impl ExportItem {
ImplItem::Method(FnSignature::new_trait_method(
self_ident.clone(),
tim.sig,
None,
i as u32,
docstring,
)?)
Expand Down
Loading

0 comments on commit afbc6f1

Please sign in to comment.