Skip to main content

oxilean_runtime/eval_error/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{
6    CollectingPanicHandler, ContextSnapshot, DiagCode, ErrorAccumulator, ErrorFilter, ErrorPolicy,
7    ErrorRenderer, ErrorSeverity, ErrorSource, ErrorTemplates, EvalError, EvalErrorBuilder,
8    EvalErrorChain, EvalErrorContext, EvalErrorKind, EvalErrorStats, EvalFrame, EvalQuota,
9    RecoveryStrategy, RenderStyle, RuntimeError, SilentPanicHandler, SourceSpan, SourcedError,
10    StackTrace,
11};
12
13#[cfg(test)]
14mod tests {
15    use super::*;
16    #[test]
17    fn test_source_span_display_with_file() {
18        let s = SourceSpan::new(10, 25, Some("foo.lean".to_string()));
19        assert_eq!(s.to_string(), "foo.lean:10..25");
20    }
21    #[test]
22    fn test_source_span_display_no_file() {
23        let s = SourceSpan::new(0, 5, None);
24        assert_eq!(s.to_string(), "0..5");
25    }
26    #[test]
27    fn test_source_span_len() {
28        let s = SourceSpan::new(3, 10, None);
29        assert_eq!(s.len(), 7);
30    }
31    #[test]
32    fn test_source_span_empty() {
33        let s = SourceSpan::synthetic();
34        assert!(s.is_empty());
35    }
36    #[test]
37    fn test_eval_frame_display() {
38        let frame = EvalFrame::new("myFn", SourceSpan::new(0, 5, Some("a.lean".to_string())));
39        let s = frame.to_string();
40        assert!(s.contains("myFn"));
41        assert!(s.contains("a.lean"));
42    }
43    #[test]
44    fn test_eval_frame_tail_call() {
45        let frame = EvalFrame::new("g", SourceSpan::synthetic()).tail_call();
46        assert!(frame.is_tail_call);
47        assert!(frame.to_string().contains("[tail]"));
48    }
49    #[test]
50    fn test_eval_error_display_div_zero() {
51        let err = EvalError::new(EvalErrorKind::DivisionByZero);
52        assert!(err.to_string().contains("division by zero"));
53    }
54    #[test]
55    fn test_eval_error_with_span() {
56        let span = SourceSpan::new(5, 10, Some("test.lean".to_string()));
57        let err = EvalError::new(EvalErrorKind::DivisionByZero).with_span(span);
58        let s = err.to_string();
59        assert!(s.contains("test.lean"));
60    }
61    #[test]
62    fn test_eval_error_with_frame() {
63        let span = SourceSpan::synthetic();
64        let frame = EvalFrame::new("foo", span.clone());
65        let err = EvalError::new(EvalErrorKind::DivisionByZero).with_frame(frame);
66        assert!(err.has_context());
67        assert!(err.to_string().contains("foo"));
68    }
69    #[test]
70    fn test_eval_error_with_hint() {
71        let err = EvalErrorBuilder::div_by_zero();
72        assert!(!err.hints.is_empty());
73        let s = err.to_string();
74        assert!(s.contains("hint:"));
75    }
76    #[test]
77    fn test_eval_error_with_note() {
78        let err = EvalErrorBuilder::sorry("myAxiom");
79        assert!(err.note.is_some());
80        assert!(err.to_string().contains("note:"));
81    }
82    #[test]
83    fn test_eval_error_type_mismatch_display() {
84        let err = EvalErrorBuilder::type_mismatch("Nat", "Bool");
85        assert!(err.to_string().contains("Nat"));
86        assert!(err.to_string().contains("Bool"));
87    }
88    #[test]
89    fn test_eval_error_to_runtime_error_div() {
90        let err = EvalErrorBuilder::div_by_zero();
91        let re = err.to_runtime_error();
92        assert_eq!(re, RuntimeError::DivisionByZero);
93    }
94    #[test]
95    fn test_eval_error_to_runtime_error_sorry() {
96        let err = EvalErrorBuilder::sorry("mySorry");
97        let re = err.to_runtime_error();
98        assert!(matches!(re, RuntimeError::SorryReached(_)));
99    }
100    #[test]
101    fn test_eval_error_builder_stack_overflow() {
102        let err = EvalErrorBuilder::stack_overflow(1000);
103        assert!(err.to_string().contains("1000"));
104        assert!(!err.hints.is_empty());
105    }
106    #[test]
107    fn test_eval_error_builder_index_oob() {
108        let err = EvalErrorBuilder::index_out_of_bounds(5, 3);
109        assert!(err.to_string().contains("5"));
110        assert!(err.to_string().contains("3"));
111    }
112    #[test]
113    fn test_eval_error_builder_fuel_exhausted() {
114        let err = EvalErrorBuilder::fuel_exhausted(500);
115        assert!(err.to_string().contains("500"));
116    }
117    #[test]
118    fn test_eval_error_builder_undefined_var() {
119        let err = EvalErrorBuilder::undefined_var("x");
120        assert!(err.to_string().contains("`x`"));
121    }
122    #[test]
123    fn test_eval_error_builder_undefined_global() {
124        let err = EvalErrorBuilder::undefined_global("Nat.add");
125        assert!(err.to_string().contains("Nat.add"));
126    }
127    #[test]
128    fn test_eval_error_builder_black_hole() {
129        let err = EvalErrorBuilder::black_hole("fibThunk");
130        assert!(err.to_string().contains("fibThunk"));
131        assert!(err.note.is_some());
132    }
133    #[test]
134    fn test_eval_error_multiple_frames() {
135        let err = EvalError::new(EvalErrorKind::DivisionByZero).with_frames(vec![
136            EvalFrame::new("a", SourceSpan::synthetic()),
137            EvalFrame::new("b", SourceSpan::synthetic()),
138        ]);
139        assert_eq!(err.frames.len(), 2);
140    }
141}
142/// A hook called when a runtime panic occurs.
143pub trait PanicHandler: Send + Sync {
144    /// Called when a panic error is encountered.
145    fn on_panic(&self, err: &EvalError);
146}
147#[cfg(test)]
148mod tests_extended {
149    use super::*;
150    #[test]
151    fn test_recovery_strategy_continuation() {
152        assert!(RecoveryStrategy::LogAndContinue.allows_continuation());
153        assert!(RecoveryStrategy::ReturnDefault.allows_continuation());
154        assert!(!RecoveryStrategy::Abort.allows_continuation());
155    }
156    #[test]
157    fn test_recovery_strategy_retry() {
158        let r = RecoveryStrategy::Retry { max_attempts: 3 };
159        assert!(r.is_retry());
160        assert!(r.allows_continuation());
161        assert_eq!(format!("{}", r), "retry(max=3)");
162    }
163    #[test]
164    fn test_error_policy_strict() {
165        let p = ErrorPolicy::strict();
166        let err = EvalErrorBuilder::div_by_zero();
167        assert_eq!(*p.strategy_for(&err), RecoveryStrategy::Abort);
168        assert!(!p.allows_continuation(&err));
169    }
170    #[test]
171    fn test_error_policy_lenient() {
172        let p = ErrorPolicy::lenient();
173        let err = EvalErrorBuilder::div_by_zero();
174        assert!(p.allows_continuation(&err));
175    }
176    #[test]
177    fn test_error_policy_custom() {
178        let p = ErrorPolicy::strict().with(
179            EvalErrorKind::DivisionByZero,
180            RecoveryStrategy::ReturnDefault,
181        );
182        let err = EvalErrorBuilder::div_by_zero();
183        assert!(p.allows_continuation(&err));
184    }
185    #[test]
186    fn test_eval_error_context_push_pop() {
187        let mut ctx = EvalErrorContext::new();
188        ctx.push("fn_a", SourceSpan::synthetic());
189        ctx.push("fn_b", SourceSpan::synthetic());
190        assert_eq!(ctx.depth(), 2);
191        ctx.pop();
192        assert_eq!(ctx.depth(), 1);
193    }
194    #[test]
195    fn test_eval_error_context_annotate() {
196        let mut ctx = EvalErrorContext::new();
197        ctx.push("helper", SourceSpan::synthetic());
198        let err = EvalError::new(EvalErrorKind::DivisionByZero);
199        let annotated = ctx.annotate(err);
200        assert!(annotated.has_context());
201    }
202    #[test]
203    fn test_eval_error_context_disabled() {
204        let mut ctx = EvalErrorContext::disabled();
205        ctx.push("f", SourceSpan::synthetic());
206        assert_eq!(ctx.depth(), 0);
207    }
208    #[test]
209    fn test_collecting_panic_handler() {
210        let handler = CollectingPanicHandler::new();
211        let err = EvalErrorBuilder::panic_msg("oops");
212        handler.on_panic(&err);
213        handler.on_panic(&err);
214        assert_eq!(handler.count(), 2);
215        let collected = handler.collected();
216        assert!(collected[0].contains("oops"));
217    }
218    #[test]
219    fn test_error_accumulator_basic() {
220        let mut acc = ErrorAccumulator::new();
221        assert!(acc.is_empty());
222        acc.push(EvalErrorBuilder::div_by_zero());
223        acc.push(EvalErrorBuilder::undefined_var("x"));
224        assert_eq!(acc.len(), 2);
225    }
226    #[test]
227    fn test_error_accumulator_limit() {
228        let mut acc = ErrorAccumulator::with_limit(2);
229        assert!(!acc.push(EvalErrorBuilder::div_by_zero()));
230        assert!(acc.push(EvalErrorBuilder::div_by_zero()));
231        assert!(acc.at_limit());
232    }
233    #[test]
234    fn test_error_accumulator_format_all() {
235        let mut acc = ErrorAccumulator::new();
236        acc.push(EvalErrorBuilder::div_by_zero());
237        let fmt = acc.format_all();
238        assert!(fmt.contains("[1]"));
239        assert!(fmt.contains("division by zero"));
240    }
241    #[test]
242    fn test_error_kind_predicates_fatal() {
243        assert!(EvalErrorKind::StackOverflow { max_depth: 1 }.is_fatal());
244        assert!(EvalErrorKind::FuelExhausted { limit: 0 }.is_fatal());
245        assert!(!EvalErrorKind::DivisionByZero.is_fatal());
246    }
247    #[test]
248    fn test_error_kind_predicates_logic() {
249        assert!(EvalErrorKind::Panic {
250            message: "x".into()
251        }
252        .is_logic_error());
253        assert!(EvalErrorKind::SorryReached { name: "s".into() }.is_logic_error());
254        assert!(!EvalErrorKind::DivisionByZero.is_logic_error());
255    }
256    #[test]
257    fn test_error_kind_predicates_type_error() {
258        assert!(EvalErrorKind::TypeMismatch {
259            expected: "Nat".into(),
260            got: "Bool".into()
261        }
262        .is_type_error());
263        assert!(!EvalErrorKind::DivisionByZero.is_type_error());
264    }
265    #[test]
266    fn test_error_kind_name() {
267        assert_eq!(
268            EvalErrorKind::DivisionByZero.kind_name(),
269            "division_by_zero"
270        );
271        assert_eq!(
272            EvalErrorKind::TypeMismatch {
273                expected: "a".into(),
274                got: "b".into()
275            }
276            .kind_name(),
277            "type_mismatch"
278        );
279    }
280    #[test]
281    fn test_eval_error_stats() {
282        let mut stats = EvalErrorStats::new();
283        assert!(!stats.has_errors());
284        stats.record(&EvalErrorBuilder::div_by_zero());
285        stats.record(&EvalErrorBuilder::div_by_zero());
286        stats.record(&EvalErrorBuilder::undefined_var("x"));
287        assert_eq!(stats.total(), 3);
288        assert_eq!(stats.count("division_by_zero"), 2);
289        assert_eq!(stats.count("undefined_variable"), 1);
290    }
291    #[test]
292    fn test_eval_error_stats_most_frequent() {
293        let mut stats = EvalErrorStats::new();
294        stats.record(&EvalErrorBuilder::div_by_zero());
295        stats.record(&EvalErrorBuilder::div_by_zero());
296        stats.record(&EvalErrorBuilder::undefined_var("x"));
297        let (kind, count) = stats
298            .most_frequent()
299            .expect("test operation should succeed");
300        assert_eq!(kind, "division_by_zero");
301        assert_eq!(count, 2);
302    }
303    #[test]
304    fn test_eval_error_stats_reset() {
305        let mut stats = EvalErrorStats::new();
306        stats.record(&EvalErrorBuilder::div_by_zero());
307        stats.reset();
308        assert_eq!(stats.total(), 0);
309        assert!(!stats.has_errors());
310    }
311    #[test]
312    fn test_stack_trace_from_error() {
313        let span = SourceSpan::new(0, 5, Some("test.lean".to_string()));
314        let err =
315            EvalError::new(EvalErrorKind::DivisionByZero).with_frame(EvalFrame::new("myFn", span));
316        let trace = StackTrace::from_error(&err);
317        assert_eq!(trace.depth(), 1);
318        assert!(trace.format().contains("division by zero"));
319        assert!(trace.format().contains("myFn"));
320    }
321    #[test]
322    fn test_stack_trace_empty() {
323        let err = EvalError::new(EvalErrorKind::DivisionByZero);
324        let trace = StackTrace::from_error(&err);
325        assert_eq!(trace.depth(), 0);
326        let fmt = trace.format();
327        assert!(!fmt.contains("call stack:"));
328    }
329    #[test]
330    fn test_stack_trace_display() {
331        let err = EvalErrorBuilder::undefined_var("foo");
332        let trace = StackTrace::from_error(&err);
333        let s = format!("{}", trace);
334        assert!(s.contains("undefined variable"));
335    }
336    #[test]
337    fn test_error_context_clear() {
338        let mut ctx = EvalErrorContext::new();
339        ctx.push("f", SourceSpan::synthetic());
340        ctx.push("g", SourceSpan::synthetic());
341        ctx.clear();
342        assert!(ctx.is_empty());
343    }
344    #[test]
345    fn test_error_context_max_frames() {
346        let mut ctx = EvalErrorContext::new();
347        ctx.max_frames = 2;
348        ctx.push("a", SourceSpan::synthetic());
349        ctx.push("b", SourceSpan::synthetic());
350        ctx.push("c", SourceSpan::synthetic());
351        assert_eq!(ctx.depth(), 2);
352    }
353    #[test]
354    fn test_eval_error_kind_resource() {
355        assert!(EvalErrorKind::StackOverflow { max_depth: 10 }.is_resource_error());
356        assert!(EvalErrorKind::FuelExhausted { limit: 100 }.is_resource_error());
357        assert!(!EvalErrorKind::DivisionByZero.is_resource_error());
358    }
359    #[test]
360    fn test_silent_panic_handler() {
361        let h = SilentPanicHandler;
362        h.on_panic(&EvalErrorBuilder::panic_msg("test"));
363    }
364    #[test]
365    fn test_recovery_strategy_display() {
366        assert_eq!(format!("{}", RecoveryStrategy::Abort), "abort");
367        assert_eq!(
368            format!("{}", RecoveryStrategy::ReturnDefault),
369            "return-default"
370        );
371        assert_eq!(
372            format!("{}", RecoveryStrategy::FallbackToSorry),
373            "fallback-to-sorry"
374        );
375        assert_eq!(
376            format!("{}", RecoveryStrategy::LogAndContinue),
377            "log-and-continue"
378        );
379    }
380}
381#[cfg(test)]
382mod tests_renderer {
383    use super::*;
384    #[test]
385    fn test_renderer_plain() {
386        let err = EvalErrorBuilder::div_by_zero();
387        let rendered = ErrorRenderer::plain().render(&err);
388        assert!(rendered.contains("division by zero"));
389    }
390    #[test]
391    fn test_renderer_compact() {
392        let err = EvalErrorBuilder::div_by_zero();
393        let rendered = ErrorRenderer::compact().render(&err);
394        assert!(rendered.contains("division by zero"));
395        assert!(!rendered.contains('\n'));
396    }
397    #[test]
398    fn test_renderer_compact_with_span() {
399        let span = SourceSpan::new(0, 5, Some("f.lean".to_string()));
400        let err = EvalError::new(EvalErrorKind::DivisionByZero).with_span(span);
401        let rendered = ErrorRenderer::compact().render(&err);
402        assert!(rendered.contains("f.lean"));
403    }
404    #[test]
405    fn test_renderer_ansi() {
406        let err = EvalErrorBuilder::div_by_zero();
407        let rendered = ErrorRenderer::new(RenderStyle::Ansi).render(&err);
408        assert!(rendered.contains("division by zero"));
409        assert!(rendered.contains('\x1b'));
410    }
411    #[test]
412    fn test_renderer_structured() {
413        let err = EvalErrorBuilder::div_by_zero();
414        let rendered = ErrorRenderer::new(RenderStyle::Structured).render(&err);
415        assert!(rendered.starts_with('{'));
416        assert!(rendered.contains("division_by_zero"));
417    }
418    #[test]
419    fn test_renderer_max_frames() {
420        let err = EvalError::new(EvalErrorKind::DivisionByZero).with_frames(vec![
421            EvalFrame::new("a", SourceSpan::synthetic()),
422            EvalFrame::new("b", SourceSpan::synthetic()),
423            EvalFrame::new("c", SourceSpan::synthetic()),
424        ]);
425        let rendered = ErrorRenderer::plain().with_max_frames(2).render(&err);
426        assert!(rendered.contains("1 more frames"));
427    }
428    #[test]
429    fn test_renderer_no_hints() {
430        let err = EvalErrorBuilder::div_by_zero();
431        let rendered = ErrorRenderer::plain().with_hints(false).render(&err);
432        assert!(!rendered.contains("hint:"));
433    }
434    #[test]
435    fn test_renderer_no_note() {
436        let err = EvalErrorBuilder::sorry("x");
437        let rendered = ErrorRenderer::plain().with_note(false).render(&err);
438        assert!(!rendered.contains("note:"));
439    }
440    #[test]
441    fn test_error_chain_basic() {
442        let chain = EvalErrorChain::new()
443            .push(EvalErrorBuilder::undefined_var("x"))
444            .push(EvalErrorBuilder::div_by_zero());
445        assert_eq!(chain.len(), 2);
446        assert!(chain
447            .root_cause()
448            .expect("test operation should succeed")
449            .to_string()
450            .contains("undefined variable"));
451        assert!(chain
452            .last_error()
453            .expect("test operation should succeed")
454            .to_string()
455            .contains("division by zero"));
456    }
457    #[test]
458    fn test_error_chain_format() {
459        let chain = EvalErrorChain::new()
460            .push(EvalErrorBuilder::div_by_zero())
461            .push(EvalErrorBuilder::panic_msg("cascaded"));
462        let s = chain.format();
463        assert!(s.contains("root cause"));
464        assert!(s.contains("caused"));
465    }
466    #[test]
467    fn test_error_chain_display() {
468        let chain = EvalErrorChain::new().push(EvalErrorBuilder::div_by_zero());
469        assert!(!chain.to_string().is_empty());
470    }
471    #[test]
472    fn test_error_filter_keep_fatal() {
473        let errors = vec![
474            EvalErrorBuilder::div_by_zero(),
475            EvalErrorBuilder::stack_overflow(100),
476            EvalErrorBuilder::undefined_var("x"),
477            EvalErrorBuilder::fuel_exhausted(500),
478        ];
479        let fatal = ErrorFilter::keep_fatal(errors);
480        assert_eq!(fatal.len(), 2);
481    }
482    #[test]
483    fn test_error_filter_keep_kind() {
484        let errors = vec![
485            EvalErrorBuilder::div_by_zero(),
486            EvalErrorBuilder::div_by_zero(),
487            EvalErrorBuilder::undefined_var("x"),
488        ];
489        let filtered = ErrorFilter::keep_kind(errors, "division_by_zero");
490        assert_eq!(filtered.len(), 2);
491    }
492    #[test]
493    fn test_error_filter_dedup_by_kind() {
494        let errors = vec![
495            EvalErrorBuilder::div_by_zero(),
496            EvalErrorBuilder::div_by_zero(),
497            EvalErrorBuilder::undefined_var("x"),
498        ];
499        let deduped = ErrorFilter::dedup_by_kind(errors);
500        assert_eq!(deduped.len(), 2);
501    }
502    #[test]
503    fn test_eval_error_has_span() {
504        let err = EvalError::new(EvalErrorKind::DivisionByZero);
505        assert!(!err.has_span());
506        let with_span = err.with_span(SourceSpan::synthetic());
507        assert!(with_span.has_span());
508    }
509    #[test]
510    fn test_eval_error_has_hints() {
511        let err = EvalError::new(EvalErrorKind::DivisionByZero);
512        assert!(!err.has_hints());
513        let with_hint = err.with_hint("fix it");
514        assert!(with_hint.has_hints());
515    }
516    #[test]
517    fn test_eval_error_compact() {
518        let err = EvalErrorBuilder::div_by_zero();
519        let s = err.compact();
520        assert!(s.contains("division by zero"));
521    }
522    #[test]
523    fn test_eval_error_matches() {
524        let err = EvalErrorBuilder::div_by_zero();
525        assert!(err.matches(|k| matches!(k, EvalErrorKind::DivisionByZero)));
526        assert!(!err.matches(|k| matches!(k, EvalErrorKind::StackOverflow { .. })));
527    }
528    #[test]
529    fn test_eval_error_context_max_frame_boundary() {
530        let mut ctx = EvalErrorContext::new();
531        ctx.max_frames = 3;
532        for i in 0..5 {
533            ctx.push(format!("f{}", i), SourceSpan::synthetic());
534        }
535        assert_eq!(ctx.depth(), 3);
536    }
537    #[test]
538    fn test_accumulator_into_errors() {
539        let mut acc = ErrorAccumulator::new();
540        acc.push(EvalErrorBuilder::div_by_zero());
541        let errors = acc.into_errors();
542        assert_eq!(errors.len(), 1);
543    }
544    #[test]
545    fn test_eval_error_stats_black_hole() {
546        let mut stats = EvalErrorStats::new();
547        stats.record(&EvalErrorBuilder::black_hole("th"));
548        assert_eq!(stats.count("black_hole"), 1);
549    }
550    #[test]
551    fn test_error_kind_io() {
552        let kind = EvalErrorKind::Io {
553            message: "read failed".into(),
554        };
555        assert_eq!(kind.kind_name(), "io");
556        assert!(!kind.is_fatal());
557        assert!(!kind.is_type_error());
558    }
559    #[test]
560    fn test_render_style_default() {
561        assert_eq!(RenderStyle::default(), RenderStyle::Plain);
562    }
563}
564/// Standard diagnostic codes for eval errors.
565pub mod diag_codes {
566    use super::DiagCode;
567    /// Division by zero.
568    pub const DIV_BY_ZERO: DiagCode = DiagCode {
569        prefix: 'E',
570        number: 1001,
571    };
572    /// Stack overflow.
573    pub const STACK_OVERFLOW: DiagCode = DiagCode {
574        prefix: 'E',
575        number: 1002,
576    };
577    /// Type mismatch.
578    pub const TYPE_MISMATCH: DiagCode = DiagCode {
579        prefix: 'E',
580        number: 1003,
581    };
582    /// Index out of bounds.
583    pub const INDEX_OOB: DiagCode = DiagCode {
584        prefix: 'E',
585        number: 1004,
586    };
587    /// Sorry reached.
588    pub const SORRY_REACHED: DiagCode = DiagCode {
589        prefix: 'W',
590        number: 2001,
591    };
592    /// Fuel exhausted.
593    pub const FUEL_EXHAUSTED: DiagCode = DiagCode {
594        prefix: 'E',
595        number: 1005,
596    };
597    /// Undefined variable.
598    pub const UNDEFINED_VAR: DiagCode = DiagCode {
599        prefix: 'E',
600        number: 1006,
601    };
602    /// Undefined global.
603    pub const UNDEFINED_GLOBAL: DiagCode = DiagCode {
604        prefix: 'E',
605        number: 1007,
606    };
607    /// Arithmetic overflow.
608    pub const ARITH_OVERFLOW: DiagCode = DiagCode {
609        prefix: 'E',
610        number: 1008,
611    };
612    /// Non-exhaustive match.
613    pub const NON_EXHAUSTIVE: DiagCode = DiagCode {
614        prefix: 'E',
615        number: 1009,
616    };
617    /// Panic.
618    pub const PANIC: DiagCode = DiagCode {
619        prefix: 'E',
620        number: 1010,
621    };
622    /// Unimplemented.
623    pub const UNIMPLEMENTED: DiagCode = DiagCode {
624        prefix: 'W',
625        number: 2002,
626    };
627    /// I/O error.
628    pub const IO_ERROR: DiagCode = DiagCode {
629        prefix: 'E',
630        number: 1011,
631    };
632    /// Black hole.
633    pub const BLACK_HOLE: DiagCode = DiagCode {
634        prefix: 'E',
635        number: 1012,
636    };
637}
638#[cfg(test)]
639mod tests_severity_quota {
640    use super::*;
641    #[test]
642    fn test_error_severity_ordering() {
643        assert!(ErrorSeverity::Info < ErrorSeverity::Warning);
644        assert!(ErrorSeverity::Warning < ErrorSeverity::Error);
645        assert!(ErrorSeverity::Error < ErrorSeverity::Critical);
646    }
647    #[test]
648    fn test_error_severity_display() {
649        assert_eq!(format!("{}", ErrorSeverity::Info), "info");
650        assert_eq!(format!("{}", ErrorSeverity::Critical), "critical");
651    }
652    #[test]
653    fn test_kind_default_severity() {
654        assert_eq!(
655            EvalErrorKind::DivisionByZero.default_severity(),
656            ErrorSeverity::Error
657        );
658        assert_eq!(
659            EvalErrorKind::StackOverflow { max_depth: 1 }.default_severity(),
660            ErrorSeverity::Critical
661        );
662        assert_eq!(
663            EvalErrorKind::SorryReached { name: "x".into() }.default_severity(),
664            ErrorSeverity::Warning
665        );
666    }
667    #[test]
668    fn test_eval_error_severity() {
669        let err = EvalErrorBuilder::stack_overflow(100);
670        assert!(err.is_critical());
671        let err2 = EvalErrorBuilder::div_by_zero();
672        assert!(!err2.is_critical());
673    }
674    #[test]
675    fn test_eval_error_is_at_least_warning() {
676        let err = EvalErrorBuilder::sorry("x");
677        assert!(err.is_at_least_warning());
678    }
679    #[test]
680    fn test_eval_quota_unlimited() {
681        let mut q = EvalQuota::unlimited();
682        assert!(q.tick().is_ok());
683        assert!(q.tick().is_ok());
684        assert!(!q.is_exhausted());
685        assert_eq!(q.remaining(), None);
686        assert_eq!(q.steps_taken(), 2);
687    }
688    #[test]
689    fn test_eval_quota_limited() {
690        let mut q = EvalQuota::limited(3);
691        assert!(q.tick().is_ok());
692        assert!(q.tick().is_ok());
693        assert!(q.tick().is_ok());
694        assert!(q.tick().is_err());
695        assert!(q.is_exhausted());
696    }
697    #[test]
698    fn test_eval_quota_consume_multiple() {
699        let mut q = EvalQuota::limited(10);
700        assert!(q.consume(5).is_ok());
701        assert_eq!(q.remaining(), Some(5));
702        assert!(q.consume(6).is_err());
703    }
704    #[test]
705    fn test_eval_quota_reset() {
706        let mut q = EvalQuota::limited(5);
707        q.consume(3).expect("test operation should succeed");
708        q.reset();
709        assert_eq!(q.remaining(), Some(5));
710        assert_eq!(q.steps_taken(), 0);
711    }
712    #[test]
713    fn test_eval_quota_default() {
714        let q = EvalQuota::default();
715        assert_eq!(q.remaining(), None);
716    }
717    #[test]
718    fn test_diag_code_display() {
719        let code = DiagCode::error(1001);
720        assert_eq!(format!("{}", code), "E1001");
721        let warn = DiagCode::warning(2001);
722        assert_eq!(format!("{}", warn), "W2001");
723    }
724    #[test]
725    fn test_diag_code_for_div_zero() {
726        let code = EvalErrorKind::DivisionByZero.diag_code();
727        assert_eq!(code.prefix, 'E');
728        assert_eq!(code.number, 1001);
729    }
730    #[test]
731    fn test_diag_code_for_sorry() {
732        let code = EvalErrorKind::SorryReached { name: "x".into() }.diag_code();
733        assert_eq!(code.prefix, 'W');
734    }
735    #[test]
736    fn test_error_source_display() {
737        assert_eq!(format!("{}", ErrorSource::Kernel), "kernel");
738        assert_eq!(
739            format!(
740                "{}",
741                ErrorSource::UserCode {
742                    decl_name: "foo".into()
743                }
744            ),
745            "user-code(foo)"
746        );
747        assert_eq!(
748            format!(
749                "{}",
750                ErrorSource::BytecodeInterp {
751                    chunk_name: "main".into(),
752                    ip: 42
753                }
754            ),
755            "bytecode-interp(main@42)"
756        );
757    }
758    #[test]
759    fn test_sourced_error_display() {
760        let se = SourcedError::unknown(EvalErrorBuilder::div_by_zero());
761        let s = format!("{}", se);
762        assert!(s.contains("[unknown]"));
763        assert!(s.contains("division by zero"));
764    }
765    #[test]
766    fn test_sourced_error_new() {
767        let se = SourcedError::new(
768            EvalErrorBuilder::undefined_var("x"),
769            ErrorSource::Elaborator,
770        );
771        assert_eq!(se.source, ErrorSource::Elaborator);
772    }
773    #[test]
774    fn test_error_filter_remove() {
775        let errors = vec![
776            EvalErrorBuilder::div_by_zero(),
777            EvalErrorBuilder::stack_overflow(10),
778        ];
779        let remaining = ErrorFilter::remove(errors, |e| e.kind.is_fatal());
780        assert_eq!(remaining.len(), 1);
781        assert_eq!(remaining[0].kind, EvalErrorKind::DivisionByZero);
782    }
783    #[test]
784    fn test_error_chain_empty() {
785        let chain = EvalErrorChain::new();
786        assert!(chain.is_empty());
787        assert!(chain.root_cause().is_none());
788    }
789    #[test]
790    fn test_diag_code_equality() {
791        let a = DiagCode::error(1001);
792        let b = DiagCode::error(1001);
793        let c = DiagCode::error(1002);
794        assert_eq!(a, b);
795        assert_ne!(a, c);
796    }
797    #[test]
798    fn test_eval_quota_fuel_error_kind() {
799        let mut q = EvalQuota::limited(1);
800        q.consume(1).expect("test operation should succeed");
801        let err = q.tick().unwrap_err();
802        assert!(matches!(err.kind, EvalErrorKind::FuelExhausted { .. }));
803    }
804}
805/// Groups a list of errors by their kind name.
806pub fn group_by_kind(errors: &[EvalError]) -> std::collections::HashMap<String, Vec<&EvalError>> {
807    let mut groups: std::collections::HashMap<String, Vec<&EvalError>> = Default::default();
808    for err in errors {
809        groups
810            .entry(err.kind.kind_name().to_string())
811            .or_default()
812            .push(err);
813    }
814    groups
815}
816#[cfg(test)]
817mod tests_templates {
818    use super::*;
819    #[test]
820    fn test_wrong_num_args() {
821        let err = ErrorTemplates::wrong_num_args(2, 3);
822        assert!(err.to_string().contains("2 arguments"));
823        assert!(!err.hints.is_empty());
824    }
825    #[test]
826    fn test_add_overflow() {
827        let err = ErrorTemplates::add_overflow(i64::MAX, 1);
828        assert!(err.to_string().contains("+"));
829    }
830    #[test]
831    fn test_mul_overflow() {
832        let err = ErrorTemplates::mul_overflow(1000, 1000);
833        assert!(err.to_string().contains("*"));
834    }
835    #[test]
836    fn test_empty_list_head() {
837        let err = ErrorTemplates::empty_list_head();
838        assert!(err.to_string().contains("head"));
839        assert!(err.note.is_some());
840    }
841    #[test]
842    fn test_assertion_failed() {
843        let err = ErrorTemplates::assertion_failed("x > 0");
844        assert!(err.to_string().contains("x > 0"));
845    }
846    #[test]
847    fn test_cast_failed() {
848        let err = ErrorTemplates::cast_failed("Bool", "Nat");
849        assert!(err.to_string().contains("Nat"));
850        assert!(!err.hints.is_empty());
851    }
852    #[test]
853    fn test_group_by_kind() {
854        let errors = vec![
855            EvalErrorBuilder::div_by_zero(),
856            EvalErrorBuilder::div_by_zero(),
857            EvalErrorBuilder::undefined_var("x"),
858        ];
859        let groups = group_by_kind(&errors);
860        assert_eq!(groups["division_by_zero"].len(), 2);
861        assert_eq!(groups["undefined_variable"].len(), 1);
862    }
863    #[test]
864    fn test_negative_index() {
865        let err = ErrorTemplates::negative_index(-5);
866        assert!(err.to_string().contains("-5"));
867    }
868    #[test]
869    fn test_empty_list_tail() {
870        let err = ErrorTemplates::empty_list_tail();
871        assert!(err.to_string().contains("tail"));
872    }
873}
874#[cfg(test)]
875mod tests_snapshot {
876    use super::*;
877    #[test]
878    fn test_context_snapshot_take() {
879        let mut ctx = EvalErrorContext::new();
880        ctx.push("fn_a", SourceSpan::synthetic());
881        ctx.push("fn_b", SourceSpan::synthetic());
882        let snap = ContextSnapshot::take(&ctx, 42);
883        assert_eq!(snap.frames.len(), 2);
884        assert_eq!(snap.step, 42);
885    }
886    #[test]
887    fn test_context_snapshot_into_error() {
888        let mut ctx = EvalErrorContext::new();
889        ctx.push("fn_x", SourceSpan::synthetic());
890        let snap = ContextSnapshot::take(&ctx, 0);
891        let err = snap.into_error(EvalErrorKind::DivisionByZero);
892        assert!(err.has_context());
893    }
894}