From c57d8e0a605d3fda1e0d575b00db5df612a5acb2 Mon Sep 17 00:00:00 2001 From: Audun Halland Date: Thu, 28 Mar 2024 00:50:49 +0100 Subject: [PATCH] fix: Basic support for mutable output fixes #48 --- CHANGELOG.md | 2 ++ src/lib.rs | 7 ++++++ src/output.rs | 5 ++++ src/output/mut_lending.rs | 23 ++++++++++++++++++ src/output/shallow/option.rs | 32 ++++++++++++++++++------- src/output/shallow/result.rs | 35 ++++++++++++++++++++------- src/value_chain.rs | 26 +++++++++++++++++--- tests/it/basic.rs | 36 ++++++++++++++++++++++++++++ unimock_macros/src/unimock/output.rs | 20 ++++++++++++++-- 9 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 src/output/mut_lending.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 67f1ccf..fa1a9e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Added +- Basic support for mutable outputs ([#49](https://github.com/audunhalland/unimock/pull/49)) ## [0.6.2] - 2024-03-27 ### Fixed diff --git a/src/lib.rs b/src/lib.rs index 77f1606..a54f57b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -869,6 +869,13 @@ impl Unimock { self.value_chain.add(value) } + /// Convert the given value into a mutable reference. + /// + /// This can be useful when returning mutable references from `answers` functions. + pub fn make_mut(&mut self, value: T) -> &mut T { + self.value_chain.add_mut(value) + } + #[track_caller] fn from_assembler( assembler_result: Result, diff --git a/src/output.rs b/src/output.rs index d5e6f8c..21eca89 100644 --- a/src/output.rs +++ b/src/output.rs @@ -4,6 +4,7 @@ use crate::alloc::Box; pub(crate) mod deep; pub(crate) mod lending; +pub(crate) mod mut_lending; pub(crate) mod owning; pub(crate) mod shallow; pub(crate) mod static_ref; @@ -59,8 +60,12 @@ pub trait ReturnDefault { fn return_default() -> K::Return; } +/// A "marker" for mutable types +pub struct Mutable(pub(crate) T); + pub use deep::Deep; pub use lending::Lending; +pub use mut_lending::MutLending; pub use owning::Owning; pub use shallow::Shallow; pub use static_ref::StaticRef; diff --git a/src/output/mut_lending.rs b/src/output/mut_lending.rs new file mode 100644 index 0000000..53ec46e --- /dev/null +++ b/src/output/mut_lending.rs @@ -0,0 +1,23 @@ +use core::marker::PhantomData; + +use super::*; + +/// A type category for values lent out by Unimock. +#[doc(hidden)] +pub struct MutLending(core::marker::PhantomData T>); + +impl Kind for MutLending { + type Return = MutLent; +} + +pub struct MutLent(PhantomData); + +impl GetOutput for MutLent { + type Output<'u> = &'u mut T + where + Self: 'u; + + fn output(&self) -> Option> { + None + } +} diff --git a/src/output/shallow/option.rs b/src/output/shallow/option.rs index 999abea..3cef425 100644 --- a/src/output/shallow/option.rs +++ b/src/output/shallow/option.rs @@ -2,19 +2,25 @@ use core::borrow::Borrow; use crate::output::*; -type Mix = Shallow>; +type Ref = Shallow>; +type Mut = Shallow>; -type Response = Option + Send + Sync>>; +type RefResponse = Option + Send + Sync>>; +type MutResponse = Option + Send + Sync>>>; -impl Kind for Mix { - type Return = Response; +impl Kind for Ref { + type Return = RefResponse; } -impl Return for Mix { +impl Kind for Mut { + type Return = MutResponse; +} + +impl Return for Ref { type Type = Option + Send + Sync>>; } -impl GetOutput for Response { +impl GetOutput for RefResponse { type Output<'u> = Option<&'u T> where Self: 'u; @@ -24,13 +30,23 @@ impl GetOutput for Response { } } +impl GetOutput for MutResponse { + type Output<'u> = Option<&'u mut T> + where + Self: 'u; + + fn output(&self) -> Option> { + None + } +} + macro_rules! into { ($trait:ident, $method:ident) => { - impl $trait> for Option + impl $trait> for Option where T0: Borrow + Send + Sync + 'static, { - fn $method(self) -> OutputResult> { + fn $method(self) -> OutputResult> { Ok(match self { Some(val) => Some(Box::new(val)), None => None, diff --git a/src/output/shallow/result.rs b/src/output/shallow/result.rs index 9a525ad..bc16bf5 100644 --- a/src/output/shallow/result.rs +++ b/src/output/shallow/result.rs @@ -4,22 +4,28 @@ use core::borrow::Borrow; use crate::output::{owning::Owned, *}; -type Mix = Shallow>; +type Ref = Shallow>; +type Mut = Shallow>; -type Response = Result + Send + Sync>, Owned>; +type RefResponse = Result + Send + Sync>, Owned>; +type MutResponse = Result + Send + Sync>>, Owned>; -impl Kind for Mix { - type Return = Response; +impl Kind for Ref { + type Return = RefResponse; } -impl Return for Mix +impl Kind for Mut { + type Return = MutResponse; +} + +impl Return for Ref where Owning: Return, { type Type = Result + Send + Sync>, as Return>::Type>; } -impl GetOutput for Response { +impl GetOutput for RefResponse { type Output<'u> = Result<&'u T, E> where Self: 'u; @@ -32,14 +38,27 @@ impl GetOutput for Response { } } +impl GetOutput for MutResponse { + type Output<'u> = Result<&'u mut T, E> + where + Self: 'u; + + fn output(&self) -> Option> { + match self { + Ok(_) => None, + Err(err) => Some(Err(err.output()?)), + } + } +} + macro_rules! into { ($trait:ident, $method:ident) => { - impl $trait> for Result + impl $trait> for Result where T0: Borrow + Send + Sync + 'static, E0: $trait>, { - fn $method(self) -> OutputResult> { + fn $method(self) -> OutputResult> { Ok(match self { Ok(val) => Ok(Box::new(val)), Err(err) => Err(err.$method()?), diff --git a/src/value_chain.rs b/src/value_chain.rs index b8df754..668d7c5 100644 --- a/src/value_chain.rs +++ b/src/value_chain.rs @@ -8,7 +8,7 @@ use crate::alloc::Box; /// This allows retaining shared references to inserted values. #[derive(Default)] pub struct ValueChain { - root_node: OnceCell, + root: OnceCell, } impl ValueChain { @@ -18,8 +18,21 @@ impl ValueChain { node.value.as_ref().downcast_ref::().unwrap() } + pub fn add_mut(&mut self, value: T) -> &mut T { + // note: There is no need for keeping the old chain. + // All those references are out of scope when add_mut is called. + self.root = Node::new(value).into(); + + self.root + .get_mut() + .unwrap() + .value + .downcast_mut::() + .unwrap() + } + fn push_node(&self, mut new_node: Node) -> &Node { - let mut cell = &self.root_node; + let mut cell = &self.root; loop { match cell.try_insert(new_node) { Ok(new_node) => { @@ -36,7 +49,7 @@ impl ValueChain { impl Drop for ValueChain { fn drop(&mut self) { - if let Some(node) = self.root_node.take() { + if let Some(node) = self.root.take() { drop(node.value); let mut cell = node.next; @@ -73,3 +86,10 @@ fn it_works() { assert_eq!(&"", second); assert_eq!(&42.0, third); } + +#[test] +fn it_works_mut() { + let mut value_chain = ValueChain::default(); + let first = value_chain.add_mut(1); + *first += 1; +} diff --git a/tests/it/basic.rs b/tests/it/basic.rs index 8fabfca..2a3bb77 100644 --- a/tests/it/basic.rs +++ b/tests/it/basic.rs @@ -1193,3 +1193,39 @@ mod apply_fn { assert_eq!(1, b); } } + +mod mut_output { + use unimock::*; + + #[unimock(api = TraitMock)] + trait Trait { + fn ref_mut(&mut self) -> &mut i32; + fn option_mut(&mut self) -> Option<&mut i32>; + fn result_mut(&mut self) -> Result<&mut i32, ()>; + } + + #[test] + fn test_ref_mut() { + let mut u = Unimock::new( + TraitMock::ref_mut + .next_call(matching!()) + .answers(&|u| u.make_mut(42)), + ); + + let num = u.ref_mut(); + *num += 1; + } + + #[test] + fn test_option_mut() { + let mut u = Unimock::new( + TraitMock::option_mut + .next_call(matching!()) + .answers(&|u| Some(u.make_mut(42))), + ); + + if let Some(num) = u.option_mut() { + *num += 1; + } + } +} diff --git a/unimock_macros/src/unimock/output.rs b/unimock_macros/src/unimock/output.rs index 4f049f7..66ee6fb 100644 --- a/unimock_macros/src/unimock/output.rs +++ b/unimock_macros/src/unimock/output.rs @@ -66,6 +66,7 @@ pub enum OutputWrapping { pub enum OutputKind { Owning, SelfReference, + MutSelfReference, ParamReference, StaticReference, Shallow, @@ -81,6 +82,7 @@ impl OutputKind { match self { Self::Owning => "Owning", Self::SelfReference => "Lending", + Self::MutSelfReference => "MutLending", Self::ParamReference => "StaticRef", Self::StaticReference => "StaticRef", Self::Shallow => "Shallow", @@ -208,7 +210,13 @@ pub fn determine_owned_or_deep_output_structure( fn make_generic_kind(ty: syn::Type, attr: &Attr) -> (OutputKind, syn::Type) { match ty { - syn::Type::Reference(reference) => (OutputKind::SelfReference, *reference.elem), + syn::Type::Reference(reference) => { + if reference.mutability.is_some() { + (OutputKind::MutSelfReference, *reference.elem) + } else { + (OutputKind::SelfReference, *reference.elem) + } + } syn::Type::Path(mut path) => { let mut kind = OutputKind::Owning; @@ -295,12 +303,20 @@ fn determine_reference_ownership( "static" => OutputKind::StaticReference, _ => match find_param_lifetime(sig, &lifetime.ident) { Some(index) => match index { - 0 => OutputKind::SelfReference, + 0 => { + if type_reference.mutability.is_some() { + OutputKind::MutSelfReference + } else { + OutputKind::SelfReference + } + } _ => OutputKind::ParamReference, }, None => OutputKind::SelfReference, }, } + } else if type_reference.mutability.is_some() { + OutputKind::MutSelfReference } else { OutputKind::SelfReference }