diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 55e597b1c..16fb78858 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -17,37 +17,34 @@ pub fn properties(_: TokenStream, item: TokenStream) -> TokenStream { /// Expand a property impl block for a node. fn expand(mut impl_block: syn::ItemImpl) -> Result { // Split the node type into name and generic type arguments. - let (self_name, self_args) = parse_self(&*impl_block.self_ty)?; + let self_ty = &*impl_block.self_ty; + let (self_name, self_args) = parse_self(self_ty)?; // Rewrite the const items from values to keys. - let mut style_ids = vec![]; let mut modules = vec![]; for item in &mut impl_block.items { if let syn::ImplItem::Const(item) = item { - let (style_id, module) = process_const(item, &self_name, &self_args)?; - style_ids.push(style_id); + let module = process_const( + item, + &impl_block.generics, + self_ty, + &self_name, + &self_args, + )?; modules.push(module); } } - // Here, we use the collected `style_ids` to provide a function that checks - // whether a property belongs to the node. - impl_block.items.insert(0, parse_quote! { - /// Check whether the property with the given type id belongs to `Self`. - pub fn has_property(id: StyleId) -> bool { - [#(#style_ids),*].contains(&id) - } - }); - // Put everything into a module with a hopefully unique type to isolate // it from the outside. let module = quote::format_ident!("{}_types", self_name); Ok(quote! { #[allow(non_snake_case)] mod #module { + use std::any::TypeId; use std::marker::PhantomData; use once_cell::sync::Lazy; - use crate::eval::{Nonfolding, Property, StyleId}; + use crate::eval::{Nonfolding, Property}; use super::*; #impl_block @@ -85,19 +82,21 @@ fn parse_self(self_ty: &syn::Type) -> Result<(String, Vec<&syn::Type>)> { /// Process a single const item. fn process_const( item: &mut syn::ImplItemConst, + impl_generics: &syn::Generics, + self_ty: &syn::Type, self_name: &str, self_args: &[&syn::Type], -) -> Result<(syn::Expr, syn::ItemMod)> { +) -> Result { + // The module that will contain the `Key` type. + let module_name = &item.ident; + // The type of the property's value is what the user of our macro wrote // as type of the const ... let value_ty = &item.ty; - // ... but the real type of the const becomes Key<#key_param>. - let key_param = if self_args.is_empty() { - quote! { #value_ty } - } else { - quote! { (#value_ty, #(#self_args),*) } - }; + // ... but the real type of the const becomes Key<#key_args>. + let key_params = &impl_generics.params; + let key_args = quote! { #value_ty #(, #self_args)* }; // The display name, e.g. `TextNode::STRONG`. let name = format!("{}::{}", self_name, &item.ident); @@ -108,7 +107,7 @@ fn process_const( let mut folder = None; let mut nonfolding = Some(quote! { - impl Nonfolding for Key {} + impl<#key_params> Nonfolding for Key<#key_args> {} }); // Look for a folding function like `#[fold(u64::add)]`. @@ -127,54 +126,50 @@ fn process_const( } } - // The implementation of the `Property` trait. - let property_impl = quote! { - impl Property for Key { - type Value = #value_ty; - - const NAME: &'static str = #name; - - fn default() -> Self::Value { - #default - } - - fn default_ref() -> &'static Self::Value { - static LAZY: Lazy<#value_ty> = Lazy::new(|| #default); - &*LAZY - } - - #folder - } - - #nonfolding - }; - - // The module that will contain the `Key` type. - let module_name = &item.ident; - - // Generate the style id and module code. - let style_id = parse_quote! { StyleId::of::<#module_name::Key<#key_param>>() }; + // Generate the module code. let module = parse_quote! { #[allow(non_snake_case)] mod #module_name { use super::*; - pub struct Key(pub PhantomData); - impl Copy for Key {} - impl Clone for Key { + pub struct Key(pub PhantomData<(T, #key_args)>); + + impl<#key_params> Copy for Key<#key_args> {} + impl<#key_params> Clone for Key<#key_args> { fn clone(&self) -> Self { *self } } - #property_impl + impl<#key_params> Property for Key<#key_args> { + type Value = #value_ty; + + const NAME: &'static str = #name; + + fn node_id() -> TypeId { + TypeId::of::<#self_ty>() + } + + fn default() -> Self::Value { + #default + } + + fn default_ref() -> &'static Self::Value { + static LAZY: Lazy<#value_ty> = Lazy::new(|| #default); + &*LAZY + } + + #folder + } + + #nonfolding } }; // Replace type and initializer expression with the `Key`. item.attrs.retain(|attr| !attr.path.is_ident("fold")); - item.ty = parse_quote! { #module_name::Key<#key_param> }; + item.ty = parse_quote! { #module_name::Key<#key_args> }; item.expr = parse_quote! { #module_name::Key(PhantomData) }; - Ok((style_id, module)) + Ok(module) } diff --git a/src/eval/class.rs b/src/eval/class.rs index b7b4d0121..307bebccb 100644 --- a/src/eval/class.rs +++ b/src/eval/class.rs @@ -66,7 +66,7 @@ impl Class { let mut styles = StyleMap::new(); self.set(args, &mut styles)?; let node = (self.construct)(ctx, args)?; - Ok(node.styled_with_map(styles)) + Ok(node.styled_with_map(styles.scoped())) } /// Execute the class's set rule. diff --git a/src/eval/node.rs b/src/eval/node.rs index 37f230ee7..ecbee8eea 100644 --- a/src/eval/node.rs +++ b/src/eval/node.rs @@ -302,7 +302,7 @@ impl Packer { if let Some(Styled { item: ParChild::Text(left), map }) = self.par.children.last_mut() { - if child.map.compatible(map, TextNode::has_property) { + if child.map.compatible::(map) { left.0.push_str(&right.0); return; } @@ -380,7 +380,7 @@ impl Packer { return; } - if !self.par.styles.compatible(styles, ParNode::has_property) { + if !self.par.styles.compatible::(styles) { self.parbreak(None); self.par.styles = styles.clone(); return; @@ -397,7 +397,7 @@ impl Packer { return; } - if self.top && !self.flow.styles.compatible(styles, PageNode::has_property) { + if self.top && !self.flow.styles.compatible::(styles) { self.pagebreak(); self.flow.styles = styles.clone(); return; diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 0f3d694ab..bdc01f1f0 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -100,6 +100,16 @@ impl StyleMap { self.0.push(Entry::new(key, true)); } + /// Mark all contained properties as _scoped_. This means that they only + /// apply to the first descendant node (of their type) in the hierarchy and + /// not its children, too. This is used by class constructors. + pub fn scoped(mut self) -> Self { + for entry in &mut self.0 { + entry.scoped = true; + } + self + } + /// Make `self` the first link of the style chain `outer`. /// /// The resulting style chain contains styles from `self` as well as @@ -109,7 +119,10 @@ impl StyleMap { if self.is_empty() { *outer } else { - StyleChain { inner: self, outer: Some(outer) } + StyleChain { + first: Link::Map(self), + outer: Some(outer), + } } } @@ -122,9 +135,7 @@ impl StyleMap { /// immutable style maps from different levels of the hierarchy. pub fn apply(&mut self, outer: &Self) { for outer in &outer.0 { - if let Some(inner) = - self.0.iter_mut().find(|inner| inner.style_id() == outer.style_id()) - { + if let Some(inner) = self.0.iter_mut().find(|inner| inner.is_same(outer)) { *inner = inner.fold(outer); continue; } @@ -145,13 +156,10 @@ impl StyleMap { self.0.retain(|x| other.0.contains(x)); } - /// Whether two style maps are equal when filtered down to the given - /// properties. - pub fn compatible(&self, other: &Self, filter: F) -> bool - where - F: Fn(StyleId) -> bool, - { - let f = |entry: &&Entry| filter(entry.style_id()); + /// Whether two style maps are equal when filtered down to properties of the + /// node `T`. + pub fn compatible(&self, other: &Self) -> bool { + let f = |entry: &&Entry| entry.is_of::(); self.0.iter().filter(f).count() == other.0.iter().filter(f).count() && self.0.iter().filter(f).all(|x| other.0.contains(x)) } @@ -168,7 +176,7 @@ impl Debug for StyleMap { impl PartialEq for StyleMap { fn eq(&self, other: &Self) -> bool { - self.compatible(other, |_| true) + self.0.len() == other.0.len() && self.0.iter().all(|x| other.0.contains(x)) } } @@ -182,15 +190,22 @@ impl PartialEq for StyleMap { #[derive(Clone, Copy, Hash)] pub struct StyleChain<'a> { /// The first map in the chain. - inner: &'a StyleMap, + first: Link<'a>, /// The remaining maps in the chain. outer: Option<&'a Self>, } +/// The two kinds of links in the chain. +#[derive(Clone, Copy, Hash)] +enum Link<'a> { + Map(&'a StyleMap), + Barrier(TypeId), +} + impl<'a> StyleChain<'a> { /// Start a new style chain with a root map. pub fn new(map: &'a StyleMap) -> Self { - Self { inner: map, outer: None } + Self { first: Link::Map(map), outer: None } } /// Get the (folded) value of a copyable style property. @@ -205,7 +220,7 @@ impl<'a> StyleChain<'a> { where P::Value: Copy, { - self.get_cloned(key) + self.get_impl(key, 0) } /// Get a reference to a style property's value. @@ -220,13 +235,7 @@ impl<'a> StyleChain<'a> { where P: Nonfolding, { - if let Some(value) = self.find(key) { - value - } else if let Some(outer) = self.outer { - outer.get_ref(key) - } else { - P::default_ref() - } + self.get_ref_impl(key, 0) } /// Get the (folded) value of any style property. @@ -238,35 +247,87 @@ impl<'a> StyleChain<'a> { /// Returns the property's default value if no map in the chain contains an /// entry for it. pub fn get_cloned(self, key: P) -> P::Value { - if let Some(value) = self.find(key).cloned() { - if !P::FOLDABLE { - return value; - } + self.get_impl(key, 0) + } - match self.outer { - Some(outer) => P::fold(value, outer.get_cloned(key)), - None => P::fold(value, P::default()), + /// Insert a barrier into the style chain. + /// + /// Barriers interact with [scoped](StyleMap::scoped) styles: A scoped style + /// can still be read through a single barrier (the one of the node it + /// _should_ apply to), but a second barrier will make it invisible. + pub fn barred<'b>(&'b self, node: TypeId) -> StyleChain<'b> { + if self.needs_barrier(node) { + StyleChain { + first: Link::Barrier(node), + outer: Some(self), + } + } else { + *self + } + } +} + +impl<'a> StyleChain<'a> { + fn get_impl(self, key: P, depth: usize) -> P::Value { + let (value, depth) = self.process(key, depth); + if let Some(value) = value.cloned() { + if P::FOLDABLE { + if let Some(outer) = self.outer { + P::fold(value, outer.get_cloned(key)) + } else { + P::fold(value, P::default()) + } + } else { + value } } else if let Some(outer) = self.outer { - outer.get_cloned(key) + outer.get_impl(key, depth) } else { P::default() } } - /// Find a property directly in the localmost map. - fn find(self, _: P) -> Option<&'a P::Value> { - self.inner - .0 - .iter() - .find(|entry| entry.is::

()) - .and_then(|entry| entry.downcast::

()) + fn get_ref_impl(self, key: P, depth: usize) -> &'a P::Value + where + P: Nonfolding, + { + let (value, depth) = self.process(key, depth); + if let Some(value) = value { + value + } else if let Some(outer) = self.outer { + outer.get_ref_impl(key, depth) + } else { + P::default_ref() + } + } + + fn process(self, _: P, depth: usize) -> (Option<&'a P::Value>, usize) { + match self.first { + Link::Map(map) => ( + map.0 + .iter() + .find(|entry| entry.is::

() && (!entry.scoped || depth <= 1)) + .and_then(|entry| entry.downcast::

()), + depth, + ), + Link::Barrier(node) => (None, depth + (P::node_id() == node) as usize), + } + } + + fn needs_barrier(self, node: TypeId) -> bool { + if let Link::Map(map) = self.first { + if map.0.iter().any(|entry| entry.is_of_same(node)) { + return true; + } + } + + self.outer.map_or(false, |outer| outer.needs_barrier(node)) } } impl Debug for StyleChain<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.inner.fmt(f)?; + self.first.fmt(f)?; if let Some(outer) = self.outer { outer.fmt(f)?; } @@ -274,14 +335,67 @@ impl Debug for StyleChain<'_> { } } -/// A unique identifier for a style property. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct StyleId(TypeId); +impl Debug for Link<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Map(map) => map.fmt(f), + Self::Barrier(id) => writeln!(f, "Barrier({:?})", id), + } + } +} -impl StyleId { - /// The style id of the property. - pub fn of() -> Self { - Self(TypeId::of::

()) +/// An entry for a single style property. +#[derive(Clone)] +struct Entry { + p: Rc, + scoped: bool, +} + +impl Entry { + fn new(key: P, value: P::Value) -> Self { + Self { p: Rc::new((key, value)), scoped: false } + } + + fn is(&self) -> bool { + self.p.style_id() == TypeId::of::

() + } + + fn is_same(&self, other: &Self) -> bool { + self.p.style_id() == other.p.style_id() + } + + fn is_of(&self) -> bool { + self.p.node_id() == TypeId::of::() + } + + fn is_of_same(&self, node: TypeId) -> bool { + self.p.node_id() == node + } + + fn downcast(&self) -> Option<&P::Value> { + self.p.as_any().downcast_ref() + } + + fn fold(&self, outer: &Self) -> Self { + self.p.fold(outer) + } +} + +impl Debug for Entry { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + self.p.dyn_fmt(f) + } +} + +impl PartialEq for Entry { + fn eq(&self, other: &Self) -> bool { + self.p.dyn_eq(other) + } +} + +impl Hash for Entry { + fn hash(&self, state: &mut H) { + state.write_u64(self.p.hash64()); } } @@ -298,6 +412,12 @@ pub trait Property: Copy + 'static { /// The name of the property, used for debug printing. const NAME: &'static str; + /// Whether the property needs folding. + const FOLDABLE: bool = false; + + /// The type id of the node this property belongs to. + fn node_id() -> TypeId; + /// The default value of the property. fn default() -> Self::Value; @@ -308,9 +428,6 @@ pub trait Property: Copy + 'static { /// recreated all the time. fn default_ref() -> &'static Self::Value; - /// Whether the property needs folding. - const FOLDABLE: bool = false; - /// Fold the property with an outer value. /// /// For example, this would fold a relative font size with an outer @@ -324,74 +441,21 @@ pub trait Property: Copy + 'static { /// Marker trait that indicates that a property doesn't need folding. pub trait Nonfolding {} -/// An entry for a single style property. -#[derive(Clone)] -struct Entry(Rc); - -impl Entry { - fn new(key: P, value: P::Value) -> Self { - Self(Rc::new((key, value))) - } - - fn style_id(&self) -> StyleId { - self.0.style_id() - } - - fn is(&self) -> bool { - self.style_id() == StyleId::of::

() - } - - fn downcast(&self) -> Option<&P::Value> { - self.0.as_any().downcast_ref() - } - - fn fold(&self, outer: &Self) -> Self { - self.0.fold(outer) - } -} - -impl Debug for Entry { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - self.0.dyn_fmt(f) - } -} - -impl PartialEq for Entry { - fn eq(&self, other: &Self) -> bool { - self.0.dyn_eq(other) - } -} - -impl Hash for Entry { - fn hash(&self, state: &mut H) { - state.write_u64(self.0.hash64()); - } -} - /// This trait is implemented for pairs of zero-sized property keys and their /// value types below. Although it is zero-sized, the property `P` must be part /// of the implementing type so that we can use it in the methods (it must be a /// constrained type parameter). trait Bounds: 'static { - fn style_id(&self) -> StyleId; - fn fold(&self, outer: &Entry) -> Entry; fn as_any(&self) -> &dyn Any; fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result; fn dyn_eq(&self, other: &Entry) -> bool; fn hash64(&self) -> u64; + fn node_id(&self) -> TypeId; + fn style_id(&self) -> TypeId; + fn fold(&self, outer: &Entry) -> Entry; } impl Bounds for (P, P::Value) { - fn style_id(&self) -> StyleId { - StyleId::of::

() - } - - fn fold(&self, outer: &Entry) -> Entry { - let outer = outer.downcast::

().unwrap(); - let combined = P::fold(self.1.clone(), outer.clone()); - Entry::new(self.0, combined) - } - fn as_any(&self) -> &dyn Any { &self.1 } @@ -401,7 +465,7 @@ impl Bounds for (P, P::Value) { } fn dyn_eq(&self, other: &Entry) -> bool { - self.style_id() == other.style_id() + self.style_id() == other.p.style_id() && if let Some(other) = other.downcast::

() { &self.1 == other } else { @@ -415,4 +479,18 @@ impl Bounds for (P, P::Value) { self.1.hash(&mut state); state.finish() } + + fn node_id(&self) -> TypeId { + P::node_id() + } + + fn style_id(&self) -> TypeId { + TypeId::of::

() + } + + fn fold(&self, outer: &Entry) -> Entry { + let outer = outer.downcast::

().unwrap(); + let combined = P::fold(self.1.clone(), outer.clone()); + Entry::new(self.0, combined) + } } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index a00687ebf..6935afc23 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -197,6 +197,8 @@ impl Layout for PackedNode { regions: &Regions, styles: StyleChain, ) -> Vec>> { + let styles = styles.barred(self.node.as_any().type_id()); + #[cfg(not(feature = "layout-cache"))] return self.node.layout(ctx, regions, styles); diff --git a/tests/ref/style/construct.png b/tests/ref/style/construct.png new file mode 100644 index 000000000..e0dcf409b Binary files /dev/null and b/tests/ref/style/construct.png differ diff --git a/tests/typ/style/construct.typ b/tests/typ/style/construct.typ new file mode 100644 index 000000000..02f1a6aac --- /dev/null +++ b/tests/typ/style/construct.typ @@ -0,0 +1,10 @@ +// Test class construction. + +--- +// Ensure that constructor styles aren't passed down the tree. +#set par(spacing: 2pt) +#list( + body-indent: 20pt, + [First], + list([A], [B]) +)