// Copyright 2022, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Derive macro for `AsCborValue`. use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::{ parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, Fields, GenericParam, Generics, Index, }; /// Derive macro that implements the `AsCborValue` trait. Using this macro requires /// that `AsCborValue`, `CborError` and `cbor_type_error` are locally `use`d. #[proc_macro_derive(AsCborValue)] pub fn derive_as_cbor_value(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); derive_as_cbor_value_internal(&input) } fn derive_as_cbor_value_internal(input: &DeriveInput) -> proc_macro::TokenStream { let name = &input.ident; // Add a bound `T: AsCborValue` for every type parameter `T`. let generics = add_trait_bounds(&input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let from_val = from_val_struct(&input.data); let to_val = to_val_struct(&input.data); let expanded = quote! { // The generated impl impl #impl_generics AsCborValue for #name #ty_generics #where_clause { fn from_cbor_value(value: ciborium::value::Value) -> Result { #from_val } fn to_cbor_value(self) -> Result { #to_val } } }; expanded.into() } /// Add a bound `T: AsCborValue` for every type parameter `T`. fn add_trait_bounds(generics: &Generics) -> Generics { let mut generics = generics.clone(); for param in &mut generics.params { if let GenericParam::Type(ref mut type_param) = *param { type_param.bounds.push(parse_quote!(AsCborValue)); } } generics } /// Generate an expression to convert an instance of a compound type to `ciborium::value::Value` fn to_val_struct(data: &Data) -> TokenStream { match *data { Data::Struct(ref data) => { match data.fields { Fields::Named(ref fields) => { // Expands to an expression like // // { // let mut v = Vec::new(); // v.try_reserve(3).map_err(|_e| CborError::AllocationFailed)?; // v.push(AsCborValue::to_cbor_value(self.x)?); // v.push(AsCborValue::to_cbor_value(self.y)?); // v.push(AsCborValue::to_cbor_value(self.z)?); // Ok(ciborium::value::Value::Array(v)) // } let nfields = fields.named.len(); let recurse = fields.named.iter().map(|f| { let name = &f.ident; quote_spanned! {f.span()=> v.push(AsCborValue::to_cbor_value(self.#name)?) } }); quote! { { let mut v = Vec::new(); v.try_reserve(#nfields).map_err(|_e| CborError::AllocationFailed)?; #(#recurse; )* Ok(ciborium::value::Value::Array(v)) } } } Fields::Unnamed(_) => unimplemented!(), Fields::Unit => unimplemented!(), } } Data::Enum(_) => { quote! { let v: ciborium::value::Integer = (self as i32).into(); Ok(ciborium::value::Value::Integer(v)) } } Data::Union(_) => unimplemented!(), } } /// Generate an expression to convert a `ciborium::value::Value` into an instance of a compound /// type. fn from_val_struct(data: &Data) -> TokenStream { match data { Data::Struct(ref data) => { match data.fields { Fields::Named(ref fields) => { // Expands to an expression like // // let mut a = match value { // ciborium::value::Value::Array(a) => a, // _ => return cbor_type_error(&value, "arr"), // }; // if a.len() != 3 { // return Err(CborError::UnexpectedItem("arr", "arr len 3")); // } // // Fields specified in reverse order to reduce shifting. // Ok(Self { // z: ::from_cbor_value(a.remove(2))?, // y: ::from_cbor_value(a.remove(1))?, // x: ::from_cbor_value(a.remove(0))?, // }) // // but using fully qualified function call syntax. let nfields = fields.named.len(); let recurse = fields.named.iter().enumerate().rev().map(|(i, f)| { let name = &f.ident; let index = Index::from(i); let typ = &f.ty; quote_spanned! {f.span()=> #name: <#typ>::from_cbor_value(a.remove(#index))? } }); quote! { let mut a = match value { ciborium::value::Value::Array(a) => a, _ => return cbor_type_error(&value, "arr"), }; if a.len() != #nfields { return Err(CborError::UnexpectedItem( "arr", concat!("arr len ", stringify!(#nfields)), )); } // Fields specified in reverse order to reduce shifting. Ok(Self { #(#recurse, )* }) } } Fields::Unnamed(_) => unimplemented!(), Fields::Unit => unimplemented!(), } } Data::Enum(enum_data) => { // This only copes with variants with no fields. // Expands to an expression like: // // use core::convert::TryInto; // let v: i32 = match value { // ciborium::value::Value::Integer(i) => i.try_into().map_err(|_| { // CborError::OutOfRangeIntegerValue // })?, // v => return cbor_type_error(&v, &"int"), // }; // match v { // x if x == Self::Variant1 as i32 => Ok(Self::Variant1), // x if x == Self::Variant2 as i32 => Ok(Self::Variant2), // x if x == Self::Variant3 as i32 => Ok(Self::Variant3), // _ => Err( CborError::OutOfRangeIntegerValue), // } let recurse = enum_data.variants.iter().map(|variant| { let vname = &variant.ident; quote_spanned! {variant.span()=> x if x == Self::#vname as i32 => Ok(Self::#vname), } }); quote! { use core::convert::TryInto; // First get the int value as an `i32`. let v: i32 = match value { ciborium::value::Value::Integer(i) => i.try_into().map_err(|_| { CborError::OutOfRangeIntegerValue })?, v => return cbor_type_error(&v, &"int"), }; // Now match against enum possibilities. match v { #(#recurse)* _ => Err( CborError::OutOfRangeIntegerValue ), } } } Data::Union(_) => unimplemented!(), } }