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/// Options controlling how Monty behavior diverges from plain CPython.
6///
7/// Consumed when code is compiled: a `MontyRun` bakes the choices into the
8/// program at construction, while a `MontyRepl` stores them so every snippet
9/// fed to the session compiles the same way.
10#[derive(Debug, Clone, Copy, Default, serde::Serialize, serde::Deserialize)]
11pub struct CompileOptions {
12    /// Give failed `assert` statements pytest-style introspected messages,
13    /// deliberately diverging from CPython; see `limitations/assert.md`.
14    /// On by default with a 120-byte operand-repr truncation.
15    pub assert_message_annotations: AssertMessageAnnotations,
16}
17
18/// Controls the pytest-style introspected `assert` failure messages of
19/// [`CompileOptions::assert_message_annotations`].
20///
21/// The choice is baked in at compile time (whether the introspecting opcodes
22/// are emitted) but the truncation limit is applied at runtime, so it also
23/// travels with serialized sessions.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
25pub enum AssertMessageAnnotations {
26    /// Disable introspection; bare asserts use CPython's empty message.
27    Off,
28    /// Retain at most this many UTF-8 bytes per operand before any `…` suffix.
29    /// Non-zero because `0` encodes [`Off`](Self::Off) on the wire.
30    MaxBytes(NonZeroU32),
31}
32
33impl AssertMessageAnnotations {
34    /// Operand-repr truncation used by [`Default`] and `From<bool>`.
35    pub const DEFAULT_MAX_BYTES: NonZeroU32 = NonZeroU32::new(120).expect("120 is non-zero");
36
37    /// Whether the compiler should emit introspecting assert opcodes.
38    #[must_use]
39    pub fn enabled(self) -> bool {
40        !matches!(self, Self::Off)
41    }
42
43    /// Returns the wire value: `0` when disabled, otherwise the UTF-8 byte cap.
44    #[must_use]
45    pub fn max_bytes(self) -> u32 {
46        match self {
47            Self::Off => 0,
48            Self::MaxBytes(n) => n.get(),
49        }
50    }
51
52    /// Decodes the wire value: `0` is off and any other value is the byte cap.
53    #[must_use]
54    pub fn from_max_bytes(value: u32) -> Self {
55        match NonZeroU32::new(value) {
56            Some(n) => Self::MaxBytes(n),
57            None => Self::Off,
58        }
59    }
60}
61
62impl Default for AssertMessageAnnotations {
63    fn default() -> Self {
64        Self::MaxBytes(Self::DEFAULT_MAX_BYTES)
65    }
66}
67
68impl From<bool> for AssertMessageAnnotations {
69    /// `true` enables the 120-byte default; `false` disables annotations.
70    fn from(enabled: bool) -> Self {
71        if enabled { Self::default() } else { Self::Off }
72    }
73}