Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/runtime/inspect.rs
Line
Count
Source
1
//! Object inspection protocol for REPL display
2
//!
3
//! [OBJ-INSPECT-002] Implement consistent object introspection API
4
5
use std::collections::{HashMap, HashSet};
6
use std::fmt::{self, Write};
7
8
/// Object inspection trait for human-readable display in REPL
9
pub trait Inspect {
10
    /// Inspect the object, writing to the inspector
11
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result;
12
    
13
    /// Maximum recursion depth for this type
14
0
    fn inspect_depth(&self) -> usize {
15
0
        1
16
0
    }
17
}
18
19
/// Inspector manages inspection state and formatting
20
pub struct Inspector {
21
    /// Current recursion depth
22
    pub depth: usize,
23
    /// Maximum allowed depth
24
    pub max_depth: usize,
25
    /// Set of visited object addresses (cycle detection)
26
    visited: VisitSet,
27
    /// Complexity budget remaining
28
    pub budget: usize,
29
    /// Display style configuration
30
    pub style: InspectStyle,
31
    /// Output buffer
32
    pub output: String,
33
}
34
35
/// Optimized set for tracking visited objects
36
struct VisitSet {
37
    /// Inline storage for common case (<8 elements)
38
    inline: [usize; 8],
39
    /// Current count
40
    count: usize,
41
    /// Overflow storage for larger sets
42
    overflow: Option<HashSet<usize>>,
43
}
44
45
impl VisitSet {
46
4
    fn new() -> Self {
47
4
        Self {
48
4
            inline: [0; 8],
49
4
            count: 0,
50
4
            overflow: None,
51
4
        }
52
4
    }
53
    
54
14
    fn insert(&mut self, addr: usize) -> bool {
55
        // Check inline storage first
56
46
        for i in 0..
self.count14
.
min14
(8) {
57
46
            if self.inline[i] == addr {
58
1
                return false; // Already visited
59
45
            }
60
        }
61
        
62
        // Check overflow if present
63
13
        if let Some(
ref mut overflow1
) = self.overflow {
64
1
            return overflow.insert(addr);
65
12
        }
66
        
67
        // Add to inline if space available
68
12
        if self.count < 8 {
69
11
            self.inline[self.count] = addr;
70
11
            self.count += 1;
71
11
            true
72
        } else {
73
            // Migrate to overflow storage
74
1
            let mut overflow = HashSet::new();
75
9
            for &
addr8
in &self.inline {
76
8
                overflow.insert(addr);
77
8
            }
78
1
            overflow.insert(addr);
79
1
            self.overflow = Some(overflow);
80
1
            self.count += 1;
81
1
            true
82
        }
83
14
    }
84
    
85
6
    fn contains(&self, addr: usize) -> bool {
86
        // Check inline
87
14
        for i in 0..
self.count6
.
min6
(8) {
88
14
            if self.inline[i] == addr {
89
3
                return true;
90
11
            }
91
        }
92
        
93
        // Check overflow
94
3
        if let Some(
ref overflow1
) = self.overflow {
95
1
            overflow.contains(&addr)
96
        } else {
97
2
            false
98
        }
99
6
    }
100
}
101
102
/// Display style configuration
103
#[derive(Debug, Clone)]
104
pub struct InspectStyle {
105
    /// Maximum elements to display in collections
106
    pub max_elements: usize,
107
    /// Maximum string length before truncation
108
    pub max_string_len: usize,
109
    /// Use colors in output
110
    pub use_colors: bool,
111
    /// Indentation string
112
    pub indent: String,
113
}
114
115
impl Default for InspectStyle {
116
2
    fn default() -> Self {
117
2
        Self {
118
2
            max_elements: 10,
119
2
            max_string_len: 100,
120
2
            use_colors: false,
121
2
            indent: "  ".to_string(),
122
2
        }
123
2
    }
124
}
125
126
/// Canonical display forms for values
127
#[derive(Debug, Clone)]
128
pub enum DisplayForm {
129
    /// Atomic values (42, true, "hello")
130
    Atomic(String),
131
    /// Composite values ([1,2,3], {x: 10})
132
    Composite(CompositeForm),
133
    /// Reference values (&value@0x7fff)
134
    Reference(usize, Box<DisplayForm>),
135
    /// Opaque values (<fn>, <thread#42>)
136
    Opaque(OpaqueHandle),
137
}
138
139
/// Composite value display structure
140
#[derive(Debug, Clone)]
141
pub struct CompositeForm {
142
    /// Opening delimiter
143
    pub opener: &'static str,
144
    /// Elements with optional labels
145
    pub elements: Vec<(Option<String>, DisplayForm)>,
146
    /// Closing delimiter
147
    pub closer: &'static str,
148
    /// Number of elided elements
149
    pub elided: Option<usize>,
150
}
151
152
/// Handle for opaque values
153
#[derive(Debug, Clone)]
154
pub struct OpaqueHandle {
155
    /// Type name
156
    pub type_name: String,
157
    /// Optional identifier
158
    pub id: Option<String>,
159
}
160
161
impl Default for Inspector {
162
0
    fn default() -> Self {
163
0
        Self::new()
164
0
    }
165
}
166
167
impl Inspector {
168
    /// Create a new inspector with default settings
169
2
    pub fn new() -> Self {
170
2
        Self::with_style(InspectStyle::default())
171
2
    }
172
    
173
    /// Create an inspector with custom style
174
2
    pub fn with_style(style: InspectStyle) -> Self {
175
2
        Self {
176
2
            depth: 0,
177
2
            max_depth: 10,
178
2
            visited: VisitSet::new(),
179
2
            budget: 10000,
180
2
            style,
181
2
            output: String::new(),
182
2
        }
183
2
    }
184
    
185
    /// Enter a new inspection context (cycle detection)
186
1
    pub fn enter<T>(&mut self, val: &T) -> bool {
187
1
        let addr = std::ptr::from_ref::<T>(val) as usize;
188
1
        if self.visited.contains(addr) {
189
0
            false // Cycle detected
190
        } else {
191
1
            self.visited.insert(addr);
192
1
            self.depth += 1;
193
1
            true
194
        }
195
1
    }
196
    
197
    /// Exit an inspection context
198
1
    pub fn exit(&mut self) {
199
1
        self.depth = self.depth.saturating_sub(1);
200
1
    }
201
    
202
    /// Check if budget allows continued inspection
203
5
    pub fn has_budget(&self) -> bool {
204
5
        self.budget > 0
205
5
    }
206
    
207
    /// Consume inspection budget
208
12
    pub fn consume_budget(&mut self, amount: usize) {
209
12
        self.budget = self.budget.saturating_sub(amount);
210
12
    }
211
    
212
    /// Get current depth
213
0
    pub fn depth(&self) -> usize {
214
0
        self.depth
215
0
    }
216
    
217
    /// Check if at maximum depth
218
1
    pub fn at_max_depth(&self) -> bool {
219
1
        self.depth >= self.max_depth
220
1
    }
221
}
222
223
impl fmt::Write for Inspector {
224
12
    fn write_str(&mut self, s: &str) -> fmt::Result {
225
12
        self.consume_budget(s.len());
226
12
        self.output.push_str(s);
227
12
        Ok(())
228
12
    }
229
}
230
231
// === Primitive Implementations ===
232
233
impl Inspect for i32 {
234
6
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
235
6
        write!(inspector, "{self}")
236
6
    }
237
}
238
239
impl Inspect for i64 {
240
0
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
241
0
        write!(inspector, "{self}")
242
0
    }
243
}
244
245
impl Inspect for f64 {
246
0
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
247
0
        write!(inspector, "{self}")
248
0
    }
249
}
250
251
impl Inspect for bool {
252
0
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
253
0
        write!(inspector, "{self}")
254
0
    }
255
}
256
257
impl Inspect for String {
258
0
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
259
0
        if self.len() <= inspector.style.max_string_len {
260
0
            write!(inspector, "\"{self}\"")
261
        } else {
262
0
            write!(inspector, "\"{}...\" ({} chars)", 
263
0
                &self[..inspector.style.max_string_len], 
264
0
                self.len())
265
        }
266
0
    }
267
}
268
269
impl Inspect for &str {
270
0
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
271
0
        if self.len() <= inspector.style.max_string_len {
272
0
            write!(inspector, "\"{self}\"")
273
        } else {
274
0
            write!(inspector, "\"{}...\" ({} chars)", 
275
0
                &self[..inspector.style.max_string_len], 
276
0
                self.len())
277
        }
278
0
    }
279
}
280
281
impl<T: Inspect> Inspect for Vec<T> {
282
1
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
283
1
        if inspector.at_max_depth() {
284
0
            return write!(inspector, "[{} elements]", self.len());
285
1
        }
286
        
287
1
        if !inspector.enter(self) {
288
0
            return write!(inspector, "[<circular>]");
289
1
        }
290
        
291
1
        write!(inspector, "[")
?0
;
292
        
293
1
        let display_count = self.len().min(inspector.style.max_elements);
294
5
        for (i, item) in 
self.iter()1
.
take1
(
display_count1
).
enumerate1
() {
295
5
            if i > 0 {
296
4
                write!(inspector, ", ")
?0
;
297
1
            }
298
5
            item.inspect(inspector)
?0
;
299
            
300
5
            if !inspector.has_budget() {
301
0
                write!(inspector, ", ...")?;
302
0
                break;
303
5
            }
304
        }
305
        
306
1
        if self.len() > display_count {
307
0
            write!(inspector, ", ...{} more", self.len() - display_count)?;
308
1
        }
309
        
310
1
        inspector.exit();
311
1
        write!(inspector, "]")
312
1
    }
313
}
314
315
impl<K: Inspect, V: Inspect> Inspect for HashMap<K, V> {
316
0
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
317
0
        if inspector.at_max_depth() {
318
0
            return write!(inspector, "{{{} entries}}", self.len());
319
0
        }
320
        
321
0
        if !inspector.enter(self) {
322
0
            return write!(inspector, "{{<circular>}}");
323
0
        }
324
        
325
0
        write!(inspector, "{{")?;
326
        
327
0
        let display_count = self.len().min(inspector.style.max_elements);
328
0
        for (i, (key, value)) in self.iter().take(display_count).enumerate() {
329
0
            if i > 0 {
330
0
                write!(inspector, ", ")?;
331
0
            }
332
0
            key.inspect(inspector)?;
333
0
            write!(inspector, ": ")?;
334
0
            value.inspect(inspector)?;
335
            
336
0
            if !inspector.has_budget() {
337
0
                write!(inspector, ", ...")?;
338
0
                break;
339
0
            }
340
        }
341
        
342
0
        if self.len() > display_count {
343
0
            write!(inspector, ", ...{} more", self.len() - display_count)?;
344
0
        }
345
        
346
0
        inspector.exit();
347
0
        write!(inspector, "}}")
348
0
    }
349
}
350
351
impl<T: Inspect> Inspect for Option<T> {
352
0
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
353
0
        match self {
354
0
            Some(val) => {
355
0
                write!(inspector, "Some(")?;
356
0
                val.inspect(inspector)?;
357
0
                write!(inspector, ")")
358
            }
359
0
            None => write!(inspector, "None"),
360
        }
361
0
    }
362
}
363
364
impl<T: Inspect, E: Inspect> Inspect for Result<T, E> {
365
0
    fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
366
0
        match self {
367
0
            Ok(val) => {
368
0
                write!(inspector, "Ok(")?;
369
0
                val.inspect(inspector)?;
370
0
                write!(inspector, ")")
371
            }
372
0
            Err(err) => {
373
0
                write!(inspector, "Err(")?;
374
0
                err.inspect(inspector)?;
375
0
                write!(inspector, ")")
376
            }
377
        }
378
0
    }
379
}
380
381
#[cfg(test)]
382
mod tests {
383
    use super::*;
384
    
385
    #[test]
386
1
    fn test_primitive_inspection() {
387
1
        let mut inspector = Inspector::new();
388
        
389
1
        42i32.inspect(&mut inspector).unwrap();
390
1
        assert!(inspector.output.contains("42"));
391
1
    }
392
    
393
    #[test]
394
1
    fn test_vector_inspection() {
395
1
        let vec = vec![1, 2, 3, 4, 5];
396
1
        let mut inspector = Inspector::new();
397
        
398
1
        vec.inspect(&mut inspector).unwrap();
399
1
        assert!(inspector.output.contains('['));
400
1
        assert!(inspector.output.contains('1'));
401
1
        assert!(inspector.output.contains('5'));
402
1
    }
403
    
404
    #[test]
405
1
    fn test_cycle_detection() {
406
        // Can't easily test with standard types, but VisitSet works
407
1
        let mut visited = VisitSet::new();
408
        
409
1
        assert!(visited.insert(0x1000));
410
1
        assert!(!visited.insert(0x1000)); // Already visited
411
1
        assert!(visited.insert(0x2000));
412
1
        assert!(visited.contains(0x1000));
413
1
        assert!(visited.contains(0x2000));
414
1
        assert!(!visited.contains(0x3000));
415
1
    }
416
    
417
    #[test]
418
1
    fn test_overflow_visit_set() {
419
1
        let mut visited = VisitSet::new();
420
        
421
        // Fill inline storage
422
11
        for 
i10
in 0..10 {
423
10
            visited.insert(i * 0x1000);
424
10
        }
425
        
426
        // Should have migrated to overflow
427
1
        assert!(visited.overflow.is_some());
428
1
        assert!(visited.contains(0x0000));
429
1
        assert!(visited.contains(0x9000));
430
1
    }
431
}