diff --git a/dev_ext/src/task.rs b/dev_ext/src/task.rs index 2963503..e1c799c 100644 --- a/dev_ext/src/task.rs +++ b/dev_ext/src/task.rs @@ -4,7 +4,7 @@ use std::io; use std::io::{Read, Write}; use std::path::PathBuf; -use pie::{Context, KeyBounds, OutputChecker, ResourceChecker, Task}; +use pie::{Context, Key, OutputChecker, ResourceChecker, Task}; use pie::resource::file::{FsError, ModifiedChecker}; use pie::task::{AlwaysConsistent, EqualsChecker}; @@ -49,7 +49,7 @@ impl Constant> { Self(Ok(val.into())) } } -impl Task for Constant { +impl Task for Constant { type Output = T; #[inline] fn execute(&self, _context: &mut C) -> Self::Output { diff --git a/pie/src/context/bottom_up.rs b/pie/src/context/bottom_up.rs index 5bec381..b52fd87 100644 --- a/pie/src/context/bottom_up.rs +++ b/pie/src/context/bottom_up.rs @@ -9,7 +9,6 @@ use crate::dependency::ResourceDependencyObj; use crate::pie::{SessionInternal, Tracking}; use crate::store::{Store, TaskNode}; use crate::trait_object::{KeyObj, ValueObj}; -use crate::trait_object::base::CloneBox; use crate::trait_object::collection::TypeToAnyMap; use crate::trait_object::task::TaskObj; @@ -61,7 +60,7 @@ impl<'p, 's> BottomUpContext<'p, 's> { /// Execute task `node` and potentially schedule new tasks based on the dependencies of the task. fn execute_and_schedule(&mut self, node: TaskNode) -> Box { - let task = self.session.store.get_task(&node).clone_box(); + let task = self.session.store.get_task(&node).to_owned(); let output = self.execute_obj(task.as_ref(), node); // Schedule tasks affected by task `node`'s resource writes. diff --git a/pie/src/dependency.rs b/pie/src/dependency.rs index 2aa45a5..5325266 100644 --- a/pie/src/dependency.rs +++ b/pie/src/dependency.rs @@ -16,7 +16,6 @@ pub struct TaskDependency { checker: C, stamp: S, } - impl> TaskDependency { #[inline] pub fn new(task: T, checker: C, stamp: C::Stamp) -> Self { Self { task, checker, stamp } } @@ -29,7 +28,7 @@ impl> TaskDependency { pub fn stamp(&self) -> &C::Stamp { &self.stamp } #[inline] - pub fn check<'i>(&'i self, output: &'i T::Output) -> Option> { + pub fn check<'i>(&'i self, output: &'i T::Output) -> Option { self.checker.check(output, &self.stamp) } @@ -48,7 +47,7 @@ pub trait TaskDependencyObj: DynClone + Debug { fn as_top_down_check(&self) -> &dyn TopDownCheck; fn is_consistent_bottom_up(&self, output: &dyn ValueObj, requiring_task: &dyn KeyObj, tracker: &mut Tracking) -> bool; } - +const_assert_object_safe!(dyn TaskDependencyObj); impl> TaskDependencyObj for TaskDependency { #[inline] fn task(&self) -> &dyn KeyObj { &self.task as &dyn KeyObj } @@ -90,7 +89,6 @@ pub struct ResourceDependency { checker: C, stamp: S, } - impl> ResourceDependency { #[inline] pub fn new(resource: R, checker: C, stamp: C::Stamp) -> Self { Self { resource, checker, stamp } } @@ -106,7 +104,7 @@ impl> ResourceDependency { pub fn check<'i, RS: ResourceState>( &'i self, state: &'i mut RS, - ) -> Result>, C::Error> { + ) -> Result, C::Error> { self.checker.check(&self.resource, state, &self.stamp) } @@ -151,6 +149,7 @@ pub trait ResourceDependencyObj: DynClone + Debug { tracker: &mut Tracking, ) -> Result>; } +const_assert_object_safe!(dyn ResourceDependencyObj); impl> ResourceDependencyObj for ResourceDependency { #[inline] fn resource(&self) -> &dyn KeyObj { &self.resource as &dyn KeyObj } diff --git a/pie/src/lib.rs b/pie/src/lib.rs index 0a0d09d..47bdc4e 100644 --- a/pie/src/lib.rs +++ b/pie/src/lib.rs @@ -1,14 +1,14 @@ //! # Trait bounds //! -//! [`Task`] and [`Resource`] are bounded by [`KeyBounds`] so that we can store types of those traits as a key in a +//! [`Task`] and [`Resource`] are bounded by [`Key`] so that we can store types of those traits as a key in a //! hashmap in trait object form. We need to store these types under a trait object to support arbitrary task and //! resource types. We also need to store an additional clone for a reverse hashmap. //! -//! [`OutputChecker`] and [`ResourceChecker`] are also bounded by [`KeyBounds`], because types of these traits may be -//! used as values in tasks, which would require them to be bounded by [`KeyBounds`] anyway. This reduces boilerplate in -//! tasks that are generic over [`OutputChecker`] and [`ResourceChecker`], as the [`KeyBounds`] bound can be omitted. +//! [`OutputChecker`] and [`ResourceChecker`] are also bounded by [`Key`], because types of these traits may be +//! used as values in tasks, which would require them to be bounded by [`Key`] anyway. This reduces boilerplate in +//! tasks that are generic over [`OutputChecker`] and [`ResourceChecker`], as the [`Key`] bound can be omitted. //! -//! [`Task::Output`], [`OutputChecker::Stamp`], and [`ResourceChecker::Stamp`] are bounded by [`ValueBounds`] because +//! [`Task::Output`], [`OutputChecker::Stamp`], and [`ResourceChecker::Stamp`] are bounded by [`Value`] because //! we need to store (cache) these values. We need to store these values for an indeterminate time, so non-`'static` //! references are ruled out. We need to clone outputs to store them. When checking dependencies, we need to clone the //! dependencies (due to lifetime/borrow complications). Since these types are used in dependencies, that is another @@ -27,6 +27,7 @@ use crate::trait_object::KeyObj; pub mod task; pub mod resource; pub mod tracker; +#[macro_use] pub mod trait_object; mod pie; @@ -36,18 +37,18 @@ mod dependency; /// Trait alias for types that are used as values: types that can be cloned, debug formatted, and contain no /// non-`'static` references. We use this as an alias for trait bounds and super-traits. -pub trait ValueBounds: Clone + Debug + 'static {} -impl ValueBounds for T {} +pub trait Value: Clone + Debug + 'static {} +impl Value for T {} -/// Trait alias for types that are used as keys: types that can be cloned, equality compared, hashed, debug formatted, -/// and contain no non-`'static` references. We use this as an alias for trait bounds and super-traits. -pub trait KeyBounds: ValueBounds + Eq + Hash {} -impl KeyBounds for T {} +/// Trait alias for types that are used as keys: types that are [values](Value) and that can be equality compared and +/// hashed. We use this as an alias for trait bounds and super-traits. +pub trait Key: Value + Eq + Hash {} +impl Key for T {} /// A unit of computation in a programmatic incremental build system. -pub trait Task: KeyBounds { +pub trait Task: Key { /// Type of task outputs. - type Output: ValueBounds; + type Output: Value; /// Execute the task under `context`, returning an output. fn execute(&self, context: &mut C) -> Self::Output; @@ -97,25 +98,22 @@ pub trait Context { H: ResourceChecker; } -/// Consistency checker for task outputs, producing and checking output stamps. For example, the equals checker uses the -/// output of a task as stamp, and checks whether they are equal. -pub trait OutputChecker: KeyBounds { +/// Consistency checker for task outputs of type `O`, producing and checking output stamps. For example, the +/// [equals checker](task::EqualsChecker) uses the output of a task as stamp, and checks whether they are equal. +pub trait OutputChecker: Key { /// Type of stamps. - type Stamp: ValueBounds; + type Stamp: Value; /// Stamps `output`. fn stamp(&self, output: &O) -> Self::Stamp; - /// Type of inconsistency used for debugging/logging purposes. The `'i` lifetime represents this checker and the - /// lifetime of the `output` and `stamp` passed to [check](Self::check). - type Inconsistency<'i>: Debug where O: 'i; /// Checks whether `output` is inconsistent w.r.t. `stamp`, returning `Some(inconsistency)` if inconsistent, `None` if - /// consistent. - fn check<'i>(&'i self, output: &'i O, stamp: &'i Self::Stamp) -> Option>; + /// consistent. The returned inconsistency can be used for debugging purposes, such as logging what has changed. + fn check(&self, output: &O, stamp: &Self::Stamp) -> Option; } /// A resource representing global (mutable) state, such as a path identifying a file on a filesystem. -pub trait Resource: KeyBounds { +pub trait Resource: Key { /// Type of readers returned from [read](Self::read), with `'rs` representing the lifetime of the resource state. type Reader<'rs>; /// Type of writers returned from [write](Self::write), with `'r` representing the lifetime of this resource. @@ -160,9 +158,9 @@ pub trait ResourceState { /// Consistency checker for resources, producing and checking resource stamps. For example, for filesystem resources, a /// last modified checker creates last modified stamps and checks whether they have changed, and a hash checker creates /// file content hash stamps and checks whether they have changed. -pub trait ResourceChecker: KeyBounds { +pub trait ResourceChecker: Key { /// Type of stamps returned from stamp methods. - type Stamp: ValueBounds; + type Stamp: Value; /// Type of errors returned from all methods. type Error: Error; @@ -187,17 +185,15 @@ pub trait ResourceChecker: KeyBounds { /// consistent with `resource`. fn stamp_writer(&self, resource: &R, writer: R::Writer<'_>) -> Result; - /// Type of inconsistency used for debugging/logging purposes. The `'i` lifetime represents this checker and the - /// lifetime of the `resource`, `state`, and `stamp` passed to [Self::check]. - type Inconsistency<'i>: Debug; /// Checks whether `resource` is inconsistent w.r.t. `stamp`, with access to `state`. Returns `Some(inconsistency)` - /// when inconsistent, `None` when consistent. - fn check<'i, RS: ResourceState>( - &'i self, - resource: &'i R, - state: &'i mut RS, - stamp: &'i Self::Stamp, - ) -> Result>, Self::Error>; + /// when inconsistent, `None` when consistent. The returned inconsistency can be used for debugging purposes, such as + /// logging what has changed. + fn check>( + &self, + resource: &R, + state: &mut RS, + stamp: &Self::Stamp, + ) -> Result, Self::Error>; /// Wraps a [resource `error`](Resource::Error) into [`Self::Error`]. fn wrap_error(&self, error: R::Error) -> Self::Error; diff --git a/pie/src/resource/file.rs b/pie/src/resource/file.rs index 0e1fe5e..56ba0ab 100644 --- a/pie/src/resource/file.rs +++ b/pie/src/resource/file.rs @@ -1,6 +1,6 @@ use std::convert::Infallible; use std::error::Error; -use std::fmt::{Display, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::fs::{self, File, Metadata, OpenOptions}; use std::io::{self, BufReader, Seek}; use std::path::{Path, PathBuf}; @@ -232,7 +232,7 @@ impl From for io::Error { } impl Display for FsError { #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.0, f) } } @@ -264,8 +264,8 @@ impl ResourceChecker for ModifiedChecker { Ok(Some(file.metadata()?.modified()?)) } - type Inconsistency<'i> = Self::Stamp; #[inline] + #[allow(refining_impl_trait)] fn check>( &self, path: &PathBuf, @@ -310,8 +310,8 @@ impl ResourceChecker for ExistsChecker { Ok(exists) } - type Inconsistency<'i> = Self::Stamp; #[inline] + #[allow(refining_impl_trait)] fn check>( &self, path: &PathBuf, diff --git a/pie/src/resource/file/hash_checker.rs b/pie/src/resource/file/hash_checker.rs index f16706b..14164eb 100644 --- a/pie/src/resource/file/hash_checker.rs +++ b/pie/src/resource/file/hash_checker.rs @@ -1,3 +1,4 @@ +use std::fmt::Debug; use std::io::Seek; use sha2::{Digest, Sha256}; @@ -34,8 +35,8 @@ impl ResourceChecker for HashChecker { Ok(Some(hash)) } - type Inconsistency<'i> = Self::Stamp; #[inline] + #[allow(refining_impl_trait)] fn check>( &self, path: &PathBuf, diff --git a/pie/src/resource/map.rs b/pie/src/resource/map.rs index 34bf2db..e561787 100644 --- a/pie/src/resource/map.rs +++ b/pie/src/resource/map.rs @@ -7,12 +7,12 @@ use std::hash::Hash; use dyn_clone::DynClone; -use crate::{KeyBounds, Resource, ResourceChecker, ResourceState}; +use crate::{Key, Resource, ResourceChecker, ResourceState}; use crate::trait_object::base::{AsAny, EqObj}; use crate::trait_object::KeyObj; /// Key of globally shared [hash maps](HashMap) from [`Self`] to [`Self::Value`]. -pub trait MapKey: KeyBounds { +pub trait MapKey: Key { type Value; } impl Resource for K { @@ -99,14 +99,13 @@ impl ResourceChecker for MapEqualsChecker where Ok(value) } - type Inconsistency<'i> = Option<&'i K::Value>; #[inline] - fn check<'i, RS: ResourceState>( + fn check>( &self, key: &K, - state: &'i mut RS, + state: &mut RS, stamp: &Self::Stamp, - ) -> Result>, Self::Error> { + ) -> Result, Self::Error> { let value = key.read(state)?; let inconsistency = if value != stamp.as_ref() { Some(value) @@ -124,7 +123,7 @@ impl ResourceChecker for MapEqualsChecker where #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] #[repr(transparent)] pub struct MapKeyToObj(pub K); -impl MapKey for MapKeyToObj { +impl MapKey for MapKeyToObj { type Value = Box; } impl MapKeyToObj { diff --git a/pie/src/store.rs b/pie/src/store.rs index 8fd3d70..a84e469 100644 --- a/pie/src/store.rs +++ b/pie/src/store.rs @@ -5,7 +5,6 @@ use pie_graph::{DAG, Node}; use crate::dependency::{Dependency, ResourceDependencyObj, TaskDependencyObj}; use crate::trait_object::{KeyObj, ValueObj}; -use crate::trait_object::base::CloneBox; use crate::trait_object::task::TaskObj; pub struct Store { @@ -57,11 +56,11 @@ impl Store { *node } else { let node = self.graph.add_node(NodeData::Task { - task: task.clone_box(), + task: task.to_owned(), output: None, }); let node = TaskNode(node); - self.task_to_node.insert(task.clone_box(), node); + self.task_to_node.insert(task.to_owned(), node); node } } @@ -85,9 +84,9 @@ impl Store { if let Some(node) = self.resource_to_node.get(resource) { *node } else { - let node = self.graph.add_node(NodeData::Resource(resource.clone_box())); + let node = self.graph.add_node(NodeData::Resource(resource.to_owned())); let node = ResourceNode(node); - self.resource_to_node.insert(resource.clone_box(), node); + self.resource_to_node.insert(resource.to_owned(), node); node } } diff --git a/pie/src/task.rs b/pie/src/task.rs index 70799e0..a4bcf44 100644 --- a/pie/src/task.rs +++ b/pie/src/task.rs @@ -4,21 +4,20 @@ use std::hash::Hash; use std::rc::Rc; use std::sync::Arc; -use crate::{Context, OutputChecker, Task, ValueBounds}; +use crate::{Context, OutputChecker, Task, Value}; /// [Task output checker](OutputChecker) that checks by equality. #[derive(Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] pub struct EqualsChecker; -impl OutputChecker for EqualsChecker { +impl OutputChecker for EqualsChecker { type Stamp = O; #[inline] fn stamp(&self, output: &O) -> Self::Stamp { output.clone() } - type Inconsistency<'i> = &'i O where O: 'i; #[inline] - fn check<'i>(&self, output: &'i O, stamp: &Self::Stamp) -> Option> { + fn check(&self, output: &O, stamp: &Self::Stamp) -> Option { if output != stamp { Some(output) } else { @@ -30,16 +29,15 @@ impl OutputChecker for EqualsChecker { /// [Task output checker](OutputChecker) that checks [Ok] by equality, but [Err] only by existence. #[derive(Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] pub struct OkEqualsChecker; -impl OutputChecker> for OkEqualsChecker { +impl OutputChecker> for OkEqualsChecker { type Stamp = Option; #[inline] fn stamp(&self, output: &Result) -> Self::Stamp { output.as_ref().ok().cloned() } - type Inconsistency<'i> = Option<&'i O> where E: 'i; #[inline] - fn check<'i>(&self, output: &'i Result, stamp: &Self::Stamp) -> Option> { + fn check(&self, output: &Result, stamp: &Self::Stamp) -> Option { let new_stamp = output.as_ref().ok(); if new_stamp != stamp.as_ref() { Some(new_stamp) @@ -52,16 +50,15 @@ impl OutputChecker> for OkEqualsChecker { /// [Task output checker](OutputChecker) that checks [Err] by equality, but [Ok] only by existence. #[derive(Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] pub struct ErrEqualsChecker; -impl OutputChecker> for ErrEqualsChecker { +impl OutputChecker> for ErrEqualsChecker { type Stamp = Option; #[inline] fn stamp(&self, output: &Result) -> Self::Stamp { output.as_ref().err().cloned() } - type Inconsistency<'i> = Option<&'i E> where O: 'i; #[inline] - fn check<'i>(&self, output: &'i Result, stamp: &Self::Stamp) -> Option> { + fn check(&self, output: &Result, stamp: &Self::Stamp) -> Option { let new_stamp = output.as_ref().err(); if new_stamp != stamp.as_ref() { Some(new_stamp) @@ -81,9 +78,8 @@ impl OutputChecker> for ResultChecker { output.is_err() } - type Inconsistency<'i> = Self::Stamp where T: 'i, E: 'i; #[inline] - fn check(&self, output: &Result, stamp: &Self::Stamp) -> Option { + fn check(&self, output: &Result, stamp: &Self::Stamp) -> Option { let new_stamp = output.is_err(); if new_stamp != *stamp { Some(new_stamp) @@ -105,10 +101,9 @@ impl OutputChecker for AlwaysConsistent { () } - type Inconsistency<'o> = Infallible where O: 'o; #[inline] - fn check(&self, _output: &O, _stamp: &Self::Stamp) -> Option { - None + fn check(&self, _output: &O, _stamp: &Self::Stamp) -> Option { + None:: } } diff --git a/pie/src/tracker/event.rs b/pie/src/tracker/event.rs index ca3444f..3386f04 100644 --- a/pie/src/tracker/event.rs +++ b/pie/src/tracker/event.rs @@ -4,7 +4,6 @@ use std::ops::RangeInclusive; use crate::Task; use crate::tracker::Tracker; use crate::trait_object::{KeyObj, ValueObj}; -use crate::trait_object::base::CloneBox; /// A [`Tracker`] that stores [`Event`]s in a [`Vec`], useful in testing to assert that a context implementation is /// incremental and correct. @@ -128,8 +127,8 @@ impl Tracker for EventTracker { #[inline] fn require_start(&mut self, task: &dyn KeyObj, checker: &dyn ValueObj) { let data = RequireStart { - task: task.clone_box(), - checker: checker.clone_box(), + task: task.to_owned(), + checker: checker.to_owned(), index: self.events.len(), }; self.events.push(Event::RequireStart(data)); @@ -143,10 +142,10 @@ impl Tracker for EventTracker { output: &dyn ValueObj, ) { let data = RequireEnd { - task: task.clone_box(), - checker: checker.clone_box(), - stamp: stamp.clone_box(), - output: output.clone_box(), + task: task.to_owned(), + checker: checker.to_owned(), + stamp: stamp.to_owned(), + output: output.to_owned(), index: self.events.len(), }; self.events.push(Event::RequireEnd(data)); @@ -155,8 +154,8 @@ impl Tracker for EventTracker { #[inline] fn read_start(&mut self, resource: &dyn KeyObj, checker: &dyn ValueObj) { let data = ResourceStart { - resource: resource.clone_box(), - checker: checker.clone_box(), + resource: resource.to_owned(), + checker: checker.to_owned(), index: self.events.len(), }; self.events.push(Event::ReadStart(data)); @@ -164,9 +163,9 @@ impl Tracker for EventTracker { #[inline] fn read_end(&mut self, resource: &dyn KeyObj, checker: &dyn ValueObj, stamp: &dyn ValueObj) { let data = ResourceEnd { - resource: resource.clone_box(), - checker: checker.clone_box(), - stamp: stamp.clone_box(), + resource: resource.to_owned(), + checker: checker.to_owned(), + stamp: stamp.to_owned(), index: self.events.len(), }; self.events.push(Event::ReadEnd(data)); @@ -175,8 +174,8 @@ impl Tracker for EventTracker { #[inline] fn write_start(&mut self, resource: &dyn KeyObj, checker: &dyn ValueObj) { let data = ResourceStart { - resource: resource.clone_box(), - checker: checker.clone_box(), + resource: resource.to_owned(), + checker: checker.to_owned(), index: self.events.len(), }; self.events.push(Event::WriteStart(data)); @@ -184,9 +183,9 @@ impl Tracker for EventTracker { #[inline] fn write_end(&mut self, resource: &dyn KeyObj, checker: &dyn ValueObj, stamp: &dyn ValueObj) { let data = ResourceEnd { - resource: resource.clone_box(), - checker: checker.clone_box(), - stamp: stamp.clone_box(), + resource: resource.to_owned(), + checker: checker.to_owned(), + stamp: stamp.to_owned(), index: self.events.len(), }; self.events.push(Event::WriteEnd(data)); @@ -195,7 +194,7 @@ impl Tracker for EventTracker { #[inline] fn execute_start(&mut self, task: &dyn KeyObj) { let data = ExecuteStart { - task: task.clone_box(), + task: task.to_owned(), index: self.events.len(), }; self.events.push(Event::ExecuteStart(data)); @@ -203,8 +202,8 @@ impl Tracker for EventTracker { #[inline] fn execute_end(&mut self, task: &dyn KeyObj, output: &dyn ValueObj) { let data = ExecuteEnd { - task: task.clone_box(), - output: output.clone_box(), + task: task.to_owned(), + output: output.to_owned(), index: self.events.len(), }; self.events.push(Event::ExecuteEnd(data)); diff --git a/pie/src/trait_object/base.rs b/pie/src/trait_object/base.rs index e953a40..e43f694 100644 --- a/pie/src/trait_object/base.rs +++ b/pie/src/trait_object/base.rs @@ -39,7 +39,9 @@ impl HashObj for T { fn hash_obj(&self, mut state: &mut dyn Hasher) { self.hash(&mut state); } } -/// Clone `&self` into `Box` where `O` can be a trait object. -pub trait CloneBox { - fn clone_box(&self) -> Box; +/// Assert that given type is object-safe at compile-time. +macro_rules! const_assert_object_safe { + ($ty:ty) => { + const _: () = { let _: &$ty; assert!(true) }; + } } diff --git a/pie/src/trait_object/mod.rs b/pie/src/trait_object/mod.rs index 625dd42..689254c 100644 --- a/pie/src/trait_object/mod.rs +++ b/pie/src/trait_object/mod.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fmt::Debug; use std::hash::{Hash, Hasher}; @@ -5,18 +6,44 @@ use dyn_clone::DynClone; use base::{AsAny, EqObj, HashObj}; -use crate::{KeyBounds, ValueBounds}; -use crate::trait_object::base::CloneBox; +use crate::{Key, Value}; -pub(crate) mod collection; +#[macro_use] pub(crate) mod base; +pub(crate) mod collection; pub(crate) mod task; -/// Object safe [`KeyBounds`] proxy that can be cloned, equality compared, hashed, converted to [`Any`], and debug -/// formatted. +/// Object safe [`Value`] proxy that can be cloned, converted to [`Any`], and debug formatted. +pub trait ValueObj: DynClone + AsAny + Debug {} +const_assert_object_safe!(dyn ValueObj); +impl ValueObj for T {} +impl<'a, T: Value> From<&'a T> for &'a dyn ValueObj { + #[inline] + fn from(value: &'a T) -> Self { value as &dyn ValueObj } +} +impl Clone for Box { + #[inline] + fn clone(&self) -> Self { dyn_clone::clone_box(self.as_ref()) } +} +impl ToOwned for dyn ValueObj { + type Owned = Box; + #[inline] + fn to_owned(&self) -> Self::Owned { dyn_clone::clone_box(self) } +} +impl<'a> From<&'a dyn ValueObj> for Cow<'a, dyn ValueObj> { + #[inline] + fn from(value: &'a dyn ValueObj) -> Self { Cow::Borrowed(value) } +} +impl<'a> From> for Cow<'a, dyn ValueObj> { + #[inline] + fn from(value: Box) -> Self { Cow::Owned(value) } +} + +/// Object safe [`Key`] proxy that can be cloned, equality compared, hashed, converted to [`Any`], and debug formatted. pub trait KeyObj: DynClone + EqObj + HashObj + AsAny + Debug {} -impl KeyObj for T {} -impl<'a, T: KeyBounds> From<&'a T> for &'a dyn KeyObj { +const_assert_object_safe!(dyn KeyObj); +impl KeyObj for T {} +impl<'a, T: Key> From<&'a T> for &'a dyn KeyObj { #[inline] fn from(value: &'a T) -> Self { value as &dyn KeyObj } } @@ -25,6 +52,10 @@ impl PartialEq for dyn KeyObj { fn eq(&self, other: &Self) -> bool { self.eq_any(other.as_any()) } } impl Eq for dyn KeyObj {} +impl PartialEq for Box { + #[inline] + fn eq(&self, other: &dyn KeyObj) -> bool { self.as_ref().eq_any(other.as_any()) } +} impl Hash for dyn KeyObj { #[inline] fn hash(&self, state: &mut H) { self.hash_obj(state); } @@ -33,31 +64,131 @@ impl Clone for Box { #[inline] fn clone(&self) -> Self { dyn_clone::clone_box(self.as_ref()) } } -impl CloneBox for T { +impl ToOwned for dyn KeyObj { + type Owned = Box; #[inline] - fn clone_box(&self) -> Box { dyn_clone::clone_box(self) } + fn to_owned(&self) -> Self::Owned { dyn_clone::clone_box(self) } } -impl CloneBox for dyn KeyObj { +impl<'a> From<&'a dyn KeyObj> for Cow<'a, dyn KeyObj> { #[inline] - fn clone_box(&self) -> Box { dyn_clone::clone_box(self) } + fn from(value: &'a dyn KeyObj) -> Self { Cow::Borrowed(value) } } - -/// Object safe [`ValueBounds`] proxy that can be cloned, converted to [`Any`], and debug formatted. -pub trait ValueObj: DynClone + AsAny + Debug {} -impl ValueObj for T {} -impl<'a, T: ValueBounds> From<&'a T> for &'a dyn ValueObj { - #[inline] - fn from(value: &'a T) -> Self { value as &dyn ValueObj } -} -impl Clone for Box { +impl<'a> From> for Cow<'a, dyn KeyObj> { #[inline] - fn clone(&self) -> Self { dyn_clone::clone_box(self.as_ref()) } -} -impl CloneBox for T { - #[inline] - fn clone_box(&self) -> Box { dyn_clone::clone_box(self) } + fn from(value: Box) -> Self { Cow::Owned(value) } } -impl CloneBox for dyn ValueObj { - #[inline] - fn clone_box(&self) -> Box { dyn_clone::clone_box(self) } + +#[cfg(test)] +mod tests { + use std::hash::DefaultHasher; + + use crate::{Context, Task}; + + use super::*; + + #[test] + fn test_val_obj() { + #[derive(Clone, Debug)] + struct ATaskOutput(usize); + + let output_1 = ATaskOutput(1); + let output_2 = ATaskOutput(2); + let val_1: Box = Box::new(output_1.clone()); + let val_2: Box = Box::new(output_2.clone()); + + macro_rules! assert_debug_eq { + ($left:expr, $right:expr $(,)?) => { + assert_eq!(format!("{:?}", $left), format!("{:?}", $right)) + }; + } + macro_rules! assert_debug_ne { + ($left:expr, $right:expr $(,)?) => { + assert_ne!(format!("{:?}", $left), format!("{:?}", $right)) + }; + } + + // Eq, PartialEq - through Debug + assert_debug_eq!(val_1, val_1); + assert_debug_ne!(val_1, val_2); + assert_debug_eq!(&val_1, &val_1); + assert_debug_ne!(&val_1, &val_2); + assert_debug_eq!(val_1.as_ref(), val_1.as_ref()); + assert_debug_ne!(val_1.as_ref(), val_2.as_ref()); + // AsAny - through Debug + assert_debug_eq!(val_1.as_ref().as_any().downcast_ref::(), Some(&output_1)); + assert_debug_ne!(val_1.as_ref().as_any().downcast_ref::(), Some(&output_2)); + assert_debug_ne!(val_2.as_ref().as_any().downcast_ref::(), Some(&output_1)); + assert_debug_eq!(*val_1.clone().into_box_any().downcast::().unwrap(), output_1.clone()); + assert_debug_ne!(*val_1.clone().into_box_any().downcast::().unwrap(), output_2.clone()); + assert_debug_ne!(*val_2.clone().into_box_any().downcast::().unwrap(), output_1.clone()); + // Clone - through Debug + assert_debug_eq!(val_1, val_1.clone()); + assert_debug_ne!(val_1, val_2.clone()); + assert_debug_ne!(val_2, val_1.clone()); + assert_debug_eq!(val_1, val_1.to_owned()); + assert_debug_ne!(val_1, val_2.to_owned()); + assert_debug_ne!(val_2, val_1.to_owned()); + // Cow - through Debug + assert_debug_eq!(Cow::from(val_1.as_ref()), Cow::from(val_1.as_ref())); + assert_debug_ne!(Cow::from(val_1.as_ref()), Cow::from(val_2.as_ref())); + assert_debug_eq!(Cow::from(val_1.as_ref()).into_owned(), val_1); + assert_debug_ne!(Cow::from(val_1.as_ref()).into_owned(), val_2); + assert_debug_ne!(Cow::from(val_2.as_ref()).into_owned(), val_1); + // Debug + assert_eq!(format!("{:?}", val_1), format!("{:?}", output_1)); + assert_ne!(format!("{:?}", val_1), format!("{:?}", output_2)); + } + + #[test] + fn test_key_obj() { + #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] + struct ToLowerCase(String); + impl Task for ToLowerCase { + type Output = String; + fn execute(&self, _context: &mut C) -> Self::Output { + self.0.to_ascii_lowercase() + } + } + + let task_a = ToLowerCase("A".to_string()); + let task_b = ToLowerCase("B".to_string()); + let key_a: Box = Box::new(task_a.clone()); + let key_b: Box = Box::new(task_b.clone()); + + // Eq, PartialEq + assert_eq!(key_a, key_a); + assert_ne!(key_a, key_b); + assert_eq!(&key_a, &key_a); + assert_ne!(&key_a, &key_b); + assert_eq!(key_a.as_ref(), key_a.as_ref()); + assert_ne!(key_a.as_ref(), key_b.as_ref()); + // Hash + let mut hasher = DefaultHasher::new(); + assert_eq!(key_a.hash(&mut hasher), task_a.hash(&mut hasher)); + // AsAny + // Note: key is `Box` which also implements `AsAny`, but would fail to downcast. Need to first call + // `as_ref` to convert `Box` into `&dyn KeyObj` which succeeds the downcast. + assert_eq!(key_a.as_ref().as_any().downcast_ref::(), Some(&task_a)); + assert_ne!(key_a.as_ref().as_any().downcast_ref::(), Some(&task_b)); + assert_ne!(key_b.as_ref().as_any().downcast_ref::(), Some(&task_a)); + assert_eq!(*key_a.clone().into_box_any().downcast::().unwrap(), task_a.clone()); + assert_ne!(*key_a.clone().into_box_any().downcast::().unwrap(), task_b.clone()); + assert_ne!(*key_b.clone().into_box_any().downcast::().unwrap(), task_a.clone()); + // Clone + assert_eq!(key_a, key_a.clone()); + assert_ne!(key_a, key_b.clone()); + assert_ne!(key_b, key_a.clone()); + assert_eq!(key_a, key_a.to_owned()); + assert_ne!(key_a, key_b.to_owned()); + assert_ne!(key_b, key_a.to_owned()); + // Cow + assert_eq!(Cow::from(key_a.as_ref()), Cow::from(key_a.as_ref())); + assert_ne!(Cow::from(key_a.as_ref()), Cow::from(key_b.as_ref())); + assert_eq!(Cow::from(key_a.as_ref()).into_owned(), key_a); + assert_ne!(Cow::from(key_a.as_ref()).into_owned(), key_b); + assert_ne!(Cow::from(key_b.as_ref()).into_owned(), key_a); + // Debug + assert_eq!(format!("{:?}", key_a), format!("{:?}", task_a)); + assert_ne!(format!("{:?}", key_a), format!("{:?}", task_b)); + } } diff --git a/pie/src/trait_object/task.rs b/pie/src/trait_object/task.rs index d8007d5..2e5ebbd 100644 --- a/pie/src/trait_object/task.rs +++ b/pie/src/trait_object/task.rs @@ -1,10 +1,11 @@ +use std::borrow::Cow; +use std::fmt::Debug; use std::hash::{Hash, Hasher}; +use crate::{OutputChecker, Task}; use crate::context::bottom_up::BottomUpContext; use crate::context::top_down::TopDownContext; -use crate::Task; use crate::trait_object::{KeyObj, ValueObj}; -use crate::trait_object::base::CloneBox; /// Internal object safe [`Task`] proxy. Has execute methods for concrete [`Context`] implementations, instead of a /// generic method, due to object safety. @@ -13,6 +14,7 @@ pub trait TaskObj: KeyObj { fn execute_top_down(&self, context: &mut TopDownContext) -> Box; fn execute_bottom_up(&self, context: &mut BottomUpContext) -> Box; } +const_assert_object_safe!(dyn TaskObj); impl TaskObj for T { #[inline] fn as_key_obj(&self) -> &dyn KeyObj { self as &dyn KeyObj } @@ -25,7 +27,6 @@ impl TaskObj for T { Box::new(self.execute(context)) } } - impl<'a, T: Task> From<&'a T> for &'a dyn TaskObj { #[inline] fn from(value: &'a T) -> Self { value as &dyn TaskObj } @@ -35,6 +36,10 @@ impl PartialEq for dyn TaskObj { fn eq(&self, other: &Self) -> bool { self.eq_any(other.as_any()) } } impl Eq for dyn TaskObj {} +impl PartialEq for Box { + #[inline] + fn eq(&self, other: &dyn TaskObj) -> bool { self.as_ref().eq_any(other.as_any()) } +} impl Hash for dyn TaskObj { #[inline] fn hash(&self, state: &mut H) { self.hash_obj(state); } @@ -43,11 +48,66 @@ impl Clone for Box { #[inline] fn clone(&self) -> Self { dyn_clone::clone_box(self.as_ref()) } } -impl CloneBox for T { +impl ToOwned for dyn TaskObj { + type Owned = Box; #[inline] - fn clone_box(&self) -> Box { dyn_clone::clone_box(self) } + fn to_owned(&self) -> Self::Owned { dyn_clone::clone_box(self) } } -impl CloneBox for dyn TaskObj { +impl<'a> From<&'a dyn TaskObj> for Cow<'a, dyn TaskObj> { #[inline] - fn clone_box(&self) -> Box { dyn_clone::clone_box(self) } + fn from(value: &'a dyn TaskObj) -> Self { Cow::Borrowed(value) } +} +impl<'a> From> for Cow<'a, dyn TaskObj> { + #[inline] + fn from(value: Box) -> Self { Cow::Owned(value) } +} + + +/// Internal object safe [`OutputChecker`] proxy. +pub trait OutputCheckerObj: KeyObj { + fn stamp_obj(&self, output: &O) -> Box; + fn check_obj<'i>(&'i self, output: &'i O, stamp: &'i dyn ValueObj) -> Option>; +} +const_assert_object_safe!(dyn OutputCheckerObj<()>); +impl, O> OutputCheckerObj for C { + #[inline] + fn stamp_obj(&self, output: &O) -> Box { + Box::new(self.stamp(output)) + } + #[inline] + fn check_obj<'i>(&'i self, output: &'i O, stamp: &'i dyn ValueObj) -> Option> { + let stamp_typed = stamp.as_any().downcast_ref::() + .expect("BUG: non-matching stamp type"); + self.check(output, stamp_typed).map(|i| Box::new(i) as Box) + } +} + + +#[cfg(test)] +mod tests { + use crate::task::EqualsChecker; + + use super::*; + + #[test] + fn test_output_checker_obj() { + let output_1 = 1usize; + let output_2 = 2usize; + + let equals_checker = EqualsChecker; + let output_checker_obj: Box> = Box::new(equals_checker); + let stamp_obj_1 = output_checker_obj.stamp_obj(&output_1); + let stamp_obj_2 = output_checker_obj.stamp_obj(&output_2); + assert!(output_checker_obj.check_obj(&output_1, stamp_obj_1.as_ref()).is_none()); + assert!(output_checker_obj.check_obj(&output_2, stamp_obj_2.as_ref()).is_none()); + assert!(output_checker_obj.check_obj(&output_1, stamp_obj_2.as_ref()).is_some()); + assert!(output_checker_obj.check_obj(&output_2, stamp_obj_1.as_ref()).is_some()); + + let stamp_1 = equals_checker.stamp(&output_1); + let stamp_2 = equals_checker.stamp(&output_2); + assert!(output_checker_obj.check_obj(&output_1, &stamp_1).is_none()); + assert!(output_checker_obj.check_obj(&output_2, &stamp_2).is_none()); + assert!(output_checker_obj.check_obj(&output_1, &stamp_2).is_some()); + assert!(output_checker_obj.check_obj(&output_2, &stamp_1).is_some()); + } }