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}