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

Add support for cfg_attr-like attributes #2113

Merged
merged 5 commits into from
May 28, 2024
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
did not already start with a capital letter, but this changes makes all type naming consistent.
([#2073](https://github.com/mozilla/uniffi-rs/issues/2073))

- Macros `uniffi::method` and `uniffi::constructor` can now be used with
`cfg_attr`. ([#2113](https://github.com/mozilla/uniffi-rs/pull/2113))

- Python: Fix custom types generating invalid code when there are forward references.
([#2067](https://github.com/mozilla/uniffi-rs/issues/2067))

Expand Down
7 changes: 7 additions & 0 deletions docs/manual/src/proc_macro/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,10 @@ to fix this limitation soon.
In addition to the per-item limitations of the macros presented above, there is also currently a
global restriction: You can only use the proc-macros inside a crate whose name is the same as the
namespace in your UDL file. This restriction will be lifted in the future.

### Conditional compilation
`uniffi::constructor|method` will work if wrapped with `cfg_attr` attribute:
```rust
#[cfg_attr(feature = "foo", uniffi::constructor)]
```
Other attributes are not currently supported, see [#2000](https://github.com/mozilla/uniffi-rs/issues/2000) for more details.
4 changes: 4 additions & 0 deletions fixtures/proc-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ publish = false
name = "uniffi_proc_macro"
crate-type = ["lib", "cdylib"]

[features]
default = ["myfeature"]
myfeature = []

[dependencies]
# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly
uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns"] }
Expand Down
4 changes: 2 additions & 2 deletions fixtures/proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ impl TraitWithForeign for RustTraitImpl {
#[derive(uniffi::Object)]
pub struct Object;

#[uniffi::export]
#[cfg_attr(feature = "myfeature", uniffi::export)]
impl Object {
#[uniffi::constructor]
#[cfg_attr(feature = "myfeature", uniffi::constructor)]
fn new() -> Arc<Self> {
Arc::new(Self)
}
Expand Down
122 changes: 79 additions & 43 deletions uniffi_macros/src/export/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use quote::ToTokens;
use syn::{
parenthesized,
parse::{Parse, ParseStream},
Attribute, Ident, LitStr, Meta, PathArguments, PathSegment, Token,
punctuated::Punctuated,
Attribute, Ident, LitStr, Meta, Path, PathArguments, PathSegment, Token,
};
use uniffi_meta::UniffiTraitDiscriminants;

Expand Down Expand Up @@ -249,57 +250,92 @@ impl ExportedImplFnAttributes {
pub fn new(attrs: &[Attribute]) -> syn::Result<Self> {
let mut this = Self::default();
for attr in attrs {
let segs = &attr.path().segments;

let fst = segs
.first()
.expect("attributes have at least one path segment");
if fst.ident != "uniffi" {
continue;
}
ensure_no_path_args(fst)?;

let args = match &attr.meta {
Meta::List(_) => attr.parse_args::<ExportFnArgs>()?,
_ => Default::default(),
};
this.args = args;

if segs.len() != 2 {
return Err(syn::Error::new_spanned(
segs,
"unsupported uniffi attribute",
));
}
let snd = &segs[1];
ensure_no_path_args(snd)?;

match snd.ident.to_string().as_str() {
"constructor" => {
if this.constructor {
return Err(syn::Error::new_spanned(
attr,
"duplicate constructor attribute",
));
let path = attr.path();

if is_uniffi_path(path) {
this.process_path(path, attr, &attr.meta)?;
} else if is_cfg_attr(attr) {
if let Ok(nested) =
attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
{
for meta in &nested {
if let Meta::Path(path) = meta {
this.process_path(path, attr, meta)?
}
}
this.constructor = true;
};
}
}

Ok(this)
}

fn process_path(&mut self, path: &Path, attr: &Attribute, meta: &Meta) -> syn::Result<()> {
let segs = &path.segments;

let fst = segs
.first()
.expect("attributes have at least one path segment");

if fst.ident != "uniffi" {
return Ok(());
}
ensure_no_path_args(fst)?;

let args = match meta {
Meta::List(_) => attr.parse_args::<ExportFnArgs>()?,
_ => Default::default(),
};
self.args = args;

if segs.len() != 2 {
return Err(syn::Error::new_spanned(
segs,
"unsupported uniffi attribute",
));
}
let snd = &segs[1];
ensure_no_path_args(snd)?;

match snd.ident.to_string().as_str() {
"constructor" => {
if self.constructor {
return Err(syn::Error::new_spanned(
attr,
"duplicate constructor attribute",
));
}
"method" => {
if this.constructor {
return Err(syn::Error::new_spanned(
attr,
"confused constructor/method attributes",
));
}
self.constructor = true;
}
"method" => {
if self.constructor {
return Err(syn::Error::new_spanned(
attr,
"confused constructor/method attributes",
));
}
_ => return Err(syn::Error::new_spanned(snd, "unknown uniffi attribute")),
}
_ => return Err(syn::Error::new_spanned(snd, "unknown uniffi attribute")),
}

Ok(this)
Ok(())
}
}

fn is_uniffi_path(path: &Path) -> bool {
path.segments
.first()
.map(|segment| segment.ident == "uniffi")
.unwrap_or(false)
}

fn is_cfg_attr(attr: &Attribute) -> bool {
attr.meta
.path()
.get_ident()
.is_some_and(|ident| *ident == "cfg_attr")
}

fn ensure_no_path_args(seg: &PathSegment) -> syn::Result<()> {
if matches!(seg.arguments, PathArguments::None) {
Ok(())
Expand Down