tokit/utils/
sliced.rs

1use super::IntoComponents;
2
3/// A value paired with its slice metadata.
4///
5/// `Sliced<D, Src>` combines a value of type `D` with slice metadata of type `Src`.
6/// This is fundamental for tracking the origin of data, such as file names, URLs,
7/// module paths, or any other contextual information about where the data came from.
8/// Unlike [`Spanned`](super::Spanned) which tracks *location within* a slice,
9/// `Sliced` tracks *which* slice the data came from.
10///
11/// # Design
12///
13/// `Sliced` uses public fields for direct access, but also provides accessor methods
14/// for consistency. It implements `Deref` and `DerefMut` to allow transparent access
15/// to the inner data while keeping slice information available when needed.
16///
17/// # Common Patterns
18///
19/// ## Transparent Access via Deref
20///
21/// Thanks to `Deref`, you can call methods on the wrapped value directly:
22///
23/// ```rust
24/// use tokit::utils::Sliced;
25///
26/// let sliced_str = Sliced::new("main.rs", "hello world");
27///
28/// // Can call str methods directly
29/// assert_eq!(sliced_str.len(), 11);
30/// assert_eq!(sliced_str.to_uppercase(), "HELLO WORLD");
31///
32/// // But can still access the slice
33/// assert_eq!(sliced_str.slice(), "main.rs");
34/// ```
35///
36/// ## Tracking File Origins
37///
38/// ```rust,ignore
39/// use tokit::utils::Sliced;
40/// use std::path::PathBuf;
41///
42/// // Parse configuration from different slices
43/// let config_from_file = Sliced::new(
44///     PathBuf::from("/etc/app/config.toml"),
45///     parse_config(file_contents)
46/// );
47///
48/// let config_from_env = Sliced::new(
49///     PathBuf::from("<environment>"),
50///     parse_env_config()
51/// );
52///
53/// // Later, when reporting errors, you know where the config came from
54/// if let Err(e) = validate(&config_from_file) {
55///     eprintln!("Invalid config in {}: {}", config_from_file.slice().display(), e);
56/// }
57/// ```
58///
59/// ## Multi-File Compilation
60///
61/// ```rust,ignore
62/// use tokit::utils::Sliced;
63///
64/// struct Module {
65///     name: String,
66///     items: Vec<Item>,
67/// }
68///
69/// // Each module knows which file it came from
70/// let modules: Vec<Sliced<Module, String>> = vec![
71///     Sliced::new("src/main.rs".to_string(), parse_file("main.rs")),
72///     Sliced::new("src/lib.rs".to_string(), parse_file("lib.rs")),
73///     Sliced::new("src/utils.rs".to_string(), parse_file("utils.rs")),
74/// ];
75///
76/// // When linking, you can report cross-module errors with file context
77/// for module in &modules {
78///     for item in &module.items {
79///         if let Err(e) = resolve_item(item, &modules) {
80///             eprintln!("Error in {}: {}", module.slice(), e);
81///         }
82///     }
83/// }
84/// ```
85///
86/// ## Mapping Values While Preserving Source
87///
88/// ```rust
89/// use tokit::utils::Sliced;
90///
91/// let sliced_str = Sliced::new("input.txt", "42");
92///
93/// // Parse the string, keeping the same slice
94/// let parsed: Sliced<i32, &str> = sliced_str.map_data(|s| s.parse().unwrap());
95///
96/// assert_eq!(*parsed, 42);
97/// assert_eq!(parsed.slice(), "input.txt");
98/// ```
99///
100/// ## Building AST with File Context
101///
102/// ```rust,ignore
103/// use tokit::utils::{Sliced, Spanned, Span};
104/// use std::path::PathBuf;
105///
106/// // Combine Sliced and Spanned for complete location tracking
107/// type Located<T> = Sliced<Spanned<T>, PathBuf>;
108///
109/// enum Expr {
110///     Number(i64),
111///     Call { func: String, args: Vec<Located<Expr>> },
112/// }
113///
114/// // Each expression knows both which file it's in AND where in that file
115/// let expr: Located<Expr> = Sliced::new(
116///     PathBuf::from("src/main.rs"),
117///     Spanned::new(
118///         Span::new(100, 150),
119///         Expr::Call {
120///             func: "print".to_string(),
121///             args: vec![/* ... */],
122///         }
123///     )
124/// );
125///
126/// // Can report: "Error in src/main.rs at line 5, column 10"
127/// ```
128///
129/// ## Error Reporting with Source Context
130///
131/// ```rust,ignore
132/// fn type_error<T>(expected: &str, got: &Sliced<T, String>) -> Error
133/// where
134///     T: core::fmt::Debug
135/// {
136///     Error {
137///         message: format!(
138///             "Type error in {}: expected {}, got {:?}",
139///             got.slice(),
140///             expected,
141///             got.data()
142///         ),
143///         slice: got.slice().clone(),
144///     }
145/// }
146/// ```
147///
148/// ## Incremental Compilation
149///
150/// ```rust,ignore
151/// use tokit::utils::Sliced;
152/// use std::collections::HashMap;
153/// use std::time::SystemTime;
154///
155/// struct CachedModule {
156///     compiled: CompiledCode,
157///     timestamp: SystemTime,
158/// }
159///
160/// // Track which files need recompilation
161/// let mut cache: HashMap<String, CachedModule> = HashMap::new();
162///
163/// fn compile_if_needed(file: Sliced<String, String>) -> CompiledCode {
164///     let slice_file = file.slice();
165///     let modified = fs::metadata(slice_file).unwrap().modified().unwrap();
166///
167///     if let Some(cached) = cache.get(slice_file) {
168///         if cached.timestamp >= modified {
169///             return cached.compiled.clone();
170///         }
171///     }
172///
173///     // Recompile because slice changed
174///     let compiled = compile(&file.data);
175///     cache.insert(slice_file.clone(), CachedModule {
176///         compiled: compiled.clone(),
177///         timestamp: modified,
178///     });
179///     compiled
180/// }
181/// ```
182///
183/// # Trait Implementations
184///
185/// - **`Deref` / `DerefMut`**: Access the inner data transparently
186/// - **`Display`**: Delegates to the inner data's `Display` implementation
187/// - **`IntoComponents`**: Destructure into `(Src, D)` tuple
188///
189/// # Examples
190///
191/// ## Basic Usage
192///
193/// ```rust
194/// use tokit::utils::Sliced;
195///
196/// let sliced = Sliced::new("config.toml", "debug = true");
197///
198/// assert_eq!(sliced.slice(), "config.toml");
199/// assert_eq!(sliced.data(), &"debug = true");
200/// assert_eq!(*sliced, "debug = true"); // Via Deref
201/// ```
202///
203/// ## Destructuring
204///
205/// ```rust
206/// use tokit::utils::Sliced;
207///
208/// let sliced = Sliced::new("file.txt", 42);
209///
210/// let (slice, value) = sliced.into_components();
211/// assert_eq!(slice, "file.txt");
212/// assert_eq!(value, 42);
213/// ```
214///
215/// ## Mutable Access
216///
217/// ```rust
218/// use tokit::utils::Sliced;
219///
220/// let mut sliced = Sliced::new("input", 10);
221///
222/// // Modify the data
223/// *sliced += 5;
224/// assert_eq!(*sliced, 15);
225///
226/// // Modify the slice
227/// *sliced.slice_mut() = "modified";
228/// assert_eq!(sliced.slice(), "modified");
229/// ```
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
231pub struct Sliced<D, Src = ()> {
232  /// The slice covers the data.
233  pub(crate) slice: Src,
234  /// The wrapped data value.
235  pub(crate) data: D,
236}
237
238impl<D, Src> AsRef<Src> for Sliced<D, Src> {
239  #[cfg_attr(not(tarpaulin), inline(always))]
240  fn as_ref(&self) -> &Src {
241    self.slice_ref()
242  }
243}
244
245impl<D, Src> core::ops::Deref for Sliced<D, Src> {
246  type Target = D;
247
248  #[cfg_attr(not(tarpaulin), inline(always))]
249  fn deref(&self) -> &Self::Target {
250    &self.data
251  }
252}
253
254impl<D, Src> core::ops::DerefMut for Sliced<D, Src> {
255  #[cfg_attr(not(tarpaulin), inline(always))]
256  fn deref_mut(&mut self) -> &mut Self::Target {
257    &mut self.data
258  }
259}
260
261impl<D, Src> core::fmt::Display for Sliced<D, Src>
262where
263  D: core::fmt::Display,
264{
265  #[cfg_attr(not(tarpaulin), inline(always))]
266  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
267    self.data.fmt(f)
268  }
269}
270
271impl<D, Src> core::error::Error for Sliced<D, Src>
272where
273  D: core::error::Error,
274  Src: core::fmt::Debug,
275{
276}
277
278impl<D, Src> IntoComponents for Sliced<D, Src> {
279  type Components = (Src, D);
280
281  #[cfg_attr(not(tarpaulin), inline(always))]
282  fn into_components(self) -> Self::Components {
283    (self.slice, self.data)
284  }
285}
286
287impl<D, Src> Sliced<D, Src> {
288  /// Create a new sliced value.
289  #[cfg_attr(not(tarpaulin), inline(always))]
290  pub const fn new(slice: Src, data: D) -> Self {
291    Self { slice, data }
292  }
293
294  /// Get a copy of the slice.
295  ///
296  /// ## Example
297  ///
298  /// ```rust
299  /// use tokit::utils::Sliced;
300  ///
301  /// let sliced = Sliced::new("file.rs", "data");
302  /// assert_eq!(sliced.slice(), "file.rs");
303  /// ```
304  #[cfg_attr(not(tarpaulin), inline(always))]
305  pub const fn slice(&self) -> Src
306  where
307    Src: Copy,
308  {
309    self.slice
310  }
311
312  /// Get a reference to the slice.
313  ///
314  /// ## Example
315  ///
316  /// ```rust
317  /// use tokit::utils::Sliced;
318  ///
319  /// let sliced = Sliced::new("config.toml", "data");
320  /// assert_eq!(sliced.slice_ref(), &"config.toml");
321  /// ```
322  #[cfg_attr(not(tarpaulin), inline(always))]
323  pub const fn slice_ref(&self) -> &Src {
324    &self.slice
325  }
326
327  /// Get a mutable reference to the slice.
328  ///
329  /// ## Example
330  ///
331  /// ```rust
332  /// use tokit::utils::Sliced;
333  ///
334  /// let mut sliced = Sliced::new("old.txt", "data");
335  /// *sliced.slice_mut() = "new.txt";
336  /// assert_eq!(sliced.slice(), "new.txt");
337  /// ```
338  #[cfg_attr(not(tarpaulin), inline(always))]
339  pub const fn slice_mut(&mut self) -> &mut Src {
340    &mut self.slice
341  }
342
343  /// Get a reference to the data.
344  ///
345  /// ## Example
346  ///
347  /// ```rust
348  /// use tokit::utils::Sliced;
349  ///
350  /// let sliced = Sliced::new("file.txt", 42);
351  /// assert_eq!(*sliced.data(), 42);
352  /// ```
353  #[cfg_attr(not(tarpaulin), inline(always))]
354  pub const fn data(&self) -> &D {
355    &self.data
356  }
357
358  /// Get a mutable reference to the data.
359  ///
360  /// ## Example
361  ///
362  /// ```rust
363  /// use tokit::utils::Sliced;
364  ///
365  /// let mut sliced = Sliced::new("file.txt", 42);
366  /// *sliced.data_mut() = 100;
367  /// assert_eq!(*sliced.data(), 100);
368  /// ```
369  #[cfg_attr(not(tarpaulin), inline(always))]
370  pub const fn data_mut(&mut self) -> &mut D {
371    &mut self.data
372  }
373
374  /// Returns a reference to the slice and data.
375  ///
376  /// ## Example
377  ///
378  /// ```rust
379  /// use tokit::utils::Sliced;
380  ///
381  /// let sliced = Sliced::new(String::from("file.txt"), String::from("hello"));
382  /// let borrowed: Sliced<&String, &String> = sliced.as_ref();
383  /// assert_eq!(borrowed.data(), &"hello");
384  /// ```
385  #[cfg_attr(not(tarpaulin), inline(always))]
386  pub const fn as_ref(&self) -> Sliced<&D, &Src> {
387    Sliced {
388      slice: &self.slice,
389      data: &self.data,
390    }
391  }
392
393  /// Returns a mutable reference to the slice and data.
394  ///
395  /// ## Example
396  ///
397  /// ```rust
398  /// use tokit::utils::Sliced;
399  ///
400  /// let mut sliced = Sliced::new(String::from("file.txt"), String::from("hello"));
401  /// let borrowed: Sliced<&mut String, &mut String> = sliced.as_mut();
402  /// borrowed.data.push_str(" world");
403  /// assert_eq!(sliced.data(), &"hello world");
404  /// ```
405  #[cfg_attr(not(tarpaulin), inline(always))]
406  pub const fn as_mut(&mut self) -> Sliced<&mut D, &mut Src> {
407    Sliced {
408      slice: &mut self.slice,
409      data: &mut self.data,
410    }
411  }
412
413  /// Consume the sliced value and return the slice.
414  #[cfg_attr(not(tarpaulin), inline(always))]
415  pub fn into_slice(self) -> Src {
416    self.slice
417  }
418
419  /// Consume the sliced value and return the data.
420  #[cfg_attr(not(tarpaulin), inline(always))]
421  pub fn into_data(self) -> D {
422    self.data
423  }
424
425  /// Decompose the sliced value into its slice and data.
426  #[cfg_attr(not(tarpaulin), inline(always))]
427  pub fn into_components(self) -> (Src, D) {
428    (self.slice, self.data)
429  }
430
431  /// Map the data to a new value, preserving the slice.
432  #[inline]
433  pub fn map_data<F, U>(self, f: F) -> Sliced<U, Src>
434  where
435    F: FnOnce(D) -> U,
436  {
437    Sliced {
438      slice: self.slice,
439      data: f(self.data),
440    }
441  }
442}