From 1279a1755de69629060945b31fed78accda33f5a Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 5 Jan 2025 00:07:13 -0400 Subject: [PATCH] Improve InputVisitable derive macro --- crates/compute_graph/src/builder.rs | 3 +- crates/compute_graph/src/input.rs | 137 ++++++++++++++ crates/compute_graph/src/lib.rs | 10 +- crates/compute_graph/src/node.rs | 22 +-- crates/compute_graph/src/rule.rs | 107 +---------- crates/compute_graph_macros/src/lib.rs | 241 ++++++++++++++++--------- crates/derive_test/src/lib.rs | 60 +++++- src/generator/archive.rs | 3 +- src/generator/css.rs | 7 +- src/generator/home.rs | 3 +- src/generator/posts.rs | 8 +- src/generator/tags.rs | 6 +- src/generator/templates.rs | 40 ++-- src/generator/util/mod.rs | 9 +- 14 files changed, 399 insertions(+), 257 deletions(-) create mode 100644 crates/compute_graph/src/input.rs diff --git a/crates/compute_graph/src/builder.rs b/crates/compute_graph/src/builder.rs index cb29d33..741e4da 100644 --- a/crates/compute_graph/src/builder.rs +++ b/crates/compute_graph/src/builder.rs @@ -1,8 +1,9 @@ +use crate::input::{DynamicInput, Input}; use crate::node::{ AsyncConstNode, AsyncDynamicRuleNode, AsyncRuleNode, ConstNode, DynamicRuleNode, ErasedNode, InvalidatableConstNode, Node, NodeValue, RuleNode, }; -use crate::rule::{AsyncDynamicRule, AsyncRule, DynamicInput, DynamicRule, Input, Rule}; +use crate::rule::{AsyncDynamicRule, AsyncRule, DynamicRule, Rule}; use crate::synchronicity::{Asynchronous, Synchronicity, Synchronous}; use crate::util; use crate::{Graph, InvalidationSignal, NodeGraph, NodeId, ValueInvalidationSignal}; diff --git a/crates/compute_graph/src/input.rs b/crates/compute_graph/src/input.rs new file mode 100644 index 0000000..54ae3f6 --- /dev/null +++ b/crates/compute_graph/src/input.rs @@ -0,0 +1,137 @@ +use std::{ + cell::{Cell, Ref, RefCell}, + ops::Deref, + rc::Rc, +}; + +use crate::{node::DynamicRuleOutput, rule::DynamicNodeFactory, NodeId}; + +pub use compute_graph_macros::InputVisitable; + +/// A placeholder for the output of one node, to be used as an input for another. +/// +/// To obtain an input, add a value or rule to a [`GraphBuilder`](`crate::builder::GraphBuilder`). +/// +/// Note that this type implements `Clone`, so can be cloned and used as an input for multiple nodes. +pub struct Input { + pub(crate) node_idx: Rc>>, + pub(crate) value: Rc>>, +} + +impl Input { + /// Retrieves a reference to the current value of the node the input represents. + /// + /// Calling this method before the node it represents has been evaluated will panic. + pub fn value(&self) -> impl Deref + '_ { + Ref::map(self.value.borrow(), |opt| { + opt.as_ref() + .expect("node must be evaluated before reading value") + }) + } + + /// Retrieves the ID of the node that this input refers to. + pub fn node_id(&self) -> NodeId { + self.node_idx.get().unwrap() + } +} + +// can't derive this impl because it incorectly adds the bound T: Clone +impl Clone for Input { + fn clone(&self) -> Self { + Self { + node_idx: Rc::clone(&self.node_idx), + value: Rc::clone(&self.value), + } + } +} + +impl std::fmt::Debug for Input { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Input<{}>({:?})", + std::any::type_name::(), + self.node_idx + ) + } +} + +/// A placeholder for the output of a dynamic rule node, to be used as an input for another. +/// +/// See [`GraphBuilder::add_dynamic_rule`](`crate::builder::GraphBuilder::add_dynamic_rule`). +/// +/// A dependency on a dynamic input represents both a dependency on the dynamic node itself, +/// as well as dependencies on each of the nodes that are the output of the dynamic node. +#[derive(Clone)] +pub struct DynamicInput { + pub(crate) input: Input>, +} + +impl DynamicInput { + /// Retrieves a reference to the current value of the dynamic node (i.e., the set of inputs + /// representing the nodes that are the outputs of the dynamic node). + pub fn value(&self) -> impl Deref> + '_ { + self.input.value() + } +} + +// TODO: i really want Input to be able to implement Deref somehow + +/// A type that can visit arbitrary [`Input`]s. +/// +/// You generally do not implement this trait yourself. An implementation is provided to +/// [`InputVisitable::visit_inputs`]. +pub trait InputVisitor { + /// Visit an input whose value is of type `T`. + fn visit(&mut self, input: &Input); +} + +/// Common supertrait of [`Rule`](`crate::rule::Rule`) and [`AsyncRule`](`crate::rule::AsyncRule`) +/// that defines how rule inputs are visited. +/// +/// The implementation of this trait can generally be derived using [`derive@InputVisitable`]. +pub trait InputVisitable { + /// Visits all the [`Input`]s of this rule. + /// + /// This method is called when the graph is built/modified in order to establish edges of the graph, + /// representing the dependencies. Any input that the [`InputVisitor::visit`] is called with is + /// considered a dependency of the rule's node. + /// + /// While it is permitted for the dependencies of a rule to change after it has been added to the graph, + /// doing so only permitted before the graph has been built or during the callback of + /// [`Graph::modify`](`crate::Graph::modify`). Changes to the rule's dependencies outside of that will + /// not be detected and will not be represented in the graph. + fn visit_inputs(&self, visitor: &mut impl InputVisitor); +} + +impl InputVisitable for Input { + fn visit_inputs(&self, visitor: &mut impl InputVisitor) { + visitor.visit(self); + } +} + +impl InputVisitable for DynamicInput { + fn visit_inputs(&self, visitor: &mut impl InputVisitor) { + // Visit the dynamic node itself + visitor.visit(&self.input); + + // And visit all the nodes it produces + let maybe_dynamic_output = self.input.value.borrow(); + if let Some(dynamic_output) = maybe_dynamic_output.as_ref() { + // This might be slightly overzealous: it is possible for a node to only depend on the + // dynamic node itself, and not directly depend on any of the nodes the dynamic node produces. + for input in dynamic_output.inputs.iter() { + visitor.visit(input); + } + } else { + // Haven't evaluated the dynamic node for the first time yet. + // Upon doing so, if the nodes it produces change, we'll modify the graph + // and end up back here in the other branch. + } + } +} + +// no-op impl, here for convenience so you don't have to have to have as many `#[skip_input]`s +impl InputVisitable for DynamicNodeFactory { + fn visit_inputs(&self, _visitor: &mut impl InputVisitor) {} +} diff --git a/crates/compute_graph/src/lib.rs b/crates/compute_graph/src/lib.rs index 7937721..1b53976 100644 --- a/crates/compute_graph/src/lib.rs +++ b/crates/compute_graph/src/lib.rs @@ -44,16 +44,17 @@ //! ``` pub mod builder; +pub mod input; pub mod node; pub mod rule; pub mod synchronicity; mod util; use builder::{BuildGraphError, GraphBuilder}; +use input::{Input, InputVisitor}; use node::{ErasedNode, NodeUpdateContext, NodeValue}; use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef}; use petgraph::{stable_graph::StableDiGraph, visit::EdgeRef}; -use rule::{Input, InputVisitor}; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::collections::VecDeque; @@ -524,9 +525,8 @@ mod tests { use rule::DynamicNodeFactory; use super::*; - use crate::rule::{ - AsyncDynamicRule, AsyncRule, ConstantRule, DynamicInput, DynamicRule, InputVisitable, Rule, - }; + use crate::input::{DynamicInput, InputVisitable}; + use crate::rule::{AsyncDynamicRule, AsyncRule, ConstantRule, DynamicRule, Rule}; #[test] fn rule_output_with_no_inputs() { @@ -831,7 +831,7 @@ mod tests { struct DynamicSum(DynamicInput); impl InputVisitable for DynamicSum { fn visit_inputs(&self, visitor: &mut impl InputVisitor) { - visitor.visit_dynamic(&self.0); + self.0.visit_inputs(visitor); } } impl Rule for DynamicSum { diff --git a/crates/compute_graph/src/node.rs b/crates/compute_graph/src/node.rs index a2fce26..824b2f7 100644 --- a/crates/compute_graph/src/node.rs +++ b/crates/compute_graph/src/node.rs @@ -1,6 +1,6 @@ +use crate::input::InputVisitable; use crate::rule::{ - AsyncDynamicRule, AsyncDynamicRuleContext, AsyncRule, DynamicInput, DynamicRule, - DynamicRuleContext, InputVisitable, Rule, + AsyncDynamicRule, AsyncDynamicRuleContext, AsyncRule, DynamicRule, DynamicRuleContext, Rule, }; use crate::synchronicity::{Asynchronous, Synchronicity}; use crate::{Graph, Input, InputVisitor, InvalidationSignal, NodeGraph, NodeId, Synchronous}; @@ -257,24 +257,6 @@ fn visit_inputs(visitable: &V, visitor: &mut dyn FnMut(NodeId fn visit(&mut self, input: &Input) { self.0(input.node_idx.get().unwrap()); } - fn visit_dynamic(&mut self, input: &DynamicInput) { - // Visit the dynamic node itself - self.visit(&input.input); - - // And visit all the nodes it produces - let maybe_dynamic_output = input.input.value.borrow(); - if let Some(dynamic_output) = maybe_dynamic_output.as_ref() { - // This might be slightly overzealous: it is possible for a node to only depend on the - // dynamic node itself, and not directly depend on any of the nodes the dynamic node produces. - for input in dynamic_output.inputs.iter() { - self.visit(input); - } - } else { - // Haven't evaluated the dynamic node for the first time yet. - // Upon doing so, if the nodes it produces change, we'll modify the graph - // and end up back here in the other branch. - } - } } visitable.visit_inputs(&mut InputIndexVisitor(visitor)); } diff --git a/crates/compute_graph/src/rule.rs b/crates/compute_graph/src/rule.rs index 19dd06d..3d50ebf 100644 --- a/crates/compute_graph/src/rule.rs +++ b/crates/compute_graph/src/rule.rs @@ -1,12 +1,9 @@ +use crate::input::{Input, InputVisitable, InputVisitor}; use crate::node::{DynamicRuleOutput, NodeValue}; use crate::{InvalidationSignal, NodeId}; -pub use compute_graph_macros::InputVisitable; -use std::cell::{Cell, Ref, RefCell}; use std::collections::{HashMap, HashSet}; use std::future::Future; use std::hash::Hash; -use std::ops::Deref; -use std::rc::Rc; /// A rule produces a value for a graph node using its [`Input`]s. /// @@ -130,7 +127,7 @@ pub trait DynamicRuleContext { /// Helper type for working with [`DynamicRule`]s. /// -/// When implementing [`DynamicRule::evaluate`], call the [`DynamicNodeFactory::add_rule`] method for +/// When implementing [`DynamicRule::evaluate`], call the [`DynamicNodeFactory::add_node`] method for /// each of the nodes that should be in the output. Then, call [`DynamicNodeFactory::all_nodes`] to produce /// the output (i.e., `Vec` of [`Input`]s) of the dynamic node. /// @@ -188,7 +185,7 @@ impl DynamicNodeFactory { input } - /// Removes any nodes that were previously output but which have not had [`DynamicNodeFactory::add_rule`] + /// Removes any nodes that were previously output but which have not had [`DynamicNodeFactory::add_node`] /// called during this evaluation. /// /// Either this method or [`DynamicNodeFactory::all_nodes`] should only be called once per evaluation. @@ -252,104 +249,6 @@ pub trait AsyncDynamicRuleContext: DynamicRuleContext { R: AsyncRule; } -/// Common supertrait of [`Rule`] and [`AsyncRule`] that defines how rule inputs are visited. -/// -/// The implementation of this trait can generally be derived using [`derive@InputVisitable`]. -pub trait InputVisitable { - /// Visits all the [`Input`]s of this rule. - /// - /// This method is called when the graph is built/modified in order to establish edges of the graph, - /// representing the dependencies. Any input that the [`InputVisitor::visit`] is called with is - /// considered a dependency of the rule's node. - /// - /// While it is permitted for the dependencies of a rule to change after it has been added to the graph, - /// doing so only permitted before the graph has been built or during the callback of - /// [`Graph::modify`](`crate::Graph::modify`). Changes to the rule's dependencies outside of that will - /// not be detected and will not be represented in the graph. - fn visit_inputs(&self, visitor: &mut impl InputVisitor); -} - -/// A placeholder for the output of one node, to be used as an input for another. -/// -/// To obtain an input, add a value or rule to a [`GraphBuilder`](`crate::builder::GraphBuilder`). -/// -/// Note that this type implements `Clone`, so can be cloned and used as an input for multiple nodes. -pub struct Input { - pub(crate) node_idx: Rc>>, - pub(crate) value: Rc>>, -} - -impl Input { - /// Retrieves a reference to the current value of the node the input represents. - /// - /// Calling this method before the node it represents has been evaluated will panic. - pub fn value(&self) -> impl Deref + '_ { - Ref::map(self.value.borrow(), |opt| { - opt.as_ref() - .expect("node must be evaluated before reading value") - }) - } - - /// Retrieves the ID of the node that this input refers to. - pub fn node_id(&self) -> NodeId { - self.node_idx.get().unwrap() - } -} - -// can't derive this impl because it incorectly adds the bound T: Clone -impl Clone for Input { - fn clone(&self) -> Self { - Self { - node_idx: Rc::clone(&self.node_idx), - value: Rc::clone(&self.value), - } - } -} - -impl std::fmt::Debug for Input { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Input<{}>({:?})", - std::any::type_name::(), - self.node_idx - ) - } -} - -/// A placeholder for the output of a dynamic rule node, to be used as an input for another. -/// -/// See [`GraphBuilder::add_dynamic_rule`](`crate::builder::GraphBuilder::add_dynamic_rule`). -/// -/// A dependency on a dynamic input represents both a dependency on the dynamic node itself, -/// as well as dependencies on each of the nodes that are the output of the dynamic node. -#[derive(Clone)] -pub struct DynamicInput { - pub(crate) input: Input>, -} - -impl DynamicInput { - /// Retrieves a reference to the current value of the dynamic node (i.e., the set of inputs - /// representing the nodes that are the outputs of the dynamic node). - pub fn value(&self) -> impl Deref> + '_ { - self.input.value() - } -} - -// TODO: i really want Input to be able to implement Deref somehow - -/// A type that can visit arbitrary [`Input`]s. -/// -/// You generally do not implement this trait yourself. An implementation is provided to -/// [`InputVisitable::visit_inputs`]. -pub trait InputVisitor { - /// Visit an input whose value is of type `T`. - fn visit(&mut self, input: &Input); - - /// Visit a dynamic input whose child value is of type `T`. - fn visit_dynamic(&mut self, input: &DynamicInput); -} - /// A simple rule that provides a constant value. /// /// Note that, because [`Rule::evaluate`] returns an owned value, this rule's value type must implement `Clone`. diff --git a/crates/compute_graph_macros/src/lib.rs b/crates/compute_graph_macros/src/lib.rs index e79c655..ef327d3 100644 --- a/crates/compute_graph_macros/src/lib.rs +++ b/crates/compute_graph_macros/src/lib.rs @@ -1,9 +1,9 @@ use proc_macro::TokenStream; -use proc_macro2::Literal; +use proc_macro2::{Literal, Span}; use quote::{format_ident, quote, ToTokens}; use syn::{ - parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Field, Fields, GenericArgument, - GenericParam, PathArguments, Type, + parse_macro_input, Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, + GenericArgument, GenericParam, Index, Member, PathArguments, Type, }; extern crate proc_macro; @@ -11,83 +11,41 @@ extern crate proc_macro; /// Derive an implementation of the `InputVisitable` trait and helper methods. /// /// This macro generates an implementation of the `InputVisitable` trait and the `visit_inputs` method that -/// calls `visit` on each field of the struct that is of type `Input` or `DynamicInput` for any `T`. +/// calls `visit_inputs` on each field of the struct that is does not have the `#[skip_visit]` attribute. /// -/// The macro also generates helper methods for accessing the value of each input less verbosely. +/// For structs only, the macro also generates helper methods for accessing the value of each input less verbosely. /// For unnamed struct fields, the methods generated have the form `input_0`, `input_1`, etc. /// For named fields, the generated method name matches the field name. In both cases, the method /// returns a reference to the input value. As with the `Input::value` method, calling the helper methods /// before the referenced node has been evaluated is forbidden. -#[proc_macro_derive(InputVisitable, attributes(ignore_input))] -pub fn derive_rule(input: TokenStream) -> TokenStream { +#[proc_macro_derive(InputVisitable, attributes(skip_visit))] +pub fn derive_input_visitable(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - if let Data::Struct(ref data) = input.data { - derive_rule_struct(&input, data) - } else { - TokenStream::from( - syn::Error::new( - input.ident.span(), - "Only structs can derive `InputVisitable`", - ) - .to_compile_error(), - ) + match input.data { + Data::Struct(ref data) => derive_input_visitable_struct(&input, data), + Data::Enum(ref data) => derive_input_visitable_enum(&input, data), + Data::Union(_) => TokenStream::from( + syn::Error::new(input.ident.span(), "Unions cannot derive `InputVisitable`") + .to_compile_error(), + ), } } -fn derive_rule_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream { - let name = &input.ident; - let lt = &input.generics.lt_token; - let params = &input.generics.params; - let gt = &input.generics.gt_token; - let generics = quote!(#lt #params #gt); - let where_clause = &input.generics.where_clause; - let params_only_names = params.iter().map(|p| match p { - GenericParam::Lifetime(_) => { - panic!("Lifetime generics aren't supported when deriving `InputVisitable`") - } - GenericParam::Type(ty) => &ty.ident, - GenericParam::Const(_) => { - panic!("Const generics aren't supported when deriving `InputVisitable`") - } - }); - let generics_only_names = quote!(#lt #(#params_only_names),* #gt); - - let visit_inputs = match data.fields { - Fields::Named(ref named) => named - .named - .iter() - .flat_map(|field| { - if let Some((_ty, is_dynamic)) = input_value_type(field) { - let ident = field.ident.as_ref().unwrap(); - if is_dynamic { - Some(quote!(visitor.visit_dynamic(&self.#ident);)) - } else { - Some(quote!(visitor.visit(&self.#ident);)) - } - } else { - None - } - }) - .collect::>(), - Fields::Unnamed(ref unnamed) => unnamed - .unnamed - .iter() - .enumerate() - .flat_map(|(i, field)| { - if let Some((_ty, is_dynamic)) = input_value_type(field) { - let idx_lit = Literal::usize_unsuffixed(i); - if is_dynamic { - Some(quote!(visitor.visit_dynamic(&self.#idx_lit);)) - } else { - Some(quote!(visitor.visit(&self.#idx_lit);)) - } - } else { - None - } - }) - .collect::>(), - Fields::Unit => vec![], - }; +fn derive_input_visitable_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream { + let visit_inputs = derive_visit_inputs( + &data.fields, + |field| { + let ident = field.ident.clone().unwrap(); + quote!(&self.#ident) + }, + |i| { + let member = Member::Unnamed(Index { + index: i as u32, + span: Span::call_site(), + }); + quote!(&self.#member) + }, + ); let input_value_methods = match data.fields { Fields::Named(ref named) => named @@ -139,23 +97,138 @@ fn derive_rule_struct(input: &DeriveInput, data: &DataStruct) -> TokenStream { Fields::Unit => vec![], }; - TokenStream::from(quote!( - - impl #generics ::compute_graph::rule::InputVisitable for #name #generics_only_names #where_clause { - fn visit_inputs(&self, visitor: &mut impl ::compute_graph::rule::InputVisitor) { - #(#visit_inputs)* + let input_visitable_impl = make_derive_impl( + input, + quote!(::compute_graph::input::InputVisitable for), + quote!( + fn visit_inputs(&self, visitor: &mut impl ::compute_graph::input::InputVisitor) { + #visit_inputs } - } + ), + ); - impl #generics #name #generics_only_names #where_clause { + let input_value_methods_impl = make_derive_impl( + input, + quote!(), + quote!( #(#input_value_methods)* - } + ), + ); + TokenStream::from(quote!( + #input_visitable_impl + #input_value_methods_impl )) } +fn derive_input_visitable_enum(input: &DeriveInput, data: &DataEnum) -> TokenStream { + let variants = data.variants.iter().map(|variant| { + let ident = &variant.ident; + let fields = match &variant.fields { + Fields::Named(fields) => { + let field_refs = fields.named.iter().filter(should_visit_field).map(|field| { + let ident = field.ident.as_ref().expect("named field must have ident"); + quote!(ref #ident) + }); + quote!({ #(#field_refs,)* .. }) + } + Fields::Unnamed(fields) => { + let field_refs = fields.unnamed.iter().enumerate().map(|(i, field)| { + if should_visit_field(&field) { + let ident = format_ident!("field_{i}"); + quote!(ref #ident) + } else { + quote!(_) + } + }); + quote!((#(#field_refs,)*)) + } + Fields::Unit => quote!(), + }; + let variant_visit_inputs = derive_visit_inputs( + &variant.fields, + |field| field.ident.clone().unwrap(), + |i| format_ident!("field_{i}"), + ); + quote!(Self::#ident #fields => { + #variant_visit_inputs + }) + }); + + make_derive_impl( + input, + quote!(::compute_graph::input::InputVisitable for), + quote!( + fn visit_inputs(&self, visitor: &mut impl ::compute_graph::input::InputVisitor) { + match self { + #(#variants)* + } + } + ), + ) + .into() +} + +fn derive_visit_inputs( + fields: &Fields, + named_field_access: impl Fn(&Field) -> NamedAccessTokens, + unnamed_field_access: impl Fn(usize) -> UnnamedAccessTokens, +) -> proc_macro2::TokenStream { + match fields { + Fields::Named(ref named) => { + let fields = named.named.iter().filter(should_visit_field).map(|field| { + let field_access = named_field_access(field); + quote!(::compute_graph::input::InputVisitable::visit_inputs(#field_access, visitor);) + }); + quote!(#(#fields)*) + } + Fields::Unnamed(ref unnamed) => { + let fields = unnamed + .unnamed + .iter() + .enumerate() + .filter(|(_i, field)| should_visit_field(field)) + .map(|(i, _field)| { + let field_access = unnamed_field_access(i); + quote!(::compute_graph::input::InputVisitable::visit_inputs(#field_access, visitor);) + }); + quote!(#(#fields)*) + } + Fields::Unit => proc_macro2::TokenStream::new(), + } +} + +fn make_derive_impl( + input: &DeriveInput, + trait_for: proc_macro2::TokenStream, + body: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let name = &input.ident; + let lt = &input.generics.lt_token; + let params = &input.generics.params; + let gt = &input.generics.gt_token; + let generics = quote!(#lt #params #gt); + let where_clause = &input.generics.where_clause; + let params_only_names = params.iter().map(|p| match p { + GenericParam::Lifetime(_) => { + panic!("Lifetime generics aren't supported when deriving `InputVisitable`") + } + GenericParam::Type(ty) => &ty.ident, + GenericParam::Const(_) => { + panic!("Const generics aren't supported when deriving `InputVisitable`") + } + }); + let generics_only_names = quote!(#lt #(#params_only_names),* #gt); + + quote!( + impl #generics #trait_for #name #generics_only_names #where_clause { + #body + } + ) +} + fn input_value_type(field: &Field) -> Option<(&Type, bool)> { - if field.attrs.iter().any(|attr| is_ignore_attr(attr)) { + if field.attrs.iter().any(is_skip_attr) { return None; } if let Type::Path(ref path) = field.ty { @@ -183,9 +256,13 @@ fn input_value_type(field: &Field) -> Option<(&Type, bool)> { } } -fn is_ignore_attr(attr: &Attribute) -> bool { +fn should_visit_field(field: &&Field) -> bool { + !field.attrs.iter().any(is_skip_attr) +} + +fn is_skip_attr(attr: &Attribute) -> bool { match attr.meta.require_path_only() { - Ok(path) => path.is_ident("ignore_input"), + Ok(path) => path.is_ident("skip_visit"), Err(_) => false, } } diff --git a/crates/derive_test/src/lib.rs b/crates/derive_test/src/lib.rs index b7e81e2..8a8cc59 100644 --- a/crates/derive_test/src/lib.rs +++ b/crates/derive_test/src/lib.rs @@ -1,8 +1,9 @@ +use compute_graph::input::{DynamicInput, Input, InputVisitable}; use compute_graph::node::NodeValue; -use compute_graph::rule::{DynamicInput, Input, InputVisitable, Rule}; +use compute_graph::rule::Rule; #[derive(InputVisitable)] -struct Add(Input, Input, i32); +struct Add(Input, Input, #[skip_visit] i32); impl Rule for Add { type Output = i32; @@ -15,6 +16,7 @@ impl Rule for Add { struct Add2 { a: Input, b: Input, + #[skip_visit] c: i32, } @@ -47,11 +49,25 @@ impl Rule for Sum { } } +#[derive(InputVisitable)] +enum E { + A(#[skip_visit] i32, Input), + B { + #[skip_visit] + x: i32, + y: Input, + }, + C { + x: Input, + }, +} + #[cfg(test)] mod tests { use compute_graph::{ builder::GraphBuilder, - rule::{ConstantRule, DynamicRule, InputVisitor}, + input::InputVisitor, + rule::{ConstantRule, DynamicRule}, synchronicity::Synchronous, }; @@ -102,9 +118,10 @@ mod tests { #[test] fn test_ignore() { + #[allow(unused)] #[derive(InputVisitable)] struct Ignore { - #[ignore_input] + #[skip_visit] input: Input, } let mut builder = GraphBuilder::::new(); @@ -113,13 +130,42 @@ mod tests { fn visit(&mut self, _input: &Input) { assert!(false); } - fn visit_dynamic(&mut self, _input: &DynamicInput) { - unreachable!(); - } } Ignore { input: builder.add_value(0), } .visit_inputs(&mut Visitor); } + + #[test] + fn test_enum() { + let mut builder = GraphBuilder::::new(); + let input = builder.add_value(1); + struct Visitor(bool, Input); + impl InputVisitor for Visitor { + fn visit(&mut self, input: &Input) { + assert_eq!(input.node_id(), self.1.node_id()); + assert!(!self.0); + self.0 = true; + } + } + + let a = E::A(0, input.clone()); + let mut visitor = Visitor(false, input.clone()); + InputVisitable::visit_inputs(&a, &mut visitor); + assert!(visitor.0); + + let b = E::B { + x: 0, + y: input.clone(), + }; + let mut visitor = Visitor(false, input.clone()); + InputVisitable::visit_inputs(&b, &mut visitor); + assert!(visitor.0); + + let c = E::C { x: input.clone() }; + let mut visitor = Visitor(false, input); + InputVisitable::visit_inputs(&c, &mut visitor); + assert!(visitor.0); + } } diff --git a/src/generator/archive.rs b/src/generator/archive.rs index bc9f338..a654b76 100644 --- a/src/generator/archive.rs +++ b/src/generator/archive.rs @@ -3,7 +3,8 @@ use std::collections::HashMap; use chrono::Datelike; use compute_graph::{ builder::GraphBuilder, - rule::{Input, InputVisitable, Rule}, + input::{Input, InputVisitable}, + rule::Rule, synchronicity::Asynchronous, }; use serde::Serialize; diff --git a/src/generator/css.rs b/src/generator/css.rs index b8c47b8..9c79b7f 100644 --- a/src/generator/css.rs +++ b/src/generator/css.rs @@ -10,7 +10,8 @@ use base64::{Engine, prelude::BASE64_STANDARD}; use compute_graph::{ InvalidationSignal, builder::GraphBuilder, - rule::{Input, InputVisitable, Rule}, + input::{Input, InputVisitable, InputVisitor}, + rule::Rule, synchronicity::Asynchronous, }; use grass::{Fs, Options, OutputStyle}; @@ -88,7 +89,7 @@ struct CompileScss { fonts: HashMap<&'static str, Input>, } impl InputVisitable for CompileScss { - fn visit_inputs(&self, visitor: &mut impl compute_graph::rule::InputVisitor) { + fn visit_inputs(&self, visitor: &mut impl InputVisitor) { for input in self.fonts.values() { visitor.visit(input); } @@ -157,7 +158,7 @@ impl<'a> Fs for TrackingFs<'a> { } #[derive(InputVisitable)] -struct ReadFile(PathBuf); +struct ReadFile(#[skip_visit] PathBuf); impl Rule for ReadFile { type Output = String; fn evaluate(&mut self) -> Self::Output { diff --git a/src/generator/home.rs b/src/generator/home.rs index f368a38..75c029f 100644 --- a/src/generator/home.rs +++ b/src/generator/home.rs @@ -1,6 +1,7 @@ use compute_graph::{ builder::GraphBuilder, - rule::{Input, InputVisitable, Rule}, + input::{Input, InputVisitable}, + rule::Rule, synchronicity::Asynchronous, }; diff --git a/src/generator/posts.rs b/src/generator/posts.rs index 559ade7..fe5580f 100644 --- a/src/generator/posts.rs +++ b/src/generator/posts.rs @@ -7,10 +7,8 @@ use std::rc::Rc; use compute_graph::{ builder::GraphBuilder, - rule::{ - DynamicInput, DynamicNodeFactory, DynamicRule, DynamicRuleContext, Input, InputVisitable, - Rule, - }, + input::{DynamicInput, Input, InputVisitable}, + rule::{DynamicNodeFactory, DynamicRule, DynamicRuleContext, Rule}, synchronicity::Asynchronous, }; use content::{HtmlContent, Post}; @@ -97,6 +95,7 @@ fn find_index(path: PathBuf) -> Option { #[derive(InputVisitable)] struct MakeReadNodes { files: Input>, + #[skip_visit] watcher: Rc>, node_factory: DynamicNodeFactory, } @@ -129,6 +128,7 @@ pub type ReadPostOutput = Option>; #[derive(InputVisitable)] struct ReadPost { + #[skip_visit] path: PathBuf, } impl Rule for ReadPost { diff --git a/src/generator/tags.rs b/src/generator/tags.rs index 168ab57..3c13b27 100644 --- a/src/generator/tags.rs +++ b/src/generator/tags.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use compute_graph::{ builder::GraphBuilder, - rule::{DynamicInput, DynamicNodeFactory, DynamicRule, Input, InputVisitable, Rule}, + input::{DynamicInput, Input, InputVisitable}, + rule::{DynamicNodeFactory, DynamicRule, Rule}, synchronicity::Asynchronous, }; use serde::Serialize; @@ -77,6 +78,7 @@ impl DynamicRule for MakePostsByTags { #[derive(InputVisitable)] struct PostsByTag { posts: DynamicInput, + #[skip_visit] tag: Tag, } impl Rule for PostsByTag { @@ -131,7 +133,7 @@ struct Entry { #[derive(InputVisitable)] struct MakeWriteTagPages { tags: DynamicInput, - #[ignore_input] + #[skip_visit] templates: Input, build_context_factory: DynamicNodeFactory, render_factory: DynamicNodeFactory, diff --git a/src/generator/templates.rs b/src/generator/templates.rs index 5a9c19f..8581f87 100644 --- a/src/generator/templates.rs +++ b/src/generator/templates.rs @@ -6,8 +6,9 @@ use std::{ use chrono::Local; use compute_graph::{ builder::GraphBuilder, + input::{Input, InputVisitable}, node::NodeValue, - rule::{Input, InputVisitable, Rule}, + rule::Rule, synchronicity::Asynchronous, }; use log::error; @@ -39,7 +40,11 @@ pub fn make_graph( } #[derive(InputVisitable)] -pub struct AddTemplate(String, PathBuf, Input); +pub struct AddTemplate( + #[skip_visit] String, + #[skip_visit] PathBuf, + Input, +); impl AddTemplate { pub fn new(name: &str, path: PathBuf, base: Input) -> Self { Self(name.into(), path, base) @@ -86,9 +91,11 @@ static CB: Lazy = Lazy::new(|| { .as_secs() }); +#[derive(InputVisitable)] pub struct BuildTemplateContext { permalink: TemplatePermalink, input: Input, + #[skip_visit] func: F, } impl ()> BuildTemplateContext { @@ -100,14 +107,6 @@ impl ()> BuildTemplateContext { } } } -impl InputVisitable for BuildTemplateContext { - fn visit_inputs(&self, visitor: &mut impl compute_graph::rule::InputVisitor) { - if let TemplatePermalink::Dynamic(ref input) = self.permalink { - visitor.visit(input); - } - visitor.visit(&self.input); - } -} impl () + 'static> Rule for BuildTemplateContext { type Output = Context; fn evaluate(&mut self) -> Self::Output { @@ -126,9 +125,10 @@ impl () + 'static> Rule for BuildTemplate } } +#[derive(InputVisitable)] pub enum TemplatePermalink { - Constant(&'static str), - ConstantOwned(String), + Constant(#[skip_visit] &'static str), + ConstantOwned(#[skip_visit] String), Dynamic(Input), } impl From<&'static str> for TemplatePermalink { @@ -147,23 +147,14 @@ impl From> for TemplatePermalink { } } +#[derive(InputVisitable)] pub struct RenderTemplate { + #[skip_visit] pub name: &'static str, pub output_path: TemplateOutputPath, pub templates: Input, pub context: Input, } -// TODO: derive InputVisitable for enums? -// make Input impl InputVisitable, then the derived impl can just call visit_inputs on everything -impl InputVisitable for RenderTemplate { - fn visit_inputs(&self, visitor: &mut impl compute_graph::rule::InputVisitor) { - if let TemplateOutputPath::Dynamic(ref input) = self.output_path { - visitor.visit(input); - } - visitor.visit(&self.templates); - visitor.visit(&self.context); - } -} impl Rule for RenderTemplate { type Output = (); @@ -190,8 +181,9 @@ impl Rule for RenderTemplate { } } } +#[derive(InputVisitable)] pub enum TemplateOutputPath { - Constant(PathBuf), + Constant(#[skip_visit] PathBuf), Dynamic(Input), } impl<'a> From<&'a str> for TemplateOutputPath { diff --git a/src/generator/util/mod.rs b/src/generator/util/mod.rs index 864981b..73fb69b 100644 --- a/src/generator/util/mod.rs +++ b/src/generator/util/mod.rs @@ -8,9 +8,12 @@ use std::io::{BufWriter, Write}; use std::path::{Component, Path, PathBuf}; use anyhow::anyhow; -use compute_graph::builder::GraphBuilder; -use compute_graph::rule::{DynamicInput, Input, InputVisitable, Rule}; -use compute_graph::synchronicity::Synchronicity; +use compute_graph::{ + builder::GraphBuilder, + input::{DynamicInput, Input, InputVisitable}, + rule::Rule, + synchronicity::Synchronicity, +}; use serde::de::DeserializeOwned; pub fn output_writer(path: impl AsRef) -> Result {