use std::collections::{HashMap, HashSet}; use crate::{ default::DefaultValue, util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs}, }; use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ parenthesized, parse::{Parse, ParseStream}, punctuated::Punctuated, Attribute, Ident, LitStr, Meta, Path, PathArguments, PathSegment, Token, }; use uniffi_meta::UniffiTraitDiscriminants; #[derive(Default)] pub struct ExportTraitArgs { pub(crate) async_runtime: Option, pub(crate) callback_interface: Option, pub(crate) with_foreign: Option, } impl Parse for ExportTraitArgs { fn parse(input: ParseStream<'_>) -> syn::Result { parse_comma_separated(input) } } impl UniffiAttributeArgs for ExportTraitArgs { fn parse_one(input: ParseStream<'_>) -> syn::Result { 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()?), ..Self::default() }) } else if lookahead.peek(kw::callback_interface) { Ok(Self { callback_interface: input.parse()?, ..Self::default() }) } else if lookahead.peek(kw::with_foreign) { Ok(Self { with_foreign: input.parse()?, ..Self::default() }) } else { Ok(Self::default()) } } fn merge(self, other: Self) -> syn::Result { let merged = Self { async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, callback_interface: either_attribute_arg( self.callback_interface, other.callback_interface, )?, with_foreign: either_attribute_arg(self.with_foreign, other.with_foreign)?, }; if merged.callback_interface.is_some() && merged.with_foreign.is_some() { return Err(syn::Error::new( merged.callback_interface.unwrap().span, "`callback_interface` and `with_foreign` are mutually exclusive", )); } Ok(merged) } } /// Attribute arguments for function /// /// This includes top-level functions, constructors, and methods. #[derive(Clone, Default)] pub struct ExportFnArgs { pub(crate) async_runtime: Option, pub(crate) name: Option, pub(crate) defaults: DefaultMap, } impl Parse for ExportFnArgs { fn parse(input: ParseStream<'_>) -> syn::Result { parse_comma_separated(input) } } impl UniffiAttributeArgs for ExportFnArgs { fn parse_one(input: ParseStream<'_>) -> syn::Result { 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()?), ..Self::default() }) } else if lookahead.peek(kw::name) { let _: kw::name = input.parse()?; let _: Token![=] = input.parse()?; let name = Some(input.parse::()?.value()); Ok(Self { name, ..Self::default() }) } else if lookahead.peek(kw::default) { Ok(Self { defaults: DefaultMap::parse(input)?, ..Self::default() }) } else { Err(syn::Error::new( input.span(), format!("attribute `{input}` is not supported here."), )) } } fn merge(self, other: Self) -> syn::Result { Ok(Self { async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, name: either_attribute_arg(self.name, other.name)?, defaults: self.defaults.merge(other.defaults), }) } } #[derive(Default)] pub struct ExportImplArgs { pub(crate) async_runtime: Option, pub(crate) name: Option, } impl Parse for ExportImplArgs { fn parse(input: ParseStream<'_>) -> syn::Result { parse_comma_separated(input) } } impl UniffiAttributeArgs for ExportImplArgs { fn parse_one(input: ParseStream<'_>) -> syn::Result { 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()?), ..Self::default() }) } else if lookahead.peek(kw::name) { let _: kw::name = input.parse()?; let _: Token![=] = input.parse()?; let name = Some(input.parse::()?.value()); Ok(Self { name, ..Self::default() }) } else { Err(syn::Error::new( input.span(), format!("uniffi::export attribute `{input}` is not supported here."), )) } } fn merge(self, other: Self) -> syn::Result { Ok(Self { async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, name: either_attribute_arg(self.name, other.name)?, }) } } #[derive(Default)] pub struct ExportStructArgs { pub(crate) traits: HashSet, } impl Parse for ExportStructArgs { fn parse(input: ParseStream<'_>) -> syn::Result { parse_comma_separated(input) } } impl UniffiAttributeArgs for ExportStructArgs { fn parse_one(input: ParseStream<'_>) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(kw::Debug) { input.parse::>()?; Ok(Self { traits: HashSet::from([UniffiTraitDiscriminants::Debug]), }) } else if lookahead.peek(kw::Display) { input.parse::>()?; Ok(Self { traits: HashSet::from([UniffiTraitDiscriminants::Display]), }) } else if lookahead.peek(kw::Hash) { input.parse::>()?; Ok(Self { traits: HashSet::from([UniffiTraitDiscriminants::Hash]), }) } else if lookahead.peek(kw::Eq) { input.parse::>()?; Ok(Self { traits: HashSet::from([UniffiTraitDiscriminants::Eq]), }) } else if lookahead.peek(kw::Ord) { input.parse::>()?; Ok(Self { traits: HashSet::from([UniffiTraitDiscriminants::Ord]), }) } else { Err(syn::Error::new( input.span(), format!( "uniffi::export struct attributes must be builtin trait names; `{input}` is invalid" ), )) } } fn merge(self, other: Self) -> syn::Result { let mut traits = self.traits; traits.extend(other.traits); Ok(Self { traits }) } } #[derive(Clone)] pub enum AsyncRuntime { Tokio(LitStr), } impl Parse for AsyncRuntime { fn parse(input: ParseStream<'_>) -> syn::Result { let lit: LitStr = input.parse()?; match lit.value().as_str() { "tokio" => Ok(Self::Tokio(lit)), _ => Err(syn::Error::new_spanned( lit, "unknown async runtime, currently only `tokio` is supported", )), } } } impl ToTokens for AsyncRuntime { fn to_tokens(&self, tokens: &mut TokenStream) { match self { AsyncRuntime::Tokio(lit) => lit.to_tokens(tokens), } } } #[derive(Default)] pub(super) struct ExportedImplFnAttributes { pub constructor: bool, pub args: ExportFnArgs, } impl ExportedImplFnAttributes { pub fn new(attrs: &[Attribute]) -> syn::Result { let mut this = Self::default(); for attr in attrs { 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::::parse_terminated) { for meta in &nested { if let Meta::Path(path) = meta { this.process_path(path, attr, meta)? } } }; } } 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::()?, _ => 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", )); } 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")), } 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(()) } else { Err(syn::Error::new_spanned(&seg.arguments, "unexpected syntax")) } } /// Maps arguments to defaults for functions #[derive(Clone, Default)] pub struct DefaultMap { map: HashMap, } impl DefaultMap { pub fn merge(self, other: Self) -> Self { let mut map = self.map; map.extend(other.map); Self { map } } pub fn remove(&mut self, ident: &Ident) -> Option { self.map.remove(ident) } pub fn idents(&self) -> Vec<&Ident> { self.map.keys().collect() } } impl Parse for DefaultMap { fn parse(input: ParseStream<'_>) -> syn::Result { let _: kw::default = input.parse()?; let content; let _ = parenthesized!(content in input); let pairs = content.parse_terminated(DefaultPair::parse, Token![,])?; Ok(Self { map: pairs.into_iter().map(|p| (p.name, p.value)).collect(), }) } } pub struct DefaultPair { pub name: Ident, pub value: DefaultValue, } impl Parse for DefaultPair { fn parse(input: ParseStream<'_>) -> syn::Result { // I'm sure there is a better way here - either want (Ident = Value) or (Ident) let lookahead = input.lookahead1(); if lookahead.peek(Ident) { let name: Ident = input.parse()?; if input.is_empty() { return Ok(Self { name, value: DefaultValue::Default, }); } if !input.peek(Token![=]) { return Err(lookahead.error()); }; let _eq: Token![=] = input.parse()?; let value: DefaultValue = input.parse()?; Ok(Self { name, value }) } else { Err(lookahead.error()) } } }