Skip to main content

monty_types/
run_options.rs

1//! Compile-time configuration: [`CompileOptions`] and the
2//! [`AssertMessageAnnotations`] introspected-assert setting.
3
4use std::num::NonZeroU32;
5
6/// Byte length up to which a source is compiled without the pre-parse nesting
7/// scan; the default for [`CompileOptions::source_scan_threshold`].
8///
9/// ruff grows its parser stack on demand, outside the sandbox allocator, at
10/// roughly 2 KiB per nesting level, and a source cannot nest deeper than it is
11/// long. 4 KiB (about 40 lines of 100 characters) caps that untracked growth
12/// at ~8 MiB while sparing ordinary programs the extra lexer pass.
13pub const SOURCE_SCAN_THRESHOLD: usize = 4 * 1024;
14
15/// Options controlling how Monty behavior diverges from plain CPython.
16///
17/// Consumed when code is compiled: a `MontyRun` bakes the choices into the
18/// program at construction, while a `MontyRepl` stores them so every snippet
19/// fed to the session compiles the same way.
20#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
21pub struct CompileOptions {
22    /// Give failed `assert` statements pytest-style introspected messages,
23    /// deliberately diverging from CPython; see `limitations/assert.md`.
24    /// On by default with a 120-byte operand-repr truncation.
25    pub assert_message_annotations: AssertMessageAnnotations,
26    /// Sources longer than this many bytes are scanned for parser nesting
27    /// before ruff sees them (see `limitations/language.md`); `0` scans every
28    /// source and `usize::MAX` none. Defaults to [`SOURCE_SCAN_THRESHOLD`].
29    pub source_scan_threshold: usize,
30}
31
32impl Default for CompileOptions {
33    fn default() -> Self {
34        Self {
35            assert_message_annotations: AssertMessageAnnotations::default(),
36            source_scan_threshold: SOURCE_SCAN_THRESHOLD,
37        }
38    }
39}
40
41/// Controls the pytest-style introspected `assert` failure messages of
42/// [`CompileOptions::assert_message_annotations`].
43///
44/// The choice is baked in at compile time (whether the introspecting opcodes
45/// are emitted) but the truncation limit is applied at runtime, so it also
46/// travels with serialized sessions.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
48pub enum AssertMessageAnnotations {
49    /// Disable introspection; bare asserts use CPython's empty message.
50    Off,
51    /// Retain at most this many UTF-8 bytes per operand before any `…` suffix.
52    /// Non-zero because `0` encodes [`Off`](Self::Off) on the wire.
53    MaxBytes(NonZeroU32),
54}
55
56impl AssertMessageAnnotations {
57    /// Operand-repr truncation used by [`Default`] and `From<bool>`.
58    pub const DEFAULT_MAX_BYTES: NonZeroU32 = NonZeroU32::new(120).expect("120 is non-zero");
59
60    /// Whether the compiler should emit introspecting assert opcodes.
61    #[must_use]
62    pub fn enabled(self) -> bool {
63        !matches!(self, Self::Off)
64    }
65
66    /// Returns the wire value: `0` when disabled, otherwise the UTF-8 byte cap.
67    #[must_use]
68    pub fn max_bytes(self) -> u32 {
69        match self {
70            Self::Off => 0,
71            Self::MaxBytes(n) => n.get(),
72        }
73    }
74
75    /// Decodes the wire value: `0` is off and any other value is the byte cap.
76    #[must_use]
77    pub fn from_max_bytes(value: u32) -> Self {
78        match NonZeroU32::new(value) {
79            Some(n) => Self::MaxBytes(n),
80            None => Self::Off,
81        }
82    }
83}
84
85impl Default for AssertMessageAnnotations {
86    fn default() -> Self {
87        Self::MaxBytes(Self::DEFAULT_MAX_BYTES)
88    }
89}
90
91impl From<bool> for AssertMessageAnnotations {
92    /// `true` enables the 120-byte default; `false` disables annotations.
93    fn from(enabled: bool) -> Self {
94        if enabled { Self::default() } else { Self::Off }
95    }
96}