## Coverage Report

#### Created: 2025-09-08 21: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
    0
        fn new() -> Self {
    47
    0
            Self {
    48
    0
                inline: [0; 8],
    49
    0
                count: 0,
    50
    0
                overflow: None,
    51
    0
            }
    52
    0
        }
    53
    54
    0
        fn insert(&mut self, addr: usize) -> bool {
    55
            // Check inline storage first
    56
    0
            for i in 0..self.count.min(8) {
    57
    0
                if self.inline[i] == addr {
    58
    0
                    return false; // Already visited
    59
    0
                }
    60
            }
    61
    62
            // Check overflow if present
    63
    0
            if let Some(ref mut overflow) = self.overflow {
    64
    0
                return overflow.insert(addr);
    65
    0
            }
    66
    67
            // Add to inline if space available
    68
    0
            if self.count < 8 {
    69
    0
                self.inline[self.count] = addr;
    70
    0
                self.count += 1;
    71
    0
                true
    72
            } else {
    73
                // Migrate to overflow storage
    74
    0
                let mut overflow = HashSet::new();
    75
    0
                for &addr in &self.inline {
    76
    0
                    overflow.insert(addr);
    77
    0
                }
    78
    0
                overflow.insert(addr);
    79
    0
                self.overflow = Some(overflow);
    80
    0
                self.count += 1;
    81
    0
                true
    82
            }
    83
    0
        }
    84
    85
    0
        fn contains(&self, addr: usize) -> bool {
    86
            // Check inline
    87
    0
            for i in 0..self.count.min(8) {
    88
    0
                if self.inline[i] == addr {
    89
    0
                    return true;
    90
    0
                }
    91
            }
    92
    93
            // Check overflow
    94
    0
            if let Some(ref overflow) = self.overflow {
    95
    0
                overflow.contains(&addr)
    96
            } else {
    97
    0
                false
    98
            }
    99
    0
        }
    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
    0
        fn default() -> Self {
    117
    0
            Self {
    118
    0
                max_elements: 10,
    119
    0
                max_string_len: 100,
    120
    0
                use_colors: false,
    121
    0
                indent: "  ".to_string(),
    122
    0
            }
    123
    0
        }
    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
    0
        pub fn new() -> Self {
    170
    0
            Self::with_style(InspectStyle::default())
    171
    0
        }
    172
    173
        /// Create an inspector with custom style
    174
    0
        pub fn with_style(style: InspectStyle) -> Self {
    175
    0
            Self {
    176
    0
                depth: 0,
    177
    0
                max_depth: 10,
    178
    0
                visited: VisitSet::new(),
    179
    0
                budget: 10000,
    180
    0
                style,
    181
    0
                output: String::new(),
    182
    0
            }
    183
    0
        }
    184
    185
        /// Enter a new inspection context (cycle detection)
    186
    0
        pub fn enter<T>(&mut self, val: &T) -> bool {
    187
    0
            let addr = std::ptr::from_ref::<T>(val) as usize;
    188
    0
            if self.visited.contains(addr) {
    189
    0
                false // Cycle detected
    190
            } else {
    191
    0
                self.visited.insert(addr);
    192
    0
                self.depth += 1;
    193
    0
                true
    194
            }
    195
    0
        }
    196
    197
        /// Exit an inspection context
    198
    0
        pub fn exit(&mut self) {
    199
    0
            self.depth = self.depth.saturating_sub(1);
    200
    0
        }
    201
    202
        /// Check if budget allows continued inspection
    203
    0
        pub fn has_budget(&self) -> bool {
    204
    0
            self.budget > 0
    205
    0
        }
    206
    207
        /// Consume inspection budget
    208
    0
        pub fn consume_budget(&mut self, amount: usize) {
    209
    0
            self.budget = self.budget.saturating_sub(amount);
    210
    0
        }
    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
    0
        pub fn at_max_depth(&self) -> bool {
    219
    0
            self.depth >= self.max_depth
    220
    0
        }
    221
    }
    222
    223
    impl fmt::Write for Inspector {
    224
    0
        fn write_str(&mut self, s: &str) -> fmt::Result {
    225
    0
            self.consume_budget(s.len());
    226
    0
            self.output.push_str(s);
    227
    0
            Ok(())
    228
    0
        }
    229
    }
    230
    231
    // === Primitive Implementations ===
    232
    233
    impl Inspect for i32 {
    234
    0
        fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
    235
    0
            write!(inspector, "{self}")
    236
    0
        }
    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
    0
        fn inspect(&self, inspector: &mut Inspector) -> fmt::Result {
    283
    0
            if inspector.at_max_depth() {
    284
    0
                return write!(inspector, "[{} elements]", self.len());
    285
    0
            }
    286
    287
    0
            if !inspector.enter(self) {
    288
    0
                return write!(inspector, "[<circular>]");
    289
    0
            }
    290
    291
    0
            write!(inspector, "[")?;
    292
    293
    0
            let display_count = self.len().min(inspector.style.max_elements);
    294
    0
            for (i, item) in self.iter().take(display_count).enumerate() {
    295
    0
                if i > 0 {
    296
    0
                    write!(inspector, ", ")?;
    297
    0
                }
    298
    0
                item.inspect(inspector)?;
    299
    300
    0
                if !inspector.has_budget() {
    301
    0
                    write!(inspector, ", ...")?;
    302
    0
                    break;
    303
    0
                }
    304
            }
    305
    306
    0
            if self.len() > display_count {
    307
    0
                write!(inspector, ", ...{} more", self.len() - display_count)?;
    308
    0
            }
    309
    310
    0
            inspector.exit();
    311
    0
            write!(inspector, "]")
    312
    0
        }
    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
        fn test_primitive_inspection() {
    387
            let mut inspector = Inspector::new();
    388
    389
            42i32.inspect(&mut inspector).unwrap();
    390
            assert!(inspector.output.contains("42"));
    391
        }
    392
    393
        #[test]
    394
        fn test_vector_inspection() {
    395
            let vec = vec![1, 2, 3, 4, 5];
    396
            let mut inspector = Inspector::new();
    397
    398
            vec.inspect(&mut inspector).unwrap();
    399
            assert!(inspector.output.contains('['));
    400
            assert!(inspector.output.contains('1'));
    401
            assert!(inspector.output.contains('5'));
    402
        }
    403
    404
        #[test]
    405
        fn test_cycle_detection() {
    406
            // Can't easily test with standard types, but VisitSet works
    407
            let mut visited = VisitSet::new();
    408
    409
            assert!(visited.insert(0x1000));
    410
            assert!(!visited.insert(0x1000)); // Already visited
    411
            assert!(visited.insert(0x2000));
    412
            assert!(visited.contains(0x1000));
    413
            assert!(visited.contains(0x2000));
    414
            assert!(!visited.contains(0x3000));
    415
        }
    416
    417
        #[test]
    418
        fn test_overflow_visit_set() {
    419
            let mut visited = VisitSet::new();
    420
    421
            // Fill inline storage
    422
            for i in 0..10 {
    423
                visited.insert(i * 0x1000);
    424
            }
    425
    426
            // Should have migrated to overflow
    427
            assert!(visited.overflow.is_some());
    428
            assert!(visited.contains(0x0000));
    429
            assert!(visited.contains(0x9000));
    430
        }
    431
    }