Skip to content

Commit

Permalink
Merge pull request #2113 from saks/possible_fix_for_2000
Browse files Browse the repository at this point in the history
Add support for cfg_attr-like attributes
  • Loading branch information
bendk authored May 28, 2024
2 parents c3e57f7 + abe062d commit e83fc4e
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 45 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,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 @@ -492,3 +492,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

0 comments on commit e83fc4e

Please sign in to comment.