|
| 1 | +// Take a look at the license at the top of the repository in the LICENSE file. |
| 2 | + |
| 3 | +use quote::quote; |
| 4 | +use syn::{parse::Parse, Token}; |
| 5 | + |
| 6 | +use crate::utils::crate_ident_new; |
| 7 | + |
| 8 | +#[derive(Default, Debug, Clone)] |
| 9 | +enum DeriveMode { |
| 10 | + From, |
| 11 | + #[default] |
| 12 | + Private, |
| 13 | +} |
| 14 | + |
| 15 | +pub struct ValueDelegateInput { |
| 16 | + delegated_ty: syn::Path, |
| 17 | + ident: syn::Ident, |
| 18 | + mode: DeriveMode, |
| 19 | + nullable: bool, |
| 20 | +} |
| 21 | + |
| 22 | +enum Arg { |
| 23 | + FromPath(syn::Path), |
| 24 | + Nullable, |
| 25 | +} |
| 26 | + |
| 27 | +impl Parse for Arg { |
| 28 | + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
| 29 | + let argname: syn::Ident = input.parse()?; |
| 30 | + if argname == "nullable" { |
| 31 | + Ok(Arg::Nullable) |
| 32 | + } else if argname == "from" { |
| 33 | + let _eq: Token![=] = input.parse()?; |
| 34 | + Ok(Arg::FromPath(input.parse()?)) |
| 35 | + } else { |
| 36 | + Err(syn::Error::new( |
| 37 | + input.span(), |
| 38 | + "expected `nullable` or `from`", |
| 39 | + )) |
| 40 | + } |
| 41 | + } |
| 42 | +} |
| 43 | + |
| 44 | +#[derive(Default)] |
| 45 | +struct Args { |
| 46 | + nullable: bool, |
| 47 | + from_path: Option<syn::Path>, |
| 48 | +} |
| 49 | + |
| 50 | +impl Parse for Args { |
| 51 | + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
| 52 | + let args = syn::punctuated::Punctuated::<Arg, Token![,]>::parse_terminated(input)?; |
| 53 | + let mut this = Args::default(); |
| 54 | + for a in args { |
| 55 | + match a { |
| 56 | + Arg::FromPath(p) => this.from_path = Some(p), |
| 57 | + Arg::Nullable => this.nullable = true, |
| 58 | + } |
| 59 | + } |
| 60 | + Ok(this) |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +impl Parse for ValueDelegateInput { |
| 65 | + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
| 66 | + let derive_input: syn::DeriveInput = input.parse()?; |
| 67 | + let args: Option<Args> = if let Some(attr) = derive_input |
| 68 | + .attrs |
| 69 | + .iter() |
| 70 | + .find(|x| x.path.is_ident("value_delegate")) |
| 71 | + { |
| 72 | + let args: Args = attr.parse_args()?; |
| 73 | + Some(args) |
| 74 | + } else { |
| 75 | + None |
| 76 | + }; |
| 77 | + |
| 78 | + let (delegated_ty, mode) = |
| 79 | + if let Some(path) = args.as_ref().and_then(|a| a.from_path.as_ref()) { |
| 80 | + (Some(path.clone()), DeriveMode::From) |
| 81 | + } else { |
| 82 | + let path = match derive_input.data { |
| 83 | + syn::Data::Struct(s) => match s.fields { |
| 84 | + syn::Fields::Unnamed(fields) if fields.unnamed.iter().count() == 1 => { |
| 85 | + fields.unnamed.into_iter().next().and_then(|x| match x.ty { |
| 86 | + syn::Type::Path(p) => Some(p.path), |
| 87 | + _ => None, |
| 88 | + }) |
| 89 | + } |
| 90 | + _ => None, |
| 91 | + }, |
| 92 | + _ => None, |
| 93 | + }; |
| 94 | + (path, DeriveMode::Private) |
| 95 | + }; |
| 96 | + let delegated_ty = delegated_ty.ok_or_else(|| { |
| 97 | + syn::Error::new( |
| 98 | + derive_input.ident.span(), |
| 99 | + "Unless `derive(ValueDelegate)` is used over a newtype with 1 field, \ |
| 100 | + the delegated type must be specified using \ |
| 101 | + #[value_delegate(from = chosen_type)]", |
| 102 | + ) |
| 103 | + })?; |
| 104 | + |
| 105 | + Ok(ValueDelegateInput { |
| 106 | + delegated_ty, |
| 107 | + ident: derive_input.ident, |
| 108 | + mode, |
| 109 | + nullable: args.map(|a| a.nullable).unwrap_or(false), |
| 110 | + }) |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +pub fn impl_value_delegate(input: ValueDelegateInput) -> syn::Result<proc_macro::TokenStream> { |
| 115 | + let ValueDelegateInput { |
| 116 | + delegated_ty, |
| 117 | + ident, |
| 118 | + mode, |
| 119 | + nullable, |
| 120 | + .. |
| 121 | + } = &input; |
| 122 | + let crate_ident = crate_ident_new(); |
| 123 | + |
| 124 | + // this must be called in a context where `this` is defined. |
| 125 | + let delegate_value = match mode { |
| 126 | + DeriveMode::From => quote!(#delegated_ty::from(this)), |
| 127 | + DeriveMode::Private => quote!(this.0), |
| 128 | + }; |
| 129 | + |
| 130 | + let to_value_optional = nullable.then(|| { |
| 131 | + quote! { |
| 132 | + impl #crate_ident::value::ToValueOptional for #ident { |
| 133 | + fn to_value_optional(s: Option<&Self>) -> #crate_ident::value::Value { |
| 134 | + if let Some(this) = s { |
| 135 | + Some(&#delegate_value).to_value() |
| 136 | + } else { |
| 137 | + #crate_ident::value::ToValueOptional::to_value_optional(None::<&#delegated_ty>) |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | + } |
| 142 | + }); |
| 143 | + |
| 144 | + let from_value = match mode { |
| 145 | + DeriveMode::From => quote!(#ident::from(#delegated_ty::from_value(value))), |
| 146 | + DeriveMode::Private => quote!(#ident(#delegated_ty::from_value(value))), |
| 147 | + }; |
| 148 | + |
| 149 | + let res = quote! { |
| 150 | + impl #crate_ident::types::StaticType for #ident { |
| 151 | + fn static_type() -> glib::types::Type { |
| 152 | + #delegated_ty::static_type() |
| 153 | + } |
| 154 | + } |
| 155 | + impl #crate_ident::value::ToValue for #ident { |
| 156 | + fn to_value(&self) -> #crate_ident::value::Value { |
| 157 | + let this = self; |
| 158 | + #delegate_value.to_value() |
| 159 | + } |
| 160 | + fn value_type(&self) -> #crate_ident::types::Type { |
| 161 | + let this = self; |
| 162 | + #delegate_value.value_type() |
| 163 | + } |
| 164 | + } |
| 165 | + |
| 166 | + #to_value_optional |
| 167 | + |
| 168 | + unsafe impl<'a> #crate_ident::value::FromValue<'a> for #ident { |
| 169 | + type Checker = <#delegated_ty as #crate_ident::value::FromValue<'a>>::Checker; |
| 170 | + |
| 171 | + unsafe fn from_value(value: &'a #crate_ident::value::Value) -> Self { |
| 172 | + #from_value |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + impl #crate_ident::HasParamSpec for #ident { |
| 177 | + type ParamSpec = <#delegated_ty as #crate_ident::HasParamSpec>::ParamSpec; |
| 178 | + type SetValue = Self; |
| 179 | + type BuilderFn = <#delegated_ty as #crate_ident::HasParamSpec>::BuilderFn; |
| 180 | + |
| 181 | + fn param_spec_builder() -> Self::BuilderFn { |
| 182 | + #delegated_ty::param_spec_builder() |
| 183 | + } |
| 184 | + } |
| 185 | + }; |
| 186 | + Ok(res.into()) |
| 187 | +} |
0 commit comments