| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| darling::{ast, FromDeriveInput, FromField, FromMeta, FromVariant}, |
| proc_macro2::TokenStream, |
| quote::{quote, quote_spanned}, |
| syn::{parse_macro_input, Ident, Type}, |
| }; |
| |
| const PATHS_DELIM: &str = ","; |
| |
| // This wrapper struct so that we can support parsing syn::Lit into Vec<syn::Path>. |
| // Rust compiler doesn't allow implementing a trait (FromMeta in this case) |
| // for a class defined externally, so we have to create a wrapper class |
| // in order to compile. |
| #[derive(Default)] |
| struct PathSet { |
| paths: Vec<syn::Path>, |
| } |
| |
| impl PathSet { |
| pub fn new(paths: Vec<syn::Path>) -> PathSet { |
| return PathSet { paths }; |
| } |
| } |
| |
| impl FromMeta for PathSet { |
| fn from_value(value: &syn::Lit) -> Result<PathSet, darling::Error> { |
| let value = String::from_value(value)?; |
| let v = value |
| .split(PATHS_DELIM) |
| .into_iter() |
| .map(|p| syn::Path::from_string(&p)) |
| .collect::<Result<Vec<syn::Path>, darling::Error>>()?; |
| Ok(PathSet::new(v)) |
| } |
| } |
| |
| #[derive(FromVariant, Clone)] |
| struct EnumVariant { |
| ident: Ident, |
| } |
| |
| #[derive(FromField, Clone)] |
| #[darling(attributes(fidl_decl))] |
| struct StructField { |
| ident: Option<Ident>, |
| ty: Type, |
| |
| // If `#[fidl_decl(default)]` is specified and the field is `None` in FIDL, |
| // the field will be set to `Default::default()` in the native type. |
| // |
| // NOTE: the `#[darling(default)]` means that the `default` field itself |
| // should default to `false`. |
| #[darling(default)] |
| default: bool, |
| |
| // Same as `#[fidl_decl(default)]`, except the [NativeIntoFidl] implementation will convert |
| // this field to `Option<Foo>` instead of `Some(Foo)`. Use this when you want to convert the |
| // default value back to `None`. |
| #[darling(default)] |
| default_preserve_none: bool, |
| } |
| |
| #[derive(Clone, Copy, Debug)] |
| enum SourcePathOpt { |
| None, |
| Dictionary, |
| NameOnly, |
| } |
| |
| impl Default for SourcePathOpt { |
| fn default() -> Self { |
| Self::None |
| } |
| } |
| |
| impl FromMeta for SourcePathOpt { |
| fn from_value(v: &syn::Lit) -> Result<Self, darling::Error> { |
| let v = String::from_value(v)?; |
| let v = v.as_str(); |
| let v = match v { |
| "none" => Self::None, |
| "dictionary" => Self::Dictionary, |
| "name_only" => Self::NameOnly, |
| _ => return Err(darling::Error::unknown_value(v)), |
| }; |
| Ok(v) |
| } |
| } |
| |
| #[derive(FromDeriveInput)] |
| #[darling(attributes(fidl_decl), supports(enum_newtype, struct_named))] |
| struct FidlDeclOpts { |
| ident: Ident, |
| data: ast::Data<EnumVariant, StructField>, |
| #[darling(default)] |
| fidl_table: Option<PathSet>, |
| #[darling(default)] |
| fidl_union: Option<PathSet>, |
| #[darling(default)] |
| source_path: SourcePathOpt, |
| } |
| |
| fn fidl_decl_derive_impl(input: syn::DeriveInput) -> TokenStream { |
| let opts = match FidlDeclOpts::from_derive_input(&input) { |
| Ok(opts) => opts, |
| Err(e) => return e.write_errors(), |
| }; |
| let ts = match opts.data { |
| ast::Data::Enum(variants) => match (opts.fidl_union, opts.fidl_table) { |
| (Some(ps), None) => { |
| let mut ts: TokenStream = TokenStream::new(); |
| for p in ps.paths.into_iter() { |
| let t = generate_enum( |
| opts.ident.clone(), |
| Type::Path(syn::TypePath { qself: None, path: p }), |
| variants.clone(), |
| ); |
| ts = quote! { |
| #ts |
| #t |
| } |
| } |
| ts |
| } |
| (Some(_), Some(_)) => { |
| darling::Error::custom("only one of `fidl_union` or `fidl_table` must be set") |
| .with_span(&input) |
| .write_errors() |
| } |
| _ => darling::Error::custom("missing `fidl_union` attribute") |
| .with_span(&input) |
| .write_errors(), |
| }, |
| ast::Data::Struct(fields) => match (opts.fidl_union, opts.fidl_table) { |
| (None, Some(ps)) => { |
| let mut ts: TokenStream = TokenStream::new(); |
| for p in ps.paths.into_iter() { |
| let t = generate_struct( |
| opts.ident.clone(), |
| Type::Path(syn::TypePath { qself: None, path: p }), |
| fields.fields.clone(), |
| ); |
| ts = quote! { |
| #ts |
| #t |
| }; |
| } |
| ts |
| } |
| (Some(_), Some(_)) => { |
| darling::Error::custom("only one of `fidl_union` or `fidl_table` must be set") |
| .with_span(&input) |
| .write_errors() |
| } |
| _ => darling::Error::custom("missing `fidl_table` attribute") |
| .with_span(&input) |
| .write_errors(), |
| }, |
| }; |
| match opts.source_path { |
| SourcePathOpt::Dictionary => { |
| let ident = opts.ident; |
| let t = quote! { |
| impl SourcePath for #ident { |
| fn source_path(&self) -> BorrowedSeparatedPath<'_> { |
| lazy_static::lazy_static! { |
| static ref DOT: RelativePath = RelativePath::dot(); |
| } |
| #[cfg(fuchsia_api_level_at_least = "HEAD")] |
| let dirname = &self.source_dictionary; |
| #[cfg(fuchsia_api_level_less_than = "HEAD")] |
| let dirname = &*DOT; |
| BorrowedSeparatedPath { |
| dirname, |
| basename: &self.source_name, |
| } |
| } |
| } |
| }; |
| quote! { |
| #ts |
| #t |
| } |
| } |
| SourcePathOpt::NameOnly => { |
| let ident = opts.ident; |
| let t = quote! { |
| impl SourcePath for #ident { |
| fn source_path(&self) -> BorrowedSeparatedPath<'_> { |
| lazy_static::lazy_static! { |
| static ref DOT: RelativePath = RelativePath::dot(); |
| } |
| BorrowedSeparatedPath { |
| dirname: &*DOT, |
| basename: &self.source_name, |
| } |
| } |
| } |
| }; |
| quote! { |
| #ts |
| #t |
| } |
| } |
| SourcePathOpt::None => ts, |
| } |
| } |
| |
| fn generate_enum( |
| enum_ident: Ident, |
| fidl_type: syn::Type, |
| variants: Vec<EnumVariant>, |
| ) -> TokenStream { |
| let fidl_into_native_lines = variants |
| .iter() |
| .map(|v| { |
| let ident = &v.ident; |
| quote_spanned! {ident.span()=> |
| Self::#ident(inner) => #enum_ident::#ident(inner.fidl_into_native()), |
| } |
| }) |
| .collect::<Vec<_>>(); |
| let native_into_fidl_lines = variants |
| .iter() |
| .map(|v| { |
| let ident = &v.ident; |
| quote_spanned! {ident.span()=> |
| Self::#ident(inner) => #fidl_type::#ident(inner.native_into_fidl()), |
| } |
| }) |
| .collect::<Vec<_>>(); |
| quote! { |
| impl FidlIntoNative<#enum_ident> for #fidl_type { |
| fn fidl_into_native(self) -> #enum_ident { |
| match self { |
| #(#fidl_into_native_lines)* |
| _ => panic!("unknown FIDL variant"), |
| } |
| } |
| } |
| |
| impl NativeIntoFidl<#fidl_type> for #enum_ident { |
| fn native_into_fidl(self) -> #fidl_type { |
| match self { |
| #(#native_into_fidl_lines)* |
| } |
| } |
| } |
| } |
| } |
| |
| enum WrapperType { |
| /// The type is not wrapped by anything. |
| Raw, |
| /// The type is Option<T>. |
| Option, |
| /// The type is Vec<T>. |
| Vec, |
| } |
| |
| /// Finds the wrapper type for the given type. This is used to find out whether the |
| /// type is an Option<T>, Vec<T>, or just T. |
| /// This is NOT fool-proof. If the type appears as std::vec::Vec<T> for instance, it |
| /// won't be recognized. That is one of the reasons this macro is not exported beyond |
| /// `cm_rust`. |
| fn wrapper_type(ty: &Type) -> WrapperType { |
| if let Type::Path(pt) = ty { |
| if pt.qself.is_none() && pt.path.leading_colon.is_none() && pt.path.segments.len() == 1 { |
| let segment = &pt.path.segments[0]; |
| if let syn::PathArguments::AngleBracketed(params) = &segment.arguments { |
| if params.args.len() == 1 { |
| if let syn::GenericArgument::Type(_) = ¶ms.args[0] { |
| if segment.ident == "Option" { |
| return WrapperType::Option; |
| } else if segment.ident == "Vec" { |
| return WrapperType::Vec; |
| } |
| } |
| } |
| } |
| } |
| } |
| WrapperType::Raw |
| } |
| |
| fn generate_struct(struct_ident: Ident, fidl_type: Type, fields: Vec<StructField>) -> TokenStream { |
| let mut fidl_into_native_lines = Vec::new(); |
| let mut native_into_fidl_lines = Vec::new(); |
| enum DefaultOption { |
| Off, |
| Default, |
| DefaultPreserveNone, |
| } |
| for field in fields { |
| let default = match (field.default, field.default_preserve_none) { |
| (true, true) => { |
| panic!( |
| "#[fidl_decl(default)] and #[fidl_decl(default_preserve_none)] \ |
| can't both be set" |
| ) |
| } |
| (true, false) => DefaultOption::Default, |
| (false, true) => DefaultOption::DefaultPreserveNone, |
| (false, false) => DefaultOption::Off, |
| }; |
| let field_ident = field.ident.unwrap(); |
| match (wrapper_type(&field.ty), default) { |
| (WrapperType::Raw, DefaultOption::Off) => { |
| fidl_into_native_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: self.#field_ident.unwrap().fidl_into_native() |
| }); |
| native_into_fidl_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: Some(self.#field_ident.native_into_fidl()) |
| }); |
| } |
| (WrapperType::Raw, DefaultOption::Default) => { |
| fidl_into_native_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: self.#field_ident.map(FidlIntoNative::fidl_into_native).unwrap_or_default() |
| }); |
| native_into_fidl_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: Some(self.#field_ident.native_into_fidl()) |
| }); |
| } |
| (WrapperType::Raw, DefaultOption::DefaultPreserveNone) => { |
| fidl_into_native_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: self.#field_ident.map(FidlIntoNative::fidl_into_native).unwrap_or_default() |
| }); |
| native_into_fidl_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: self.#field_ident.native_into_fidl() |
| }); |
| } |
| (WrapperType::Option, DefaultOption::Off) => { |
| fidl_into_native_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: self.#field_ident.map(FidlIntoNative::fidl_into_native) |
| }); |
| native_into_fidl_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: self.#field_ident.map(NativeIntoFidl::native_into_fidl) |
| }); |
| } |
| (WrapperType::Option, _) => { |
| panic!("fidl_decl(default) attribute cannot be used with Option types") |
| } |
| (WrapperType::Vec, DefaultOption::Off) => { |
| fidl_into_native_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: self |
| .#field_ident |
| .into_iter() |
| .map(std::iter::IntoIterator::into_iter) |
| .flatten() |
| .map(FidlIntoNative::fidl_into_native). |
| collect() |
| }); |
| native_into_fidl_lines.push(quote_spanned! {field_ident.span()=> |
| #field_ident: if self.#field_ident.is_empty() { |
| None |
| } else { |
| Some(self.#field_ident.into_iter().map(NativeIntoFidl::native_into_fidl).collect()) |
| } |
| }); |
| } |
| (WrapperType::Vec, _) => { |
| panic!("fidl_decl(default) attribute cannot be used with Vec types") |
| } |
| } |
| } |
| quote! { |
| impl FidlIntoNative< #struct_ident > for #fidl_type { |
| fn fidl_into_native(self) -> #struct_ident { |
| #struct_ident { |
| #( #fidl_into_native_lines, )* |
| } |
| } |
| } |
| |
| impl NativeIntoFidl< #fidl_type > for #struct_ident { |
| fn native_into_fidl(self) -> #fidl_type { |
| #fidl_type { |
| #( #native_into_fidl_lines, )* |
| ..<#fidl_type>::default() |
| } |
| } |
| } |
| } |
| } |
| |
| /// A derive-macro that generates implementations of `NativeIntoFidl` and `FidlIntoNative`. |
| /// |
| /// The macro supports the following top-level attributes: |
| /// |
| /// - `#[fidl_decl(fidl_table = "path::to::fidl::Table")]`: Generates implementations to convert |
| /// between this struct and a FIDL table of the given name. |
| /// - `#[fidl_decl(fidl_union = "path::to::fidl::Union")]`: Generates implementations to convert |
| /// between this enum and a FIDL union of the given name. |
| /// |
| /// The names of the fields/variants are mapped to those of the FIDL table/union, so they must be |
| /// the same. |
| /// |
| /// A field/variant can be omitted, in which case the conversion impl panics when the FIDL |
| /// table/union has that field/variant present. |
| #[proc_macro_derive(FidlDecl, attributes(fidl_decl))] |
| pub fn fidl_decl_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
| fidl_decl_derive_impl(parse_macro_input!(input)).into() |
| } |
| |
| #[derive(FromDeriveInput)] |
| #[darling(supports(struct_named))] |
| struct DeclCommonOpts { |
| ident: Ident, |
| } |
| |
| fn use_decl_common_derive_impl(input: syn::DeriveInput) -> TokenStream { |
| let struct_ident = match DeclCommonOpts::from_derive_input(&input) { |
| Ok(opts) => opts.ident, |
| Err(e) => return e.write_errors(), |
| }; |
| |
| quote! { |
| impl SourceName for #struct_ident { |
| fn source_name(&self) -> &Name { |
| &self.source_name |
| } |
| } |
| |
| impl UseDeclCommon for #struct_ident { |
| fn source(&self) -> &UseSource { |
| &self.source |
| } |
| fn availability(&self) -> &Availability { |
| &self.availability |
| } |
| } |
| } |
| } |
| |
| /// A derive-macro that generates an implementation of `UseDeclCommon`. Use this for |
| /// the inner structs of each variant of `UseDecl`. |
| #[proc_macro_derive(UseDeclCommon)] |
| pub fn use_decl_common_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
| use_decl_common_derive_impl(parse_macro_input!(input)).into() |
| } |
| |
| fn offer_decl_common_derive_impl(input: syn::DeriveInput) -> TokenStream { |
| let struct_ident = match DeclCommonOpts::from_derive_input(&input) { |
| Ok(opts) => opts.ident, |
| Err(e) => return e.write_errors(), |
| }; |
| |
| quote! { |
| impl SourceName for #struct_ident { |
| fn source_name(&self) -> &Name { |
| &self.source_name |
| } |
| } |
| |
| impl OfferDeclCommon for #struct_ident { |
| fn target_name(&self) -> &Name { |
| &self.target_name |
| } |
| |
| fn source(&self) -> &OfferSource { |
| &self.source |
| } |
| |
| fn target(&self) -> &OfferTarget { |
| &self.target |
| } |
| |
| fn availability(&self) -> &Availability { |
| &self.availability |
| } |
| } |
| } |
| } |
| |
| /// A derive-macro that generates an implementation of `OfferDeclCommon`. Use this for |
| /// the inner structs of each variant of `OfferDecl`. |
| #[proc_macro_derive(OfferDeclCommon)] |
| pub fn offer_decl_common_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
| offer_decl_common_derive_impl(parse_macro_input!(input)).into() |
| } |
| |
| fn offer_decl_common_derive_availability_required_impl(input: syn::DeriveInput) -> TokenStream { |
| let struct_ident = match DeclCommonOpts::from_derive_input(&input) { |
| Ok(opts) => opts.ident, |
| Err(e) => return e.write_errors(), |
| }; |
| |
| quote! { |
| impl SourceName for #struct_ident { |
| fn source_name(&self) -> &Name { |
| &self.source_name |
| } |
| } |
| |
| impl OfferDeclCommon for #struct_ident { |
| fn target_name(&self) -> &Name { |
| &self.target_name |
| } |
| |
| fn source(&self) -> &OfferSource { |
| &self.source |
| } |
| |
| fn target(&self) -> &OfferTarget { |
| &self.target |
| } |
| |
| fn availability(&self) -> &Availability { |
| &Availability::Required |
| } |
| } |
| } |
| } |
| |
| /// A derive-macro that generates an implementation of `OfferDeclCommon`. Use this for |
| /// the inner structs of each variant of `OfferDecl` that do not have an `availability` field. |
| #[proc_macro_derive(OfferDeclCommonNoAvailability)] |
| pub fn offer_decl_common_derive_availability_required( |
| input: proc_macro::TokenStream, |
| ) -> proc_macro::TokenStream { |
| offer_decl_common_derive_availability_required_impl(parse_macro_input!(input)).into() |
| } |
| |
| fn expose_decl_common_derive_impl(input: syn::DeriveInput) -> TokenStream { |
| let struct_ident = match DeclCommonOpts::from_derive_input(&input) { |
| Ok(opts) => opts.ident, |
| Err(e) => return e.write_errors(), |
| }; |
| |
| quote! { |
| impl SourceName for #struct_ident { |
| fn source_name(&self) -> &Name { |
| &self.source_name |
| } |
| } |
| |
| impl ExposeDeclCommon for #struct_ident { |
| fn target_name(&self) -> &Name { |
| &self.target_name |
| } |
| |
| fn source(&self) -> &ExposeSource { |
| &self.source |
| } |
| |
| fn target(&self) -> &ExposeTarget { |
| &self.target |
| } |
| |
| fn availability(&self) -> &Availability { |
| &self.availability |
| } |
| } |
| } |
| } |
| |
| /// A derive-macro that generates an implementation of `ExposeDeclCommon`. Use this |
| /// for the inner structs of each variant of `ExposeDecl` that supports specifying availability. |
| #[proc_macro_derive(ExposeDeclCommon)] |
| pub fn expose_decl_common_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
| expose_decl_common_derive_impl(parse_macro_input!(input)).into() |
| } |
| |
| fn expose_decl_common_availability_always_required_derive_impl( |
| input: syn::DeriveInput, |
| ) -> TokenStream { |
| let struct_ident = match DeclCommonOpts::from_derive_input(&input) { |
| Ok(opts) => opts.ident, |
| Err(e) => return e.write_errors(), |
| }; |
| |
| quote! { |
| impl SourceName for #struct_ident { |
| fn source_name(&self) -> &Name { |
| &self.source_name |
| } |
| } |
| |
| impl ExposeDeclCommon for #struct_ident { |
| fn target_name(&self) -> &Name { |
| &self.target_name |
| } |
| |
| fn source(&self) -> &ExposeSource { |
| &self.source |
| } |
| |
| fn target(&self) -> &ExposeTarget { |
| &self.target |
| } |
| |
| fn availability(&self) -> &Availability { |
| &Availability::Required |
| } |
| } |
| } |
| } |
| |
| /// A derive-macro that generates an implementation of `ExposeDeclCommon`. Use this |
| /// for inner structs of each variant of `ExposeDecl` whose availability is always required. |
| #[proc_macro_derive(ExposeDeclCommonAlwaysRequired)] |
| pub fn expose_decl_common_with_availability_derive( |
| input: proc_macro::TokenStream, |
| ) -> proc_macro::TokenStream { |
| expose_decl_common_availability_always_required_derive_impl(parse_macro_input!(input)).into() |
| } |