Split impl FromRaw generation into its own macro

This commit is contained in:
Jonas Platte 2020-03-16 23:02:05 +01:00
parent 0efe82bf88
commit 016659a99c
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
6 changed files with 112 additions and 49 deletions

View File

@ -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<TokenStream> {
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)*
}
}
}
})
}

View File

@ -148,7 +148,6 @@ impl ToTokens for RumaEvent {
// are `Some` in order to increase the number of fields we tell serde to serialize. // 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 optional_field_idents = Vec::with_capacity(event_fields.len());
let mut try_from_field_values: Vec<TokenStream> = Vec::with_capacity(event_fields.len());
let mut serialize_field_calls: Vec<TokenStream> = Vec::with_capacity(event_fields.len()); let mut serialize_field_calls: Vec<TokenStream> = Vec::with_capacity(event_fields.len());
for field in event_fields { for field in event_fields {
@ -162,40 +161,6 @@ impl ToTokens for RumaEvent {
let span = field.span(); 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")] // Does the same thing as #[serde(skip_serializing_if = "Option::is_none")]
let serialize_field_call = if is_option(&field.ty) { let serialize_field_call = if is_option(&field.ty) {
optional_field_idents.push(ident.clone()); optional_field_idents.push(ident.clone());
@ -341,23 +306,13 @@ impl ToTokens for RumaEvent {
let output = quote!( let output = quote!(
#(#attrs)* #(#attrs)*
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug, ruma_events_macros::FromRaw)]
pub struct #name { pub struct #name {
#(#stripped_fields),* #(#stripped_fields),*
} }
#content #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_event_result_compatible_for_content
impl serde::Serialize for #name { impl serde::Serialize for #name {

View File

@ -34,9 +34,11 @@ extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::ToTokens; 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 gen;
mod parse; mod parse;
@ -132,3 +134,14 @@ pub fn ruma_event(input: TokenStream) -> TokenStream {
ruma_event.into_token_stream().into() 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()
}

View File

@ -1,5 +1,6 @@
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::HashMap,
convert::{Infallible, TryFrom}, convert::{Infallible, TryFrom},
fmt::{Debug, Display, Formatter, Result as FmtResult}, fmt::{Debug, Display, Formatter, Result as FmtResult},
}; };
@ -103,6 +104,25 @@ pub trait TryFromRaw: Sized {
fn try_from_raw(_: Self::Raw) -> Result<Self, Self::Err>; fn try_from_raw(_: Self::Raw) -> Result<Self, Self::Err>;
} }
impl FromRaw for serde_json::Value {
type Raw = Self;
fn from_raw(raw: Self) -> Self {
raw
}
}
impl<K, V, S> FromRaw for HashMap<K, V, S>
where
Self: DeserializeOwned,
{
type Raw = Self;
fn from_raw(raw: Self) -> Self {
raw
}
}
impl<T: FromRaw> TryFromRaw for T { impl<T: FromRaw> TryFromRaw for T {
type Raw = <T as FromRaw>::Raw; type Raw = <T as FromRaw>::Raw;
type Err = Infallible; type Err = Infallible;
@ -472,7 +492,7 @@ mod type_alias {
/// ///
/// A mapping of `UserId`'s to a collection of `RoomId`'s which are considered /// A mapping of `UserId`'s to a collection of `RoomId`'s which are considered
/// *direct* for that particular user. /// *direct* for that particular user.
std::collections::HashMap<ruma_identifiers::UserId, Vec<ruma_identifiers::RoomId>> HashMap<ruma_identifiers::UserId, Vec<ruma_identifiers::RoomId>>
} }
} }
} }

View File

@ -1,4 +1,4 @@
use std::{convert::Infallible, fmt::Display}; use std::{collections::HashMap, convert::Infallible, fmt::Display};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
@ -27,6 +27,25 @@ pub trait TryFromRaw: Sized {
fn try_from_raw(_: Self::Raw) -> Result<Self, Self::Err>; fn try_from_raw(_: Self::Raw) -> Result<Self, Self::Err>;
} }
impl FromRaw for serde_json::Value {
type Raw = Self;
fn from_raw(raw: Self) -> Self {
raw
}
}
impl<K, V, S> FromRaw for HashMap<K, V, S>
where
Self: DeserializeOwned,
{
type Raw = Self;
fn from_raw(raw: Self) -> Self {
raw
}
}
impl<T: FromRaw> TryFromRaw for T { impl<T: FromRaw> TryFromRaw for T {
type Raw = <T as FromRaw>::Raw; type Raw = <T as FromRaw>::Raw;
type Err = Infallible; type Err = Infallible;

View File

@ -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. /// A basic event.
pub trait Event: Debug + Serialize + Sized + TryFromRaw { pub trait Event: Debug + Serialize + Sized + TryFromRaw {
/// The type of this event's `content` field. /// The type of this event's `content` field.