From 380c6bc30c8a538485e7166f4444f29ec5fe14f9 Mon Sep 17 00:00:00 2001 From: phil Date: Mon, 30 Mar 2026 17:54:35 -0700 Subject: [PATCH] it's possible these lifetimes make sense --- src/mst/mod.rs | 2 +- src/mst/slice_tricks.rs | 64 ++++++++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/mst/mod.rs b/src/mst/mod.rs index 42f2a1a..a39413f 100644 --- a/src/mst/mod.rs +++ b/src/mst/mod.rs @@ -6,7 +6,7 @@ pub mod slice_tricks; use std::collections::BTreeMap; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Span { gap_before: bool, things: BTreeMap, // gap after diff --git a/src/mst/slice_tricks.rs b/src/mst/slice_tricks.rs index 629d81f..4e4e242 100644 --- a/src/mst/slice_tricks.rs +++ b/src/mst/slice_tricks.rs @@ -25,10 +25,10 @@ type Result = std::result::Result; /// /// there is probably a nice way to implement PartialOrd, but... we're just /// going to tack a `/` on the end and call it a day -#[derive(Debug, PartialEq, Eq)] -pub struct SortableCollection(Nsid<'static>); +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SortableCollection<'a>(Nsid<'a>); -impl Ord for SortableCollection { +impl Ord for SortableCollection<'_> { fn cmp(&self, other: &Self) -> Ordering { let (s, o) = (&self.0.as_str(), &other.0.as_str()); @@ -48,44 +48,56 @@ impl Ord for SortableCollection { } } -impl PartialOrd for SortableCollection { +impl PartialOrd for SortableCollection<'_> { fn partial_cmp(&self, other: &SortableCollection) -> Option { Some(self.cmp(other)) } } -impl<'a> From<&'a Nsid<'a>> for SortableCollection { - fn from(nsid: &'a Nsid<'a>) -> SortableCollection { - let s = nsid.to_string(); - SortableCollection(s.into()) +impl<'a> From<&'a Nsid<'a>> for SortableCollection<'a> { + fn from(nsid: &'a Nsid<'a>) -> SortableCollection<'a> { + SortableCollection(nsid.clone()) } } -impl From<&SortableCollection> for Nsid<'static> { +impl<'a> From> for SortableCollection<'a> { + fn from(nsid: Nsid<'a>) -> SortableCollection<'a> { + SortableCollection(nsid) + } +} + +impl<'a> From<&'a SortableCollection<'a>> for Nsid<'a> { /// go back to jacquard typed (unchecked) /// /// panics if missing the '/' suffix or if the nsid got messed up - fn from(SortableCollection(s): &SortableCollection) -> Nsid<'static> { + fn from(SortableCollection(s): &'a SortableCollection<'a>) -> Nsid<'a> { s.clone() } } /// represent the collections across a whole, possibly sparse, repo -type CollectionSpan = Span; +type CollectionSpan<'a> = Span>; -impl CollectionSpan { +impl<'a> CollectionSpan<'a> { /// get a list of NSIDs if the span has no gaps /// /// a CollectionSpan can be complete even if it contains gaps, as long as /// those gaps are *within* collection-bounding keys - fn complete(&self) -> Option>> { - self.is_complete() - .then(|| self.things.keys().map(Into::into).collect()) + fn complete(&self) -> Option>> { + if self.is_complete() { + return Some( + self.things + .keys() + .map(|c| c.0.clone()) // unsatisfying :/ + .collect(), + ); + } + None } /// whether it's possible that this span covers some NSIDs /// /// each NSID from the set must either be in span, or in a gap of it - pub fn could_cover(&self, collections: &BTreeSet>) -> bool { + pub fn could_cover(&self, collections: &BTreeSet>) -> bool { let mut candidates = collections.iter().map(Into::::into); let Some(mut candidate) = candidates.next() else { return true; // empty set can always be covered, even by a zero-gap @@ -134,7 +146,7 @@ impl CollectionSpan { } /// extract a span of collection NSIDs (with possible gaps) from a CAR slice -fn span_from_slice(car: &mut MemCar) -> Result { +fn span_from_slice(car: &mut MemCar) -> Result> { let mut prev_gap = false; let mut prev_collection = None; @@ -152,7 +164,7 @@ fn span_from_slice(car: &mut MemCar) -> Result { let (nsid_str, _) = key.split_once('/').ok_or_else(|| { MstSliceTricksError::InvalidData(format!("missing '/' in key: {key}")) })?; - let collection: Nsid<'_> = nsid_str + let collection: Nsid<'static> = nsid_str .parse() .map_err(|e| MstSliceTricksError::InvalidData(format!("nsid parse: {e}")))?; @@ -164,7 +176,7 @@ fn span_from_slice(car: &mut MemCar) -> Result { } prev_gap = false; - prev_collection = Some((&collection).into()); + prev_collection = Some(collection.into()); } if let Some(prev) = prev_collection { @@ -197,9 +209,9 @@ pub enum RepoCollections { /// the CAR slice provably covered every collection! Complete(BTreeSet>), /// the repo is likely very small - Tiny(CollectionSpan), + Tiny(CollectionSpan<'static>), /// non-tiny incomplete car, collections we know about - Otherwise(CollectionSpan), + Otherwise(CollectionSpan<'static>), } /// Assess a CAR slice @@ -225,7 +237,7 @@ pub fn report(car: &mut MemCar) -> Result { #[cfg(test)] mod tests { use super::*; - use jacquard_common::types::string::Nsid; + use jacquard_common::{IntoStatic, types::string::Nsid}; use std::collections::BTreeSet; // --- helpers shared across all tests --- @@ -234,14 +246,14 @@ mod tests { Nsid::new_owned(s).unwrap() } - fn make_span(gap_before: bool, things: &[(&str, bool)]) -> CollectionSpan { + fn make_span(gap_before: bool, things: &[(&str, bool)]) -> CollectionSpan<'static> { CollectionSpan { gap_before, things: things .iter() .map(|(k, v)| { let nsid = Nsid::from(k.to_string()); - ((&nsid).into(), *v) + (nsid.into_static().into(), *v) }) .collect(), } @@ -268,8 +280,8 @@ mod tests { Ordering::Greater, ), // the surprising one: `/` always follows nsid, sorts after `.` ] { - let us_nsid: SortableCollection = (&nsid(us)).into(); - let them_nsid: SortableCollection = (&nsid(them)).into(); + let us_nsid: SortableCollection = nsid(us).into(); + let them_nsid: SortableCollection = nsid(them).into(); assert_eq!( us_nsid.cmp(&them_nsid), expected, -- 2.51.2