From 548beade5496976be5aaad4c59a60b4e0a79ea8d Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 2 Aug 2022 14:40:19 +0300 Subject: [PATCH] extract configuration struct/impl code from entity `#[salsa::input]` structs will need similar code --- components/salsa-entity-macros/src/entity.rs | 83 +++---------------- .../salsa-entity-macros/src/entity_like.rs | 56 ++++++++++++- 2 files changed, 66 insertions(+), 73 deletions(-) diff --git a/components/salsa-entity-macros/src/entity.rs b/components/salsa-entity-macros/src/entity.rs index 01519af6..a88a880f 100644 --- a/components/salsa-entity-macros/src/entity.rs +++ b/components/salsa-entity-macros/src/entity.rs @@ -1,15 +1,12 @@ -use heck::CamelCase; use proc_macro2::{Literal, TokenStream}; -use crate::configuration; use crate::entity_like::{EntityField, EntityLike}; -// #[salsa::Entity(#id_ident in Jar0)] -// #[derive(Eq, PartialEq, Hash, Debug, Clone)] -// struct EntityData0 { -// id: u32 -// } - +/// For an entity struct `Foo` with fields `f1: T1, ..., fN: TN`, we generate... +/// +/// * the "id struct" `struct Foo(salsa::Id)` +/// * the entity ingredient, which maps the id fields to the `Id` +/// * for each value field, a function ingredient pub(crate) fn entity( args: proc_macro::TokenStream, input: proc_macro::TokenStream, @@ -24,14 +21,14 @@ impl EntityLike { fn generate_entity(&self) -> syn::Result { self.validate_entity()?; - let config_structs = self.config_structs(); + let (config_structs, config_impls) = + self.field_config_structs_and_impls(self.value_fields()); let id_struct = self.id_struct(); - let inherent_impl = self.id_inherent_impl(); - let ingredients_for_impl = self.id_ingredients_for_impl(&config_structs); + let inherent_impl = self.entity_inherent_impl(); + let ingredients_for_impl = self.entity_ingredients(&config_structs); let entity_in_db_impl = self.entity_in_db_impl(); let as_id_impl = self.as_id_impl(); - let config_impls = self.config_impls(&config_structs); Ok(quote! { #(#config_structs)* @@ -50,32 +47,8 @@ impl EntityLike { Ok(()) } - /// For each of the value fields in the entity, - /// we will generate a memoized function that stores its value. - /// Generate a struct for the "Configuration" of each of those functions. - fn config_structs(&self) -> Vec { - let ident = &self.id_ident(); - let visibility = self.visibility(); - self.value_fields() - .map(EntityField::name) - .map(|value_field_name| { - let config_name = syn::Ident::new( - &format!( - "__{}", - format!("{}_{}", ident, value_field_name).to_camel_case() - ), - value_field_name.span(), - ); - parse_quote! { - #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] - #visibility struct #config_name(std::convert::Infallible); - } - }) - .collect() - } - /// Generate an inherent impl with methods on the entity type. - fn id_inherent_impl(&self) -> syn::ItemImpl { + fn entity_inherent_impl(&self) -> syn::ItemImpl { let ident = self.id_ident(); let jar_ty = self.jar_ty(); let db_dyn_ty = self.db_dyn_ty(); @@ -165,7 +138,7 @@ impl EntityLike { /// /// The entity's ingredients include both the main entity ingredient along with a /// function ingredient for each of the value fields. - fn id_ingredients_for_impl(&self, config_structs: &[syn::ItemStruct]) -> syn::ItemImpl { + fn entity_ingredients(&self, config_structs: &[syn::ItemStruct]) -> syn::ItemImpl { let ident = self.id_ident(); let jar_ty = self.jar_ty(); let id_field_tys: Vec<&syn::Type> = self.id_fields().map(EntityField::ty).collect(); @@ -242,40 +215,6 @@ impl EntityLike { } } - fn config_impls(&self, config_structs: &[syn::ItemStruct]) -> Vec { - let ident = self.id_ident(); - let jar_ty = self.jar_ty(); - let value_field_tys = self.value_fields().map(EntityField::ty); - let value_field_backdates = self.value_fields().map(EntityField::is_backdate_field); - value_field_tys - .into_iter() - .zip(config_structs.iter().map(|s| &s.ident)) - .zip(value_field_backdates) - .map(|((value_field_ty, config_struct_name), value_field_backdate)| { - let should_backdate_value_fn = configuration::should_backdate_value_fn(value_field_backdate); - - parse_quote! { - impl salsa::function::Configuration for #config_struct_name { - type Jar = #jar_ty; - type Key = #ident; - type Value = #value_field_ty; - const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = salsa::cycle::CycleRecoveryStrategy::Panic; - - #should_backdate_value_fn - - fn execute(db: &salsa::function::DynDb, key: Self::Key) -> Self::Value { - unreachable!() - } - - fn recover_from_cycle(db: &salsa::function::DynDb, cycle: &salsa::Cycle, key: Self::Key) -> Self::Value { - unreachable!() - } - } - } - }) - .collect() - } - /// List of id fields (fields that are part of the entity's identity across revisions). /// /// If this is an enum, empty iterator. diff --git a/components/salsa-entity-macros/src/entity_like.rs b/components/salsa-entity-macros/src/entity_like.rs index 9b3a4a04..f02ae842 100644 --- a/components/salsa-entity-macros/src/entity_like.rs +++ b/components/salsa-entity-macros/src/entity_like.rs @@ -25,7 +25,9 @@ //! * data method `impl Foo { fn data(&self, db: &dyn crate::Db) -> FooData { FooData { f: self.f(db), ... } } }` //! * this could be optimized, particularly for interned fields -use crate::{data_item::DataItem, options::Options}; +use heck::CamelCase; + +use crate::{data_item::DataItem, options::Options, configuration}; pub(crate) struct EntityLike { args: Options, @@ -185,6 +187,58 @@ impl EntityLike { self.data_item.visibility() } + /// For each of the fields passed as an argument, + /// generate a struct named `Ident_Field` and an impl + /// of `salsa::function::Configuration` for that struct. + pub(crate) fn field_config_structs_and_impls<'a>( + &self, + fields: impl Iterator, + ) -> (Vec, Vec) { + let ident = &self.id_ident(); + let jar_ty = self.jar_ty(); + let visibility = self.visibility(); + fields + .map(|ef| { + let value_field_name = ef.name(); + let value_field_ty = ef.ty(); + let value_field_backdate = ef.is_backdate_field(); + let config_name = syn::Ident::new( + &format!( + "__{}", + format!("{}_{}", ident, value_field_name).to_camel_case() + ), + value_field_name.span(), + ); + let item_struct: syn::ItemStruct = parse_quote! { + #[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] + #visibility struct #config_name(std::convert::Infallible); + }; + + let should_backdate_value_fn = configuration::should_backdate_value_fn(value_field_backdate); + let item_impl: syn::ItemImpl = parse_quote! { + impl salsa::function::Configuration for #config_name { + type Jar = #jar_ty; + type Key = #ident; + type Value = #value_field_ty; + const CYCLE_STRATEGY: salsa::cycle::CycleRecoveryStrategy = salsa::cycle::CycleRecoveryStrategy::Panic; + + #should_backdate_value_fn + + fn execute(db: &salsa::function::DynDb, key: Self::Key) -> Self::Value { + unreachable!() + } + + fn recover_from_cycle(db: &salsa::function::DynDb, cycle: &salsa::Cycle, key: Self::Key) -> Self::Value { + unreachable!() + } + } + }; + + (item_struct, item_impl) + }) + .unzip() + } + /// Generate `impl salsa::AsId for Foo` pub(crate) fn as_id_impl(&self) -> syn::ItemImpl { let ident = self.id_ident();