From a5834ea192dc9e6107767c8112f301c06c2998cb Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Wed, 19 Jun 2019 15:40:29 -0700 Subject: [PATCH] Combine and sort common and custom fields using a Vec. --- src/gen.rs | 195 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 124 insertions(+), 71 deletions(-) diff --git a/src/gen.rs b/src/gen.rs index c84cb45f..42f807ef 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -2,7 +2,12 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; -use syn::{Attribute, Field, Ident}; +use syn::{ + parse::{self, Parse, ParseStream}, + parse_quote, + punctuated::Punctuated, + Attribute, Field, Ident, Token, +}; use crate::parse::{EventKind, RumaEventInput}; @@ -14,77 +19,44 @@ pub struct RumaEvent { /// The name of the type of the event's `content` field. content_name: Ident, - /// Additional named struct fields in the top level event struct. - fields: Option>, + /// Struct fields of the event. + fields: Vec, /// The kind of event. + #[allow(dead_code)] kind: EventKind, /// The name of the event. name: Ident, } -impl RumaEvent { - /// Fills in the event's struct definition with fields common to all basic events. - fn common_basic_event_fields(&self) -> TokenStream { - let content_name = &self.content_name; - - quote! { - /// The event's content. - pub content: #content_name, - } - } - - /// Fills in the event's struct definition with fields common to all room events. - fn common_room_event_fields(&self) -> TokenStream { - let common_basic_event_fields = self.common_basic_event_fields(); - - quote! { - #common_basic_event_fields - - /// The unique identifier for the event. - pub event_id: ruma_identifiers::EventId, - - /// Timestamp (milliseconds since the UNIX epoch) on originating homeserver when this - /// event was sent. - pub origin_server_ts: js_int::UInt, - - /// The unique identifier for the room associated with this event. - pub room_id: Option, - - /// Additional key-value pairs not signed by the homeserver. - pub unsigned: Option, - - /// The unique identifier for the user who sent this event. - pub sender: ruma_identifiers::UserId, - } - } - - /// Fills in the event's struct definition with fields common to all state events. - fn common_state_event_fields(&self) -> TokenStream { - let content_name = &self.content_name; - let common_room_event_fields = self.common_room_event_fields(); - - quote! { - #common_room_event_fields - - /// The previous content for this state key, if any. - pub prev_content: Option<#content_name>, - - /// A key that determines which piece of room state the event represents. - pub state_key: String, - } - } -} - impl From for RumaEvent { fn from(input: RumaEventInput) -> Self { + let kind = input.kind; + let name = input.name; + let content_name = Ident::new(&format!("{}Content", &name), Span::call_site()); + + let mut fields = match kind { + EventKind::Event => { + populate_event_fields(content_name.clone(), input.fields.unwrap_or_else(Vec::new)) + } + EventKind::RoomEvent => populate_room_event_fields( + content_name.clone(), + input.fields.unwrap_or_else(Vec::new), + ), + EventKind::StateEvent => { + populate_state_fields(content_name.clone(), input.fields.unwrap_or_else(Vec::new)) + } + }; + + fields.sort_unstable_by_key(|field| field.ident.clone().unwrap()); + Self { attrs: input.attrs, - content_name: Ident::new(&format!("{}Content", input.name), Span::call_site()), - fields: input.fields, - kind: input.kind, - name: input.name, + content_name, + fields, + kind, + name, } } } @@ -94,16 +66,7 @@ impl ToTokens for RumaEvent { let attrs = &self.attrs; let content_name = &self.content_name; - let common_event_fields = match self.kind { - EventKind::Event => self.common_basic_event_fields(), - EventKind::RoomEvent => self.common_room_event_fields(), - EventKind::StateEvent => self.common_state_event_fields(), - }; - - let event_fields = match &self.fields { - Some(fields) => fields.clone(), - None => vec![], - }; + let event_fields = &self.fields; let name = &self.name; @@ -113,7 +76,6 @@ impl ToTokens for RumaEvent { #(#attrs),* #[derive(Clone, Debug)] pub struct #name { - #common_event_fields #(#event_fields),* } @@ -126,3 +88,94 @@ impl ToTokens for RumaEvent { output.to_tokens(tokens); } } + +/// Fills in the event's struct definition with fields common to all basic events. +fn populate_event_fields(content_name: Ident, mut fields: Vec) -> Vec { + let punctuated_fields: Punctuated = parse_quote! { + /// The event's content. + pub content: #content_name, + }; + + let mut additional_fields = Vec::with_capacity(punctuated_fields.len()); + + for punctuated_field in punctuated_fields { + additional_fields.push(punctuated_field.field); + } + + fields.extend(additional_fields); + + fields +} + +/// Fills in the event's struct definition with fields common to all room events. +fn populate_room_event_fields(content_name: Ident, fields: Vec) -> Vec { + let mut fields = populate_event_fields(content_name, fields); + + let punctuated_fields: Punctuated = parse_quote! { + /// The unique identifier for the event. + pub event_id: ruma_identifiers::EventId, + + /// Timestamp (milliseconds since the UNIX epoch) on originating homeserver when this + /// event was sent. + pub origin_server_ts: js_int::UInt, + + /// The unique identifier for the room associated with this event. + pub room_id: Option, + + /// Additional key-value pairs not signed by the homeserver. + pub unsigned: Option, + + /// The unique identifier for the user who sent this event. + pub sender: ruma_identifiers::UserId, + }; + + let mut additional_fields = Vec::with_capacity(punctuated_fields.len()); + + for punctuated_field in punctuated_fields { + additional_fields.push(punctuated_field.field); + } + + fields.extend(additional_fields); + + fields +} + +/// Fills in the event's struct definition with fields common to all state events. +fn populate_state_fields(content_name: Ident, fields: Vec) -> Vec { + let mut fields = populate_room_event_fields(content_name.clone(), fields); + + let punctuated_fields: Punctuated = parse_quote! { + /// The previous content for this state key, if any. + pub prev_content: Option<#content_name>, + + /// A key that determines which piece of room state the event represents. + pub state_key: String, + }; + + let mut additional_fields = Vec::with_capacity(punctuated_fields.len()); + + for punctuated_field in punctuated_fields { + additional_fields.push(punctuated_field.field); + } + + fields.extend(additional_fields); + + fields +} + +/// A wrapper around `syn::Field` that makes it possible to parse `Punctuated` +/// from a `TokenStream`. +/// +/// See https://github.com/dtolnay/syn/issues/651 for more context. +struct ParsableNamedField { + /// The wrapped `Field`. + pub field: Field, +} + +impl Parse for ParsableNamedField { + fn parse(input: ParseStream<'_>) -> parse::Result { + let field = Field::parse_named(input)?; + + Ok(Self { field }) + } +}