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}