Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::util::{
create_metadata_items, either_attribute_arg, ident_to_string, kw, mod_path,
parse_comma_separated, UniffiAttributeArgs,
};
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};
use syn::{
braced,
parse::{Parse, ParseStream},
spanned::Spanned,
token::Brace,
Expr, ExprClosure, Pat, Token, Type,
};
pub struct CustomTypeArgs {
custom_type: Type,
uniffi_type: Type,
options: CustomTypeOptions,
}
impl Parse for CustomTypeArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
// Parse the custom / UniFFI type which are both required
let custom_type = input.parse()?;
input.parse::<Token![,]>()?;
let uniffi_type = input.parse()?;
let options = if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
if input.peek(Brace) {
let content;
braced!(content in input);
content.parse()?
} else {
CustomTypeOptions::default()
}
} else {
CustomTypeOptions::default()
};
Ok(Self {
custom_type,
uniffi_type,
options,
})
}
}
/// Handle extra options for the custom type macro
#[derive(Default)]
pub struct CustomTypeOptions {
lower: Option<ConvertClosure>,
try_lift: Option<ConvertClosure>,
remote: Option<kw::remote>,
}
impl CustomTypeOptions {
fn is_remote(&self) -> bool {
self.remote.is_some()
}
}
impl Parse for CustomTypeOptions {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
parse_comma_separated(input)
}
}
impl UniffiAttributeArgs for CustomTypeOptions {
fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::lower) {
input.parse::<kw::lower>()?;
input.parse::<Token![:]>()?;
Ok(Self {
lower: Some(input.parse()?),
..Self::default()
})
} else if lookahead.peek(kw::try_lift) {
input.parse::<kw::try_lift>()?;
input.parse::<Token![:]>()?;
Ok(Self {
try_lift: Some(input.parse()?),
..Self::default()
})
} else if lookahead.peek(kw::remote) {
Ok(Self {
remote: Some(input.parse()?),
..Self::default()
})
} else {
Err(lookahead.error())
}
}
fn merge(self, other: Self) -> syn::Result<Self> {
Ok(Self {
lower: either_attribute_arg(self.lower, other.lower)?,
try_lift: either_attribute_arg(self.try_lift, other.try_lift)?,
remote: either_attribute_arg(self.remote, other.remote)?,
})
}
}
struct ConvertClosure {
closure: ExprClosure,
param_ident: Ident,
body: Expr,
}
impl ConvertClosure {
fn token_tuple(&self) -> (TokenStream, TokenStream) {
let ConvertClosure {
param_ident, body, ..
} = self;
(quote! { #param_ident }, quote! { #body })
}
}
impl Parse for ConvertClosure {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let closure: ExprClosure = input.parse()?;
if closure.inputs.len() != 1 {
return Err(syn::Error::new(
closure.inputs.span(),
"Expected exactly 1 argument",
));
}
let param_ident = match closure.inputs.last().unwrap().clone() {
Pat::Ident(i) => Ok(i.ident),
Pat::Type(p) => match *p.pat {
Pat::Ident(i) => Ok(i.ident),
_ => Err(p.pat.span()),
},
input => Err(input.span()),
}
.map_err(|span| syn::Error::new(span, "Unexpected argument type"))?;
Ok(Self {
body: *closure.body.clone(),
closure,
param_ident,
})
}
}
impl ToTokens for ConvertClosure {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.closure.to_tokens(tokens)
}
}
pub(crate) fn expand_custom_type(args: CustomTypeArgs) -> syn::Result<TokenStream> {
let CustomTypeArgs {
custom_type,
uniffi_type,
options,
} = args;
let name = match &custom_type {
Type::Path(p) => match p.path.get_ident() {
Some(i) => Ok(ident_to_string(i)),
None => Err("Custom types must only have one component"),
},
_ => Err("Custom types must be specified as simple idents"),
}
.map_err(|msg| syn::Error::new(custom_type.span(), msg))?;
let mod_path = mod_path()?;
let (impl_spec, derive_ffi_traits) = if options.is_remote() {
(
quote! { unsafe impl ::uniffi::FfiConverter<crate::UniFfiTag> for #custom_type },
quote! { ::uniffi::derive_ffi_traits!(local #custom_type); },
)
} else {
(
quote! { unsafe impl<UT> ::uniffi::FfiConverter<UT> for #custom_type },
quote! { ::uniffi::derive_ffi_traits!(blanket #custom_type); },
)
};
let (lower_param, lower_expr) = match options.lower {
Some(convert_closure) => convert_closure.token_tuple(),
None => (
quote! { val },
quote! { <#custom_type as Into<#uniffi_type>>::into(val) },
),
};
let (try_lift, try_lift_expr) = match options.try_lift {
Some(convert_closure) => convert_closure.token_tuple(),
None => (
quote! { val },
quote! { Ok(<#uniffi_type as TryInto<#custom_type>>::try_into(val)?) },
),
};
let docstring = ""; // todo...
let meta_static_var = custom_type_meta_static_var(&name, &uniffi_type, docstring)?;
Ok(quote! {
#[allow(non_camel_case_types)]
#impl_spec {
// Note: the UniFFI type needs to implement both `Lower` and `Lift'. We use the
// `Lower` trait to get the associated type `FfiType` and const `TYPE_ID_META`. These
// can't differ between `Lower` and `Lift`.
type FfiType = <#uniffi_type as ::uniffi::Lower<crate::UniFfiTag>>::FfiType;
fn lower(#lower_param: #custom_type ) -> Self::FfiType {
<#uniffi_type as ::uniffi::Lower<crate::UniFfiTag>>::lower(#lower_expr)
}
fn try_lift(v: Self::FfiType) -> ::uniffi::Result<#custom_type> {
let #try_lift = <#uniffi_type as ::uniffi::Lift<crate::UniFfiTag>>::try_lift(v)?;
#try_lift_expr
}
fn write(#lower_param: #custom_type, buf: &mut Vec<u8>) {
<#uniffi_type as ::uniffi::Lower<crate::UniFfiTag>>::write(#lower_expr, buf);
}
fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<#custom_type> {
let #try_lift = <#uniffi_type as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?;
#try_lift_expr
}
const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_CUSTOM)
.concat_str(#mod_path)
.concat_str(#name)
.concat(<#uniffi_type as ::uniffi::TypeId<crate::UniFfiTag>>::TYPE_ID_META);
}
#derive_ffi_traits
#meta_static_var
})
}
pub struct CustomNewtypeArgs {
ident: Type,
uniffi_type: Type,
}
impl Parse for CustomNewtypeArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let ident = input.parse()?;
input.parse::<Token![,]>()?;
let uniffi_type = input.parse()?;
Ok(Self { ident, uniffi_type })
}
}
// Generate TypeConverter implementation for a newtype
pub(crate) fn expand_custom_newtype(args: CustomNewtypeArgs) -> syn::Result<TokenStream> {
let CustomNewtypeArgs { ident, uniffi_type } = args;
Ok(quote! {
uniffi::custom_type!(#ident, #uniffi_type, {
lower: |obj| obj.0,
try_lift: |val| Ok(#ident(val)),
});
})
}
pub(crate) fn custom_type_meta_static_var(
name: &str,
builtin: &Type,
docstring: &str,
) -> syn::Result<TokenStream> {
let module_path = mod_path()?;
let builtin = crate::ffiops::type_id_meta(builtin);
let metadata_expr = quote! {
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CUSTOM_TYPE)
.concat_str(#module_path)
.concat_str(#name)
.concat(#builtin)
.concat_long_str(#docstring)
};
Ok(create_metadata_items(
"custom_type",
name,
metadata_expr,
None,
))
}