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}