From 5661fd610c9eada348c0587b24871931c6256cf5 Mon Sep 17 00:00:00 2001 From: Leo Valais Date: Wed, 17 Apr 2024 18:31:02 +0200 Subject: [PATCH 1/6] editoast: add some ModelV2 declaration ToTokens types - ModelImpl: `impl Model for M` - RowDecl: `struct MRow` - ChangesetDecl: `struct MChangeset` --- editoast/editoast_derive/src/modelv2/args.rs | 8 ++ .../editoast_derive/src/modelv2/codegen.rs | 87 +++++++++++-------- .../src/modelv2/codegen/changeset_decl.rs | 52 +++++++++++ .../src/modelv2/codegen/model_impl.rs | 24 +++++ .../src/modelv2/codegen/row_decl.rs | 49 +++++++++++ .../editoast_derive/src/modelv2/config.rs | 13 +-- 6 files changed, 190 insertions(+), 43 deletions(-) create mode 100644 editoast/editoast_derive/src/modelv2/codegen/changeset_decl.rs create mode 100644 editoast/editoast_derive/src/modelv2/codegen/model_impl.rs create mode 100644 editoast/editoast_derive/src/modelv2/codegen/row_decl.rs diff --git a/editoast/editoast_derive/src/modelv2/args.rs b/editoast/editoast_derive/src/modelv2/args.rs index 667a5551979..f11938834ed 100644 --- a/editoast/editoast_derive/src/modelv2/args.rs +++ b/editoast/editoast_derive/src/modelv2/args.rs @@ -68,4 +68,12 @@ impl GeneratedTypeArgs { pub fn ident(&self) -> syn::Ident { syn::Ident::new(self.type_name.as_ref().unwrap(), Span::call_site()) } + + pub fn visibility(&self) -> syn::Visibility { + if self.public { + syn::Visibility::Public(Default::default()) + } else { + syn::Visibility::Inherited + } + } } diff --git a/editoast/editoast_derive/src/modelv2/codegen.rs b/editoast/editoast_derive/src/modelv2/codegen.rs index 93b14c5dbc6..64cc081a7c0 100644 --- a/editoast/editoast_derive/src/modelv2/codegen.rs +++ b/editoast/editoast_derive/src/modelv2/codegen.rs @@ -1,6 +1,16 @@ +mod changeset_decl; +mod model_impl; +mod row_decl; + use proc_macro2::{Span, TokenStream}; use quote::quote; +use crate::modelv2::codegen::changeset_decl::ChangesetDecl; +use crate::modelv2::codegen::changeset_decl::ChangesetFieldDecl; +use crate::modelv2::codegen::model_impl::ModelImpl; +use crate::modelv2::codegen::row_decl::RowDecl; +use crate::modelv2::codegen::row_decl::RowFieldDecl; + use super::utils::np; use super::Identifier; use super::ModelConfig; @@ -9,47 +19,50 @@ impl ModelConfig { pub fn make_model_decl(&self, vis: &syn::Visibility) -> TokenStream { let model = &self.model; let table = &self.table; - let np!(field, ty, column): np!(vec3) = self - .iter_fields() - .map(|field| np!(&field.ident, field.transform_type(), &field.column)) - .unzip(); - let np!(cs_field, cs_ty, cs_column): np!(vec3) = self - .iter_fields() - .filter(|f| !self.is_primary(f)) - .map(|field| np!(&field.ident, field.transform_type(), &field.column)) - .unzip(); - let cs_ident = self.changeset.ident(); - let cs_derive = &self.changeset.derive; - let cs_pub = if self.changeset.public { - quote! { pub } - } else { - quote! {} + + let model_impl = ModelImpl { + model: model.clone(), + row: self.row.ident(), + changeset: self.changeset.ident(), }; - let row_ident = self.row.ident(); - let row_derive = &self.row.derive; - let row_pub = if self.row.public { - quote! { pub } - } else { - quote! {} + + let row_decl = RowDecl { + vis: vis.clone(), + ident: self.row.ident(), + table: table.clone(), + additional_derives: self.row.derive.clone(), + fields: self + .iter_fields() + .map(|field| RowFieldDecl { + vis: self.row.visibility(), + name: field.ident.clone(), + ty: field.transform_type(), + column: field.column.clone(), + }) + .collect(), }; - quote! { - #[automatically_derived] - impl crate::modelsv2::Model for #model { - type Row = #row_ident; - type Changeset = #cs_ident; - } - #[derive(Queryable, QueryableByName, #(#row_derive),*)] - #[diesel(table_name = #table)] - #vis struct #row_ident { - #(#[diesel(column_name = #column)] #row_pub #field: #ty),* - } + let cs_decl = ChangesetDecl { + vis: vis.clone(), + ident: self.changeset.ident(), + table: table.clone(), + additional_derives: self.changeset.derive.clone(), + fields: self + .iter_fields() + .filter(|f| !self.is_primary(f)) + .map(|field| ChangesetFieldDecl { + vis: self.changeset.visibility(), + name: field.ident.clone(), + ty: field.transform_type(), + column: field.column.clone(), + }) + .collect(), + }; - #[derive(Default, Queryable, AsChangeset, Insertable, #(#cs_derive),*)] - #[diesel(table_name = #table)] - #vis struct #cs_ident { - #(#[diesel(deserialize_as = #cs_ty, column_name = #cs_column)] #cs_pub #cs_field: Option<#cs_ty>),* - } + quote! { + #model_impl + #row_decl + #cs_decl } } diff --git a/editoast/editoast_derive/src/modelv2/codegen/changeset_decl.rs b/editoast/editoast_derive/src/modelv2/codegen/changeset_decl.rs new file mode 100644 index 00000000000..910f0bbfaa1 --- /dev/null +++ b/editoast/editoast_derive/src/modelv2/codegen/changeset_decl.rs @@ -0,0 +1,52 @@ +use super::np; +use quote::quote; +use quote::ToTokens; + +pub(crate) struct ChangesetDecl { + pub(super) vis: syn::Visibility, + pub(super) ident: syn::Ident, + pub(super) table: syn::Path, + pub(super) additional_derives: darling::util::PathList, + pub(super) fields: Vec, +} + +pub(crate) struct ChangesetFieldDecl { + pub(super) vis: syn::Visibility, + pub(super) name: syn::Ident, + pub(super) ty: syn::Type, + pub(super) column: String, +} + +impl ToTokens for ChangesetDecl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { + vis, + ident, + table, + additional_derives, + fields, + } = self; + let np!(field_column, field_vis, field_name, field_type): np!(vec4) = fields + .iter() + .map(|field| { + let ChangesetFieldDecl { + vis, + name, + ty, + column, + } = field; + np!(column, vis, name, ty) + }) + .unzip(); + tokens.extend(quote! { + #[derive(Default, Queryable, AsChangeset, Insertable, #(#additional_derives),*)] + #[diesel(table_name = #table)] + #vis struct #ident { + #( + #[diesel(deserialize_as = #field_type, column_name = #field_column)] + #field_vis #field_name: Option<#field_type> + ),* + } + }); + } +} diff --git a/editoast/editoast_derive/src/modelv2/codegen/model_impl.rs b/editoast/editoast_derive/src/modelv2/codegen/model_impl.rs new file mode 100644 index 00000000000..f2827b0676f --- /dev/null +++ b/editoast/editoast_derive/src/modelv2/codegen/model_impl.rs @@ -0,0 +1,24 @@ +use quote::{quote, ToTokens}; + +pub(crate) struct ModelImpl { + pub(super) model: syn::Ident, + pub(super) row: syn::Ident, + pub(super) changeset: syn::Ident, +} + +impl ToTokens for ModelImpl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { + model, + row, + changeset, + } = self; + tokens.extend(quote! { + #[automatically_derived] + impl crate::modelsv2::Model for #model { + type Row = #row; + type Changeset = #changeset; + } + }); + } +} diff --git a/editoast/editoast_derive/src/modelv2/codegen/row_decl.rs b/editoast/editoast_derive/src/modelv2/codegen/row_decl.rs new file mode 100644 index 00000000000..8e37f1cdd06 --- /dev/null +++ b/editoast/editoast_derive/src/modelv2/codegen/row_decl.rs @@ -0,0 +1,49 @@ +use super::np; +use quote::quote; +use quote::ToTokens; + +pub(crate) struct RowDecl { + pub(super) vis: syn::Visibility, + pub(super) ident: syn::Ident, + pub(super) table: syn::Path, + pub(super) additional_derives: darling::util::PathList, + pub(super) fields: Vec, +} + +pub(crate) struct RowFieldDecl { + pub(super) vis: syn::Visibility, + pub(super) name: syn::Ident, + pub(super) ty: syn::Type, + pub(super) column: String, +} + +impl ToTokens for RowDecl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { + vis, + ident, + table, + additional_derives, + fields, + } = self; + let np!(field_column, field_vis, field_name, field_type): np!(vec4) = fields + .iter() + .map(|field| { + let RowFieldDecl { + vis, + name, + ty, + column, + } = field; + np!(column, vis, name, ty) + }) + .unzip(); + tokens.extend(quote! { + #[derive(Queryable, QueryableByName, #(#additional_derives),*)] + #[diesel(table_name = #table)] + #vis struct #ident { + #(#[diesel(column_name = #field_column)] #field_vis #field_name: #field_type),* + } + }); + } +} diff --git a/editoast/editoast_derive/src/modelv2/config.rs b/editoast/editoast_derive/src/modelv2/config.rs index 7ec6dc5e507..818e29bc690 100644 --- a/editoast/editoast_derive/src/modelv2/config.rs +++ b/editoast/editoast_derive/src/modelv2/config.rs @@ -5,6 +5,7 @@ use std::{ use proc_macro2::TokenStream; use quote::quote; +use syn::parse_quote; use super::{args::GeneratedTypeArgs, identifier::Identifier}; @@ -140,15 +141,15 @@ impl ModelField { } } - pub fn transform_type(&self) -> TokenStream { + pub fn transform_type(&self) -> syn::Type { let ty = &self.ty; match self.transform { - Some(FieldTransformation::Remote(ref ty)) => quote! { #ty }, - Some(FieldTransformation::Json) => quote! { diesel_json::Json<#ty> }, + Some(FieldTransformation::Remote(ref ty)) => parse_quote! { #ty }, + Some(FieldTransformation::Json) => parse_quote! { diesel_json::Json<#ty> }, Some(FieldTransformation::Geo) => unimplemented!("to be designed"), - Some(FieldTransformation::ToString) => quote! { String }, - Some(FieldTransformation::ToEnum(_)) => quote! { i16 }, - None => quote! { #ty }, + Some(FieldTransformation::ToString) => parse_quote! { String }, + Some(FieldTransformation::ToEnum(_)) => parse_quote! { i16 }, + None => ty.clone(), } } } From a0855cd45c3740317b0b9cea969ef76d535e338b Mon Sep 17 00:00:00 2001 From: Leo Valais Date: Wed, 17 Apr 2024 18:52:39 +0200 Subject: [PATCH 2/6] editoast: better codegen exposition in ModelConfig API --- editoast/editoast_derive/src/modelv2.rs | 12 +++++-- .../editoast_derive/src/modelv2/codegen.rs | 35 ++++++++----------- .../editoast_derive/src/modelv2/config.rs | 1 + .../editoast_derive/src/modelv2/parsing.rs | 7 +++- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/editoast/editoast_derive/src/modelv2.rs b/editoast/editoast_derive/src/modelv2.rs index b36d9c4a723..6fb63fd280a 100644 --- a/editoast/editoast_derive/src/modelv2.rs +++ b/editoast/editoast_derive/src/modelv2.rs @@ -19,10 +19,13 @@ pub fn model(input: &DeriveInput) -> Result { let model_name = &input.ident; let model_vis = &input.vis; let options = ModelArgs::from_derive_input(input)?; - let config = ModelConfig::from_macro_args(options, model_name.clone())?; + let config = ModelConfig::from_macro_args(options, model_name.clone(), model_vis.clone())?; + + let model_impl = config.model_impl(); + let row_decl = config.row_decl(); + let changeset_decl = config.changeset_decl(); let identifiers_impls = config.make_identifiers_impls(); - let model_decl = config.make_model_decl(model_vis); let from_impls = config.make_from_impls(); let cs_builder = config.make_builder(true); @@ -31,8 +34,11 @@ pub fn model(input: &DeriveInput) -> Result { let model_impls = config.make_model_traits_impl(); Ok(quote! { + #model_impl + #row_decl + #changeset_decl + #identifiers_impls - #model_decl #from_impls #cs_builder #patch_builder diff --git a/editoast/editoast_derive/src/modelv2/codegen.rs b/editoast/editoast_derive/src/modelv2/codegen.rs index 64cc081a7c0..da3c1ead680 100644 --- a/editoast/editoast_derive/src/modelv2/codegen.rs +++ b/editoast/editoast_derive/src/modelv2/codegen.rs @@ -16,20 +16,19 @@ use super::Identifier; use super::ModelConfig; impl ModelConfig { - pub fn make_model_decl(&self, vis: &syn::Visibility) -> TokenStream { - let model = &self.model; - let table = &self.table; - - let model_impl = ModelImpl { - model: model.clone(), + pub(crate) fn model_impl(&self) -> ModelImpl { + ModelImpl { + model: self.model.clone(), row: self.row.ident(), changeset: self.changeset.ident(), - }; + } + } - let row_decl = RowDecl { - vis: vis.clone(), + pub(crate) fn row_decl(&self) -> RowDecl { + RowDecl { + vis: self.visibility.clone(), ident: self.row.ident(), - table: table.clone(), + table: self.table.clone(), additional_derives: self.row.derive.clone(), fields: self .iter_fields() @@ -40,12 +39,14 @@ impl ModelConfig { column: field.column.clone(), }) .collect(), - }; + } + } - let cs_decl = ChangesetDecl { - vis: vis.clone(), + pub(crate) fn changeset_decl(&self) -> ChangesetDecl { + ChangesetDecl { + vis: self.visibility.clone(), ident: self.changeset.ident(), - table: table.clone(), + table: self.table.clone(), additional_derives: self.changeset.derive.clone(), fields: self .iter_fields() @@ -57,12 +58,6 @@ impl ModelConfig { column: field.column.clone(), }) .collect(), - }; - - quote! { - #model_impl - #row_decl - #cs_decl } } diff --git a/editoast/editoast_derive/src/modelv2/config.rs b/editoast/editoast_derive/src/modelv2/config.rs index 818e29bc690..077e73a79f8 100644 --- a/editoast/editoast_derive/src/modelv2/config.rs +++ b/editoast/editoast_derive/src/modelv2/config.rs @@ -12,6 +12,7 @@ use super::{args::GeneratedTypeArgs, identifier::Identifier}; #[derive(Debug, PartialEq)] pub struct ModelConfig { pub model: syn::Ident, + pub visibility: syn::Visibility, pub table: syn::Path, pub fields: Fields, pub row: GeneratedTypeArgs, diff --git a/editoast/editoast_derive/src/modelv2/parsing.rs b/editoast/editoast_derive/src/modelv2/parsing.rs index fca7e960010..35789f4acef 100644 --- a/editoast/editoast_derive/src/modelv2/parsing.rs +++ b/editoast/editoast_derive/src/modelv2/parsing.rs @@ -10,7 +10,11 @@ use super::{ }; impl ModelConfig { - pub fn from_macro_args(options: ModelArgs, model_name: syn::Ident) -> darling::Result { + pub fn from_macro_args( + options: ModelArgs, + model_name: syn::Ident, + visibility: syn::Visibility, + ) -> darling::Result { let row = GeneratedTypeArgs { type_name: options.row.type_name.or(Some(format!("{}Row", model_name))), ..options.row @@ -104,6 +108,7 @@ impl ModelConfig { Ok(Self { model: model_name, + visibility, table: options.table, fields, identifiers, From ceec03116eee58dc336c1880942348ddade0053b Mon Sep 17 00:00:00 2001 From: Leo Valais Date: Wed, 17 Apr 2024 19:43:34 +0200 Subject: [PATCH 3/6] editoast: add ToTokens definiton for Identifiable and PreferredId impls --- editoast/editoast_derive/src/modelv2.rs | 8 +++-- .../editoast_derive/src/modelv2/codegen.rs | 34 +++++++++++-------- .../src/modelv2/codegen/identifiable_impl.rs | 22 ++++++++++++ .../src/modelv2/codegen/preferred_id_impl.rs | 17 ++++++++++ .../editoast_derive/src/modelv2/config.rs | 9 ++--- 5 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 editoast/editoast_derive/src/modelv2/codegen/identifiable_impl.rs create mode 100644 editoast/editoast_derive/src/modelv2/codegen/preferred_id_impl.rs diff --git a/editoast/editoast_derive/src/modelv2.rs b/editoast/editoast_derive/src/modelv2.rs index 6fb63fd280a..9d0f0a59cec 100644 --- a/editoast/editoast_derive/src/modelv2.rs +++ b/editoast/editoast_derive/src/modelv2.rs @@ -25,7 +25,9 @@ pub fn model(input: &DeriveInput) -> Result { let row_decl = config.row_decl(); let changeset_decl = config.changeset_decl(); - let identifiers_impls = config.make_identifiers_impls(); + let identifiable_impls = config.identifiable_impls(); + let preferred_id_impl = config.preferred_id_impl(); + let from_impls = config.make_from_impls(); let cs_builder = config.make_builder(true); @@ -38,7 +40,9 @@ pub fn model(input: &DeriveInput) -> Result { #row_decl #changeset_decl - #identifiers_impls + #(#identifiable_impls)* + #preferred_id_impl + #from_impls #cs_builder #patch_builder diff --git a/editoast/editoast_derive/src/modelv2/codegen.rs b/editoast/editoast_derive/src/modelv2/codegen.rs index da3c1ead680..c1a3ebee11c 100644 --- a/editoast/editoast_derive/src/modelv2/codegen.rs +++ b/editoast/editoast_derive/src/modelv2/codegen.rs @@ -1,5 +1,7 @@ mod changeset_decl; +mod identifiable_impl; mod model_impl; +mod preferred_id_impl; mod row_decl; use proc_macro2::{Span, TokenStream}; @@ -11,6 +13,9 @@ use crate::modelv2::codegen::model_impl::ModelImpl; use crate::modelv2::codegen::row_decl::RowDecl; use crate::modelv2::codegen::row_decl::RowFieldDecl; +use self::identifiable_impl::IdentifiableImpl; +use self::preferred_id_impl::PreferredIdImpl; + use super::utils::np; use super::Identifier; use super::ModelConfig; @@ -120,22 +125,21 @@ impl ModelConfig { } } - pub fn make_identifiers_impls(&self) -> TokenStream { - let model = &self.model; - let (idents, ty): (Vec<_>, Vec<_>) = self - .identifiers + pub(crate) fn identifiable_impls(&self) -> Vec { + self.identifiers .iter() - .map(|id| (id.get_idents(), id.type_expr(self))) - .unzip(); - let prefer_ty = self.preferred_identifier.type_expr(self); - quote! { - #(#[automatically_derived] impl crate::models::Identifiable<#ty> for #model { - fn get_id(&self) -> #ty { - (#(self.#idents.clone()),*) - } - })* - #[automatically_derived] - impl crate::models::PreferredId<#prefer_ty> for #model {} + .map(|identifier| IdentifiableImpl { + model: self.model.clone(), + ty: identifier.type_expr(self), + fields: identifier.get_idents(), + }) + .collect() + } + + pub(crate) fn preferred_id_impl(&self) -> PreferredIdImpl { + PreferredIdImpl { + model: self.model.clone(), + ty: self.preferred_identifier.type_expr(self), } } diff --git a/editoast/editoast_derive/src/modelv2/codegen/identifiable_impl.rs b/editoast/editoast_derive/src/modelv2/codegen/identifiable_impl.rs new file mode 100644 index 00000000000..6cd880fec44 --- /dev/null +++ b/editoast/editoast_derive/src/modelv2/codegen/identifiable_impl.rs @@ -0,0 +1,22 @@ +use quote::quote; +use quote::ToTokens; + +pub(crate) struct IdentifiableImpl { + pub(super) model: syn::Ident, + pub(super) ty: syn::Type, + pub(super) fields: Vec, +} + +impl ToTokens for IdentifiableImpl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { model, ty, fields } = self; + tokens.extend(quote! { + #[automatically_derived] + impl crate::models::Identifiable<#ty> for #model { + fn get_id(&self) -> #ty { + (#(self.#fields.clone()),*) + } + } + }); + } +} diff --git a/editoast/editoast_derive/src/modelv2/codegen/preferred_id_impl.rs b/editoast/editoast_derive/src/modelv2/codegen/preferred_id_impl.rs new file mode 100644 index 00000000000..c07a8154928 --- /dev/null +++ b/editoast/editoast_derive/src/modelv2/codegen/preferred_id_impl.rs @@ -0,0 +1,17 @@ +use quote::quote; +use quote::ToTokens; + +pub(crate) struct PreferredIdImpl { + pub(super) model: syn::Ident, + pub(super) ty: syn::Type, +} + +impl ToTokens for PreferredIdImpl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { model, ty } = self; + tokens.extend(quote! { + #[automatically_derived] + impl crate::models::PreferredId<#ty> for #model {} + }); + } +} diff --git a/editoast/editoast_derive/src/modelv2/config.rs b/editoast/editoast_derive/src/modelv2/config.rs index 077e73a79f8..c2e1cbab41e 100644 --- a/editoast/editoast_derive/src/modelv2/config.rs +++ b/editoast/editoast_derive/src/modelv2/config.rs @@ -75,17 +75,14 @@ impl Identifier { } } - pub fn type_expr(&self, config: &ModelConfig) -> TokenStream { + pub fn type_expr(&self, config: &ModelConfig) -> syn::Type { match self { - Self::Field(_) => { - let ty = &self.get_field(config).unwrap().ty; - quote! { #ty } - } + Self::Field(_) => self.get_field(config).unwrap().ty.clone(), Self::Compound(idents) => { let ty = idents .iter() .map(|ident| &config.fields.get(ident).unwrap().ty); - quote! { (#(#ty),*) } + syn::parse_quote! { (#(#ty),*) } // tuple type } } } From 78cf64e709b3adad51afd2f6090e68dfab494c89 Mon Sep 17 00:00:00 2001 From: Leo Valais Date: Wed, 17 Apr 2024 20:07:29 +0200 Subject: [PATCH 4/6] editoast: add ToToken ModelV2 definition ModelFromRowImpl --- editoast/editoast_derive/src/modelv2.rs | 3 ++ .../editoast_derive/src/modelv2/codegen.rs | 27 ++++++-------- .../modelv2/codegen/model_from_row_impl.rs | 35 +++++++++++++++++++ .../editoast_derive/src/modelv2/config.rs | 12 +++---- 4 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 editoast/editoast_derive/src/modelv2/codegen/model_from_row_impl.rs diff --git a/editoast/editoast_derive/src/modelv2.rs b/editoast/editoast_derive/src/modelv2.rs index 9d0f0a59cec..88c21e4fcce 100644 --- a/editoast/editoast_derive/src/modelv2.rs +++ b/editoast/editoast_derive/src/modelv2.rs @@ -28,6 +28,7 @@ pub fn model(input: &DeriveInput) -> Result { let identifiable_impls = config.identifiable_impls(); let preferred_id_impl = config.preferred_id_impl(); + let model_from_row_impl = config.model_from_row_impl(); let from_impls = config.make_from_impls(); let cs_builder = config.make_builder(true); @@ -43,6 +44,8 @@ pub fn model(input: &DeriveInput) -> Result { #(#identifiable_impls)* #preferred_id_impl + #model_from_row_impl + #from_impls #cs_builder #patch_builder diff --git a/editoast/editoast_derive/src/modelv2/codegen.rs b/editoast/editoast_derive/src/modelv2/codegen.rs index c1a3ebee11c..fc6db59b1c8 100644 --- a/editoast/editoast_derive/src/modelv2/codegen.rs +++ b/editoast/editoast_derive/src/modelv2/codegen.rs @@ -1,5 +1,6 @@ mod changeset_decl; mod identifiable_impl; +mod model_from_row_impl; mod model_impl; mod preferred_id_impl; mod row_decl; @@ -14,6 +15,7 @@ use crate::modelv2::codegen::row_decl::RowDecl; use crate::modelv2::codegen::row_decl::RowFieldDecl; use self::identifiable_impl::IdentifiableImpl; +use self::model_from_row_impl::ModelFromRowImpl; use self::preferred_id_impl::PreferredIdImpl; use super::utils::np; @@ -143,15 +145,16 @@ impl ModelConfig { } } + pub(crate) fn model_from_row_impl(&self) -> ModelFromRowImpl { + ModelFromRowImpl { + model: self.model.clone(), + row: self.row.ident(), + fields: self.fields.clone(), + } + } + pub fn make_from_impls(&self) -> TokenStream { let model = &self.model; - let (row_field, row_value): (Vec<_>, Vec<_>) = self - .iter_fields() - .map(|field| { - let ident = &field.ident; - (ident, field.from_transformed(quote! { row.#ident })) - }) - .unzip(); let (cs_field, cs_value): (Vec<_>, Vec<_>) = self .iter_fields() .filter(|f| !self.is_primary(f)) @@ -160,18 +163,8 @@ impl ModelConfig { (ident, field.into_transformed(quote! { model.#ident })) }) .unzip(); - let row_ident = self.row.ident(); let cs_ident = self.changeset.ident(); quote! { - #[automatically_derived] - impl From<#row_ident> for #model { - fn from(row: #row_ident) -> Self { - Self { - #( #row_field: #row_value ),* - } - } - } - #[automatically_derived] impl From<#model> for #cs_ident { fn from(model: #model) -> Self { diff --git a/editoast/editoast_derive/src/modelv2/codegen/model_from_row_impl.rs b/editoast/editoast_derive/src/modelv2/codegen/model_from_row_impl.rs new file mode 100644 index 00000000000..b0f4ea8f33e --- /dev/null +++ b/editoast/editoast_derive/src/modelv2/codegen/model_from_row_impl.rs @@ -0,0 +1,35 @@ +use crate::modelv2::utils::np; +use crate::modelv2::ModelField; +use quote::quote; +use quote::ToTokens; +use syn::parse_quote; + +pub(crate) struct ModelFromRowImpl { + pub(super) model: syn::Ident, + pub(super) row: syn::Ident, + pub(super) fields: Vec, +} + +impl ToTokens for ModelFromRowImpl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { model, row, fields } = self; + let np!(field, value): np!(vec2) = fields + .iter() + .map(|field| { + let ident = &field.ident; + let expr = field.from_transformed(parse_quote! { row.#ident }); + (ident, expr) + }) + .unzip(); + tokens.extend(quote! { + #[automatically_derived] + impl From<#row> for #model { + fn from(row: #row) -> Self { + Self { + #( #field: #value ),* + } + } + } + }); + } +} diff --git a/editoast/editoast_derive/src/modelv2/config.rs b/editoast/editoast_derive/src/modelv2/config.rs index c2e1cbab41e..021040d287b 100644 --- a/editoast/editoast_derive/src/modelv2/config.rs +++ b/editoast/editoast_derive/src/modelv2/config.rs @@ -126,16 +126,16 @@ impl ModelField { } #[allow(clippy::wrong_self_convention)] - pub fn from_transformed(&self, expr: TokenStream) -> TokenStream { + pub fn from_transformed(&self, expr: syn::Expr) -> syn::Expr { match self.transform { - Some(FieldTransformation::Remote(_)) => quote! { #expr.into() }, - Some(FieldTransformation::Json) => quote! { #expr.0 }, + Some(FieldTransformation::Remote(_)) => parse_quote! { #expr.into() }, + Some(FieldTransformation::Json) => parse_quote! { #expr.0 }, Some(FieldTransformation::Geo) => unimplemented!("to be designed"), - Some(FieldTransformation::ToString) => quote! { String::from(#expr.parse()) }, + Some(FieldTransformation::ToString) => parse_quote! { String::from(#expr.parse()) }, Some(FieldTransformation::ToEnum(ref ty)) => { - quote! { #ty::from_repr(#expr as usize).expect("Invalid variant repr") } + parse_quote! { #ty::from_repr(#expr as usize).expect("Invalid variant repr") } } - None => quote! { #expr }, + None => parse_quote! { #expr }, } } From ef9250bd3fdb28ac37833fed6ec80de2e8aaa3fd Mon Sep 17 00:00:00 2001 From: Leo Valais Date: Wed, 17 Apr 2024 20:18:49 +0200 Subject: [PATCH 5/6] editoast: add ToToken ModelV2 definition ChangesetFromModelImpl --- editoast/editoast_derive/src/modelv2.rs | 4 +- .../editoast_derive/src/modelv2/codegen.rs | 35 +++++++---------- .../modelv2/codegen/changeset_from_model.rs | 39 +++++++++++++++++++ .../editoast_derive/src/modelv2/config.rs | 14 +++---- 4 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 editoast/editoast_derive/src/modelv2/codegen/changeset_from_model.rs diff --git a/editoast/editoast_derive/src/modelv2.rs b/editoast/editoast_derive/src/modelv2.rs index 88c21e4fcce..936597ef1ab 100644 --- a/editoast/editoast_derive/src/modelv2.rs +++ b/editoast/editoast_derive/src/modelv2.rs @@ -29,7 +29,7 @@ pub fn model(input: &DeriveInput) -> Result { let preferred_id_impl = config.preferred_id_impl(); let model_from_row_impl = config.model_from_row_impl(); - let from_impls = config.make_from_impls(); + let changeset_from_model_impl = config.changeset_from_model_impl(); let cs_builder = config.make_builder(true); let patch_builder = config.make_builder(false); @@ -45,8 +45,8 @@ pub fn model(input: &DeriveInput) -> Result { #preferred_id_impl #model_from_row_impl + #changeset_from_model_impl - #from_impls #cs_builder #patch_builder #model_impls diff --git a/editoast/editoast_derive/src/modelv2/codegen.rs b/editoast/editoast_derive/src/modelv2/codegen.rs index fc6db59b1c8..1f3d14b7f25 100644 --- a/editoast/editoast_derive/src/modelv2/codegen.rs +++ b/editoast/editoast_derive/src/modelv2/codegen.rs @@ -1,4 +1,5 @@ mod changeset_decl; +mod changeset_from_model; mod identifiable_impl; mod model_from_row_impl; mod model_impl; @@ -7,6 +8,7 @@ mod row_decl; use proc_macro2::{Span, TokenStream}; use quote::quote; +use syn::parse_quote; use crate::modelv2::codegen::changeset_decl::ChangesetDecl; use crate::modelv2::codegen::changeset_decl::ChangesetFieldDecl; @@ -14,6 +16,7 @@ use crate::modelv2::codegen::model_impl::ModelImpl; use crate::modelv2::codegen::row_decl::RowDecl; use crate::modelv2::codegen::row_decl::RowFieldDecl; +use self::changeset_from_model::ChangesetFromModelImpl; use self::identifiable_impl::IdentifiableImpl; use self::model_from_row_impl::ModelFromRowImpl; use self::preferred_id_impl::PreferredIdImpl; @@ -75,7 +78,7 @@ impl ModelConfig { .filter(|field| !field.builder_skip) .map(|field| { let ident = &field.ident; - let expr = field.into_transformed(quote! { #ident }); + let expr = field.into_transformed(parse_quote! { #ident }); let body = if changeset { quote! { self.#ident = Some(#expr) } } else { @@ -153,26 +156,16 @@ impl ModelConfig { } } - pub fn make_from_impls(&self) -> TokenStream { - let model = &self.model; - let (cs_field, cs_value): (Vec<_>, Vec<_>) = self - .iter_fields() - .filter(|f| !self.is_primary(f)) - .map(|field| { - let ident = &field.ident; - (ident, field.into_transformed(quote! { model.#ident })) - }) - .unzip(); - let cs_ident = self.changeset.ident(); - quote! { - #[automatically_derived] - impl From<#model> for #cs_ident { - fn from(model: #model) -> Self { - Self { - #( #cs_field: Some(#cs_value) ),* - } - } - } + pub(crate) fn changeset_from_model_impl(&self) -> ChangesetFromModelImpl { + ChangesetFromModelImpl { + model: self.model.clone(), + changeset: self.changeset.ident(), + fields: self + .fields + .iter() + .filter(|f| !self.is_primary(f)) + .cloned() + .collect(), } } diff --git a/editoast/editoast_derive/src/modelv2/codegen/changeset_from_model.rs b/editoast/editoast_derive/src/modelv2/codegen/changeset_from_model.rs new file mode 100644 index 00000000000..4c185a722dc --- /dev/null +++ b/editoast/editoast_derive/src/modelv2/codegen/changeset_from_model.rs @@ -0,0 +1,39 @@ +use crate::modelv2::utils::np; +use crate::modelv2::ModelField; +use quote::quote; +use quote::ToTokens; +use syn::parse_quote; + +pub(crate) struct ChangesetFromModelImpl { + pub(super) model: syn::Ident, + pub(super) changeset: syn::Ident, + pub(super) fields: Vec, +} + +impl ToTokens for ChangesetFromModelImpl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { + model, + changeset, + fields, + } = self; + let np!(field, value): np!(vec2) = fields + .iter() + .map(|field| { + let ident = &field.ident; + let expr = field.into_transformed(parse_quote! { model.#ident }); + (ident, expr) + }) + .unzip(); + tokens.extend(quote! { + #[automatically_derived] + impl From<#model> for #changeset { + fn from(model: #model) -> Self { + Self { + #( #field: Some(#value) ),* + } + } + } + }); + } +} diff --git a/editoast/editoast_derive/src/modelv2/config.rs b/editoast/editoast_derive/src/modelv2/config.rs index 021040d287b..3dbce2395c3 100644 --- a/editoast/editoast_derive/src/modelv2/config.rs +++ b/editoast/editoast_derive/src/modelv2/config.rs @@ -3,8 +3,6 @@ use std::{ ops::{Deref, DerefMut}, }; -use proc_macro2::TokenStream; -use quote::quote; use syn::parse_quote; use super::{args::GeneratedTypeArgs, identifier::Identifier}; @@ -112,16 +110,16 @@ impl ModelConfig { impl ModelField { #[allow(clippy::wrong_self_convention)] - pub fn into_transformed(&self, expr: TokenStream) -> TokenStream { + pub fn into_transformed(&self, expr: syn::Expr) -> syn::Expr { match self.transform { - Some(FieldTransformation::Remote(_)) => quote! { #expr.into() }, - Some(FieldTransformation::Json) => quote! { diesel_json::Json(#expr) }, + Some(FieldTransformation::Remote(_)) => parse_quote! { #expr.into() }, + Some(FieldTransformation::Json) => parse_quote! { diesel_json::Json(#expr) }, Some(FieldTransformation::Geo) => unimplemented!("to be designed"), - Some(FieldTransformation::ToString) => quote! { #expr.to_string() }, + Some(FieldTransformation::ToString) => parse_quote! { #expr.to_string() }, Some(FieldTransformation::ToEnum(_)) => { - quote! { #expr as i16 } + parse_quote! { #expr as i16 } } - None => quote! { #expr }, + None => parse_quote! { #expr }, } } From 0f13129d21ae4b581b60d8ce0df82829aeed5e7f Mon Sep 17 00:00:00 2001 From: Leo Valais Date: Wed, 17 Apr 2024 21:27:46 +0200 Subject: [PATCH 6/6] editoast: add ToToken ModelV2 definition ChangesetBuilderImplBlock --- editoast/editoast_derive/src/modelv2.rs | 7 +- .../editoast_derive/src/modelv2/codegen.rs | 77 ++++----------- .../codegen/changeset_builder_impl_block.rs | 93 +++++++++++++++++++ 3 files changed, 117 insertions(+), 60 deletions(-) create mode 100644 editoast/editoast_derive/src/modelv2/codegen/changeset_builder_impl_block.rs diff --git a/editoast/editoast_derive/src/modelv2.rs b/editoast/editoast_derive/src/modelv2.rs index 936597ef1ab..1449ba66123 100644 --- a/editoast/editoast_derive/src/modelv2.rs +++ b/editoast/editoast_derive/src/modelv2.rs @@ -31,8 +31,8 @@ pub fn model(input: &DeriveInput) -> Result { let model_from_row_impl = config.model_from_row_impl(); let changeset_from_model_impl = config.changeset_from_model_impl(); - let cs_builder = config.make_builder(true); - let patch_builder = config.make_builder(false); + let changeset_builder = config.changeset_builder_impl_block(); + let patch_builder = config.patch_builder_impl_block(); let model_impls = config.make_model_traits_impl(); @@ -47,8 +47,9 @@ pub fn model(input: &DeriveInput) -> Result { #model_from_row_impl #changeset_from_model_impl - #cs_builder + #changeset_builder #patch_builder + #model_impls }) } diff --git a/editoast/editoast_derive/src/modelv2/codegen.rs b/editoast/editoast_derive/src/modelv2/codegen.rs index 1f3d14b7f25..b38c5d3e748 100644 --- a/editoast/editoast_derive/src/modelv2/codegen.rs +++ b/editoast/editoast_derive/src/modelv2/codegen.rs @@ -1,3 +1,4 @@ +mod changeset_builder_impl_block; mod changeset_decl; mod changeset_from_model; mod identifiable_impl; @@ -8,7 +9,6 @@ mod row_decl; use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::parse_quote; use crate::modelv2::codegen::changeset_decl::ChangesetDecl; use crate::modelv2::codegen::changeset_decl::ChangesetFieldDecl; @@ -16,6 +16,8 @@ use crate::modelv2::codegen::model_impl::ModelImpl; use crate::modelv2::codegen::row_decl::RowDecl; use crate::modelv2::codegen::row_decl::RowFieldDecl; +use self::changeset_builder_impl_block::BuilderType; +use self::changeset_builder_impl_block::ChangesetBuilderImplBlock; use self::changeset_from_model::ChangesetFromModelImpl; use self::identifiable_impl::IdentifiableImpl; use self::model_from_row_impl::ModelFromRowImpl; @@ -71,65 +73,26 @@ impl ModelConfig { } } - pub fn make_builder(&self, changeset: bool) -> TokenStream { - let np!(fields, fns, flat_fns, types, bodies, flat_bodies): np!(vec6) = self - .iter_fields() - .filter(|f| !self.is_primary(f)) - .filter(|field| !field.builder_skip) - .map(|field| { - let ident = &field.ident; - let expr = field.into_transformed(parse_quote! { #ident }); - let body = if changeset { - quote! { self.#ident = Some(#expr) } - } else { - quote! { self.changeset.#ident = Some(#expr) } - }; - let flat_body = if changeset { - quote! { self.#ident = #ident.map(|#ident| #expr) } - } else { - quote! { self.changeset.#ident = #ident.map(|#ident| #expr) } - }; - np!( - ident, - &field.builder_ident, - syn::Ident::new(&format!("flat_{}", &field.builder_ident), Span::call_site()), - &field.ty, - body, - flat_body - ) - }) - .unzip(); - - let impl_decl = if changeset { - let tn = self.changeset.ident(); - quote! { impl #tn } - } else { - let tn = &self.model; - quote! { impl<'a> crate::modelsv2::Patch<'a, #tn> } - }; - - quote! { - #[automatically_derived] - #impl_decl { - #( - #[allow(unused)] - #[must_use = "builder methods are intended to be chained"] - pub fn #fns(mut self, #fields: #types) -> Self { - #bodies; - self - } - - #[allow(unused)] - #[must_use = "builder methods are intended to be chained"] - pub fn #flat_fns(mut self, #fields: Option<#types>) -> Self { - #flat_bodies; - self - } - )* - } + pub(crate) fn changeset_builder_impl_block(&self) -> ChangesetBuilderImplBlock { + ChangesetBuilderImplBlock { + builder_type: BuilderType::Changeset, + model: self.model.clone(), + changeset: self.changeset.ident(), + fields: self + .iter_fields() + .filter(|field| !self.is_primary(field)) + .filter(|field| !field.builder_skip) + .cloned() + .collect(), } } + pub(crate) fn patch_builder_impl_block(&self) -> ChangesetBuilderImplBlock { + let mut builder = self.changeset_builder_impl_block(); + builder.builder_type = BuilderType::Patch; + builder + } + pub(crate) fn identifiable_impls(&self) -> Vec { self.identifiers .iter() diff --git a/editoast/editoast_derive/src/modelv2/codegen/changeset_builder_impl_block.rs b/editoast/editoast_derive/src/modelv2/codegen/changeset_builder_impl_block.rs new file mode 100644 index 00000000000..45855aa27ab --- /dev/null +++ b/editoast/editoast_derive/src/modelv2/codegen/changeset_builder_impl_block.rs @@ -0,0 +1,93 @@ +use proc_macro2::Span; +use proc_macro2::TokenStream; +use quote::quote; +use quote::ToTokens; +use syn::parse_quote; + +use crate::modelv2::ModelField; + +pub(super) enum BuilderType { + Changeset, + Patch, +} + +pub(crate) struct ChangesetBuilderImplBlock { + pub(super) builder_type: BuilderType, + pub(super) model: syn::Ident, + pub(super) changeset: syn::Ident, + pub(super) fields: Vec, +} + +impl ChangesetBuilderImplBlock { + fn impl_decl(&self) -> TokenStream { + let Self { + model, changeset, .. + } = self; + match self.builder_type { + BuilderType::Changeset => quote! { impl #changeset }, + BuilderType::Patch => quote! { impl<'a> crate::modelsv2::Patch<'a, #model> }, + } + } + + fn builder_field_fn_decl(&self, field: &ModelField) -> syn::ItemFn { + let ident = &field.ident; + let ty = &field.ty; + let value = field.into_transformed(parse_quote! { #ident }); + let statement = match self.builder_type { + BuilderType::Changeset => quote! { self.#ident = Some(#value) }, + BuilderType::Patch => quote! { self.changeset.#ident = Some(#value) }, + }; + parse_quote! { + pub fn #ident(mut self, #ident: #ty) -> Self { + #statement; + self + } + } + } + + fn builder_flat_fn_decl(&self, field: &ModelField) -> syn::ItemFn { + let ident = &field.ident; + let ty = &field.ty; + let value = field.into_transformed(parse_quote! { #ident }); + let statement = match self.builder_type { + BuilderType::Changeset => quote! { self.#ident = #ident.map(|#ident| #value) }, + BuilderType::Patch => quote! { self.changeset.#ident = #ident.map(|#ident| #value) }, + }; + let name = syn::Ident::new(&format!("flat_{}", &field.builder_ident), Span::call_site()); + parse_quote! { + pub fn #name(mut self, #ident: Option<#ty>) -> Self { + #statement; + self + } + } + } +} + +impl ToTokens for ChangesetBuilderImplBlock { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let impl_decl = self.impl_decl(); + let field_fns = self + .fields + .iter() + .map(|field| self.builder_field_fn_decl(field)); + let flat_fns = self + .fields + .iter() + .map(|field| self.builder_flat_fn_decl(field)); + tokens.extend(quote! { + #[automatically_derived] + #impl_decl { + #( + #[allow(unused)] + #[must_use = "builder methods are intended to be chained"] + #field_fns + )* + #( + #[allow(unused)] + #[must_use = "builder methods are intended to be chained"] + #flat_fns + )* + } + }); + } +}