1use std::collections::{HashMap, HashSet};
5use std::fmt::{self, Write};
6pub trait Inspect {
8 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result;
10 fn inspect_depth(&self) -> usize {
12 1
13 }
14}
15pub struct Inspector {
17 pub depth: usize,
19 pub max_depth: usize,
21 visited: VisitSet,
23 pub budget: usize,
25 pub style: InspectStyle,
27 pub output: String,
29}
30struct VisitSet {
32 inline: [usize; 8],
34 count: usize,
36 overflow: Option<HashSet<usize>>,
38}
39impl VisitSet {
40 fn new() -> Self {
41 Self {
42 inline: [0; 8],
43 count: 0,
44 overflow: None,
45 }
46 }
47 fn insert(&mut self, addr: usize) -> bool {
48 for i in 0..self.count.min(8) {
50 if self.inline[i] == addr {
51 return false; }
53 }
54 if let Some(ref mut overflow) = self.overflow {
56 return overflow.insert(addr);
57 }
58 if self.count < 8 {
60 self.inline[self.count] = addr;
61 self.count += 1;
62 true
63 } else {
64 let mut overflow = HashSet::new();
66 for &addr in &self.inline {
67 overflow.insert(addr);
68 }
69 overflow.insert(addr);
70 self.overflow = Some(overflow);
71 self.count += 1;
72 true
73 }
74 }
75 fn contains(&self, addr: usize) -> bool {
76 for i in 0..self.count.min(8) {
78 if self.inline[i] == addr {
79 return true;
80 }
81 }
82 if let Some(ref overflow) = self.overflow {
84 overflow.contains(&addr)
85 } else {
86 false
87 }
88 }
89}
90#[derive(Debug, Clone)]
92pub struct InspectStyle {
93 pub max_elements: usize,
95 pub max_string_len: usize,
97 pub use_colors: bool,
99 pub indent: String,
101}
102impl Default for InspectStyle {
103 fn default() -> Self {
104 Self {
105 max_elements: 10,
106 max_string_len: 100,
107 use_colors: false,
108 indent: " ".to_string(),
109 }
110 }
111}
112#[derive(Debug, Clone)]
114pub enum DisplayForm {
115 Atomic(String),
117 Composite(CompositeForm),
119 Reference(usize, Box<DisplayForm>),
121 Opaque(OpaqueHandle),
123}
124#[derive(Debug, Clone)]
126pub struct CompositeForm {
127 pub opener: &'static str,
129 pub elements: Vec<(Option<String>, DisplayForm)>,
131 pub closer: &'static str,
133 pub elided: Option<usize>,
135}
136#[derive(Debug, Clone)]
138pub struct OpaqueHandle {
139 pub type_name: String,
141 pub id: Option<String>,
143}
144impl Default for Inspector {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149impl Inspector {
150 pub fn new() -> Self {
160 Self::with_style(InspectStyle::default())
161 }
162 pub fn with_style(style: InspectStyle) -> Self {
173 Self {
174 depth: 0,
175 max_depth: 10,
176 visited: VisitSet::new(),
177 budget: 10000,
178 style,
179 output: String::new(),
180 }
181 }
182 pub fn enter<T>(&mut self, val: &T) -> bool {
184 let addr = std::ptr::from_ref::<T>(val) as usize;
185 if self.visited.contains(addr) {
186 false } else {
188 self.visited.insert(addr);
189 self.depth += 1;
190 true
191 }
192 }
193 pub fn exit(&mut self) {
204 self.depth = self.depth.saturating_sub(1);
205 }
206 pub fn has_budget(&self) -> bool {
216 self.budget > 0
217 }
218 pub fn consume_budget(&mut self, amount: usize) {
229 self.budget = self.budget.saturating_sub(amount);
230 }
231 pub fn depth(&self) -> usize {
241 self.depth
242 }
243 pub fn at_max_depth(&self) -> bool {
254 self.depth >= self.max_depth
255 }
256}
257impl fmt::Write for Inspector {
258 fn write_str(&mut self, s: &str) -> fmt::Result {
259 self.consume_budget(s.len());
260 self.output.push_str(s);
261 Ok(())
262 }
263}
264impl Inspect for i32 {
266 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
267 write!(inspector, "{self}")
268 }
269}
270impl Inspect for i64 {
271 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
272 write!(inspector, "{self}")
273 }
274}
275impl Inspect for f64 {
276 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
277 write!(inspector, "{self}")
278 }
279}
280impl Inspect for bool {
281 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
282 write!(inspector, "{self}")
283 }
284}
285impl Inspect for String {
286 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
287 if self.len() <= inspector.style.max_string_len {
288 write!(inspector, "\"{self}\"")
289 } else {
290 write!(
291 inspector,
292 "\"{}...\" ({} chars)",
293 &self[..inspector.style.max_string_len],
294 self.len()
295 )
296 }
297 }
298}
299impl Inspect for &str {
300 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
301 if self.len() <= inspector.style.max_string_len {
302 write!(inspector, "\"{self}\"")
303 } else {
304 write!(
305 inspector,
306 "\"{}...\" ({} chars)",
307 &self[..inspector.style.max_string_len],
308 self.len()
309 )
310 }
311 }
312}
313impl<T: Inspect> Inspect for Vec<T> {
314 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
315 if inspector.at_max_depth() {
316 return write!(inspector, "[{} elements]", self.len());
317 }
318 if !inspector.enter(self) {
319 return write!(inspector, "[<circular>]");
320 }
321 write!(inspector, "[")?;
322 let display_count = self.len().min(inspector.style.max_elements);
323 for (i, item) in self.iter().take(display_count).enumerate() {
324 if i > 0 {
325 write!(inspector, ", ")?;
326 }
327 item.inspect(inspector)?;
328 if !inspector.has_budget() {
329 write!(inspector, ", ...")?;
330 break;
331 }
332 }
333 if self.len() > display_count {
334 write!(inspector, ", ...{} more", self.len() - display_count)?;
335 }
336 inspector.exit();
337 write!(inspector, "]")
338 }
339}
340impl<K: Inspect, V: Inspect> Inspect for HashMap<K, V> {
341 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
342 if inspector.at_max_depth() {
343 return write!(inspector, "{{{} entries}}", self.len());
344 }
345 if !inspector.enter(self) {
346 return write!(inspector, "{{<circular>}}");
347 }
348 write!(inspector, "{{")?;
349 let display_count = self.len().min(inspector.style.max_elements);
350 for (i, (key, value)) in self.iter().take(display_count).enumerate() {
351 if i > 0 {
352 write!(inspector, ", ")?;
353 }
354 key.inspect(inspector)?;
355 write!(inspector, ": ")?;
356 value.inspect(inspector)?;
357 if !inspector.has_budget() {
358 write!(inspector, ", ...")?;
359 break;
360 }
361 }
362 if self.len() > display_count {
363 write!(inspector, ", ...{} more", self.len() - display_count)?;
364 }
365 inspector.exit();
366 write!(inspector, "}}")
367 }
368}
369impl<T: Inspect> Inspect for Option<T> {
370 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
371 match self {
372 Some(val) => {
373 write!(inspector, "Some(")?;
374 val.inspect(inspector)?;
375 write!(inspector, ")")
376 }
377 None => write!(inspector, "None"),
378 }
379 }
380}
381impl<T: Inspect, E: Inspect> Inspect for Result<T, E> {
382 fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
383 match self {
384 Ok(val) => {
385 write!(inspector, "Ok(")?;
386 val.inspect(inspector)?;
387 write!(inspector, ")")
388 }
389 Err(err) => {
390 write!(inspector, "Err(")?;
391 err.inspect(inspector)?;
392 write!(inspector, ")")
393 }
394 }
395 }
396}
397#[cfg(test)]
398mod tests {
399 use super::*;
400 #[test]
401 fn test_primitive_inspection() {
402 let mut inspector = Inspector::new();
403 42i32.inspect(&mut inspector).unwrap();
404 assert!(inspector.output.contains("42"));
405 }
406 #[test]
407 fn test_vector_inspection() {
408 let vec = vec![1, 2, 3, 4, 5];
409 let mut inspector = Inspector::new();
410 vec.inspect(&mut inspector).unwrap();
411 assert!(inspector.output.contains('['));
412 assert!(inspector.output.contains('1'));
413 assert!(inspector.output.contains('5'));
414 }
415 #[test]
416 fn test_cycle_detection() {
417 let mut visited = VisitSet::new();
419 assert!(visited.insert(0x1000));
420 assert!(!visited.insert(0x1000)); assert!(visited.insert(0x2000));
422 assert!(visited.contains(0x1000));
423 assert!(visited.contains(0x2000));
424 assert!(!visited.contains(0x3000));
425 }
426 #[test]
427 fn test_overflow_visit_set() {
428 let mut visited = VisitSet::new();
429 for i in 0..10 {
431 visited.insert(i * 0x1000);
432 }
433 assert!(visited.overflow.is_some());
435 assert!(visited.contains(0x0000));
436 assert!(visited.contains(0x9000));
437 }
438}
439#[cfg(test)]
440mod property_tests_inspect {
441 use proptest::proptest;
442
443 proptest! {
444 #[test]
446 fn test_new_never_panics(input: String) {
447 let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
449 let _ = std::panic::catch_unwind(|| {
451 });
454 }
455 }
456}