From 016659a99c55bcaca4c7ee47f95fa1528d8a7753 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 16 Mar 2020 23:02:05 +0100 Subject: [PATCH] Split `impl FromRaw` generation into its own macro --- ruma-events-macros/src/from_raw.rs | 48 +++++++++++++++++++ ruma-events-macros/src/gen.rs | 47 +----------------- ruma-events-macros/src/lib.rs | 15 +++++- .../tests/ruma_events_macros.rs | 22 ++++++++- src/from_raw.rs | 21 +++++++- src/lib.rs | 8 ++++ 6 files changed, 112 insertions(+), 49 deletions(-) create mode 100644 ruma-events-macros/src/from_raw.rs diff --git a/ruma-events-macros/src/from_raw.rs b/ruma-events-macros/src/from_raw.rs new file mode 100644 index 00000000..b9a8eb99 --- /dev/null +++ b/ruma-events-macros/src/from_raw.rs @@ -0,0 +1,48 @@ +//! Implementation of the `FromRaw` derive macro + +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{spanned::Spanned, Data, DeriveInput, Fields}; + +/// Create a `FromRaw` implementation for a struct +pub fn expand_from_raw(input: DeriveInput) -> syn::Result { + let fields = match input.data { + Data::Struct(s) => match s.fields { + Fields::Named(fs) => fs.named, + _ => panic!("#[derive(FromRaw)] only supports structs with named fields!"), + }, + _ => panic!("#[derive(FromRaw)] only supports structs!"), + }; + let ident = &input.ident; + + let init_list = fields.iter().map(|field| { + let field_ident = field.ident.as_ref().unwrap(); + let field_span = field.span(); + + if field_ident == "content" { + quote_spanned! {field_span=> + content: crate::FromRaw::from_raw(raw.content), + } + } else if field_ident == "prev_content" { + quote_spanned! {field_span=> + prev_content: raw.prev_content.map(crate::FromRaw::from_raw), + } + } else { + quote_spanned! {field_span=> + #field_ident: raw.#field_ident, + } + } + }); + + Ok(quote! { + impl crate::FromRaw for #ident { + type Raw = raw::#ident; + + fn from_raw(raw: raw::#ident) -> Self { + Self { + #(#init_list)* + } + } + } + }) +} diff --git a/ruma-events-macros/src/gen.rs b/ruma-events-macros/src/gen.rs index d7bacc31..12bb3b09 100644 --- a/ruma-events-macros/src/gen.rs +++ b/ruma-events-macros/src/gen.rs @@ -148,7 +148,6 @@ impl ToTokens for RumaEvent { // are `Some` in order to increase the number of fields we tell serde to serialize. let mut optional_field_idents = Vec::with_capacity(event_fields.len()); - let mut try_from_field_values: Vec = Vec::with_capacity(event_fields.len()); let mut serialize_field_calls: Vec = Vec::with_capacity(event_fields.len()); for field in event_fields { @@ -162,40 +161,6 @@ impl ToTokens for RumaEvent { let span = field.span(); - let try_from_field_value = if ident == "content" { - match &self.content { - Content::Struct(_) => { - quote_spanned! {span=> - content: crate::FromRaw::from_raw(raw.content), - } - } - Content::Typedef(_) => { - quote_spanned! {span=> - content: raw.content, - } - } - } - } else if ident == "prev_content" { - match &self.content { - Content::Struct(_) => { - quote_spanned! {span=> - prev_content: raw.prev_content.map(crate::FromRaw::from_raw), - } - } - Content::Typedef(_) => { - quote_spanned! {span=> - prev_content: raw.prev_content, - } - } - } - } else { - quote_spanned! {span=> - #ident: raw.#ident, - } - }; - - try_from_field_values.push(try_from_field_value); - // Does the same thing as #[serde(skip_serializing_if = "Option::is_none")] let serialize_field_call = if is_option(&field.ty) { optional_field_idents.push(ident.clone()); @@ -341,23 +306,13 @@ impl ToTokens for RumaEvent { let output = quote!( #(#attrs)* - #[derive(Clone, PartialEq, Debug)] + #[derive(Clone, PartialEq, Debug, ruma_events_macros::FromRaw)] pub struct #name { #(#stripped_fields),* } #content - impl crate::FromRaw for #name { - type Raw = raw::#name; - - fn from_raw(raw: raw::#name) -> Self { - Self { - #(#try_from_field_values)* - } - } - } - #impl_event_result_compatible_for_content impl serde::Serialize for #name { diff --git a/ruma-events-macros/src/lib.rs b/ruma-events-macros/src/lib.rs index 80ca371b..793271b0 100644 --- a/ruma-events-macros/src/lib.rs +++ b/ruma-events-macros/src/lib.rs @@ -34,9 +34,11 @@ extern crate proc_macro; use proc_macro::TokenStream; use quote::ToTokens; +use syn::{parse_macro_input, DeriveInput}; -use crate::{gen::RumaEvent, parse::RumaEventInput}; +use self::{from_raw::expand_from_raw, gen::RumaEvent, parse::RumaEventInput}; +mod from_raw; mod gen; mod parse; @@ -132,3 +134,14 @@ pub fn ruma_event(input: TokenStream) -> TokenStream { ruma_event.into_token_stream().into() } + +/// Generates an implementation of `ruma_events::FromRaw`. Only usable inside of `ruma_events`. +/// Requires there to be a `raw` module in the same scope, with a type with the same name and fields +/// as the one that this macro is used on. +#[proc_macro_derive(FromRaw)] +pub fn derive_from_raw(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + expand_from_raw(input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/ruma-events-macros/tests/ruma_events_macros.rs b/ruma-events-macros/tests/ruma_events_macros.rs index a583f6f8..bc486f3e 100644 --- a/ruma-events-macros/tests/ruma_events_macros.rs +++ b/ruma-events-macros/tests/ruma_events_macros.rs @@ -1,5 +1,6 @@ use std::{ borrow::Cow, + collections::HashMap, convert::{Infallible, TryFrom}, fmt::{Debug, Display, Formatter, Result as FmtResult}, }; @@ -103,6 +104,25 @@ pub trait TryFromRaw: Sized { fn try_from_raw(_: Self::Raw) -> Result; } +impl FromRaw for serde_json::Value { + type Raw = Self; + + fn from_raw(raw: Self) -> Self { + raw + } +} + +impl FromRaw for HashMap +where + Self: DeserializeOwned, +{ + type Raw = Self; + + fn from_raw(raw: Self) -> Self { + raw + } +} + impl TryFromRaw for T { type Raw = ::Raw; type Err = Infallible; @@ -472,7 +492,7 @@ mod type_alias { /// /// A mapping of `UserId`'s to a collection of `RoomId`'s which are considered /// *direct* for that particular user. - std::collections::HashMap> + HashMap> } } } diff --git a/src/from_raw.rs b/src/from_raw.rs index 020ca4cf..95213391 100644 --- a/src/from_raw.rs +++ b/src/from_raw.rs @@ -1,4 +1,4 @@ -use std::{convert::Infallible, fmt::Display}; +use std::{collections::HashMap, convert::Infallible, fmt::Display}; use serde::de::DeserializeOwned; @@ -27,6 +27,25 @@ pub trait TryFromRaw: Sized { fn try_from_raw(_: Self::Raw) -> Result; } +impl FromRaw for serde_json::Value { + type Raw = Self; + + fn from_raw(raw: Self) -> Self { + raw + } +} + +impl FromRaw for HashMap +where + Self: DeserializeOwned, +{ + type Raw = Self; + + fn from_raw(raw: Self) -> Self { + raw + } +} + impl TryFromRaw for T { type Raw = ::Raw; type Err = Infallible; diff --git a/src/lib.rs b/src/lib.rs index e2bec39f..f4d72d69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -350,6 +350,14 @@ impl<'de> Deserialize<'de> for Empty { } } +impl FromRaw for Empty { + type Raw = Self; + + fn from_raw(raw: Self) -> Self { + raw + } +} + /// A basic event. pub trait Event: Debug + Serialize + Sized + TryFromRaw { /// The type of this event's `content` field.