Skip to main content

hegel/
test_case.rs

1pub use crate::backend::{DataSource, DataSourceError};
2use crate::generators::Generator;
3use crate::runner::Mode;
4use ciborium::Value;
5use parking_lot::Mutex;
6use std::any::Any;
7use std::cell::RefCell;
8use std::collections::{HashMap, HashSet};
9use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
10use std::sync::Arc;
11
12use crate::generators::value;
13
14// We use the __IsTestCase trait internally to provide nice error messages for misuses of #[composite].
15// It should not be used by users.
16//
17// The idea is #[composite] calls __assert_is_test_case(<first param>), which errors with our on_unimplemented
18// message iff the first param does not have type TestCase.
19
20#[diagnostic::on_unimplemented(
21    // NOTE: worth checking if edits to this message should also be applied to the similar-but-different
22    // error message in #[composite] in hegel-macros.
23    message = "The first parameter in a #[composite] generator must have type TestCase.",
24    label = "This type does not match `TestCase`."
25)]
26pub trait __IsTestCase {}
27impl __IsTestCase for TestCase {}
28pub fn __assert_is_test_case<T: __IsTestCase>() {}
29
30pub(crate) const ASSUME_FAIL_STRING: &str = "__HEGEL_ASSUME_FAIL";
31
32/// The sentinel string used to identify overflow/StopTest panics.
33/// Distinct from ASSUME_FAIL_STRING so callers can tell user-initiated
34/// assumption failures apart from backend-initiated data exhaustion.
35pub(crate) const STOP_TEST_STRING: &str = "__HEGEL_STOP_TEST";
36
37/// The sentinel string used by `TestCase::repeat` to signal that its loop
38/// completed naturally (the collection said "stop" and no panic occurred
39/// inside the body). Because `repeat` returns `!`, it has no normal-return
40/// path; this panic is how it tells the runner "this test case finished
41/// successfully, record it as Valid".
42pub(crate) const LOOP_DONE_STRING: &str = "__HEGEL_LOOP_DONE";
43
44/// Panic with the appropriate sentinel for the given data source error.
45fn panic_on_data_source_error(e: DataSourceError) -> ! {
46    match e {
47        DataSourceError::StopTest => panic!("{}", STOP_TEST_STRING),
48        DataSourceError::Assume => panic!("{}", ASSUME_FAIL_STRING), // nocov
49        DataSourceError::ServerError(msg) => panic!("{}", msg),
50    }
51}
52
53pub(crate) struct TestCaseGlobalData {
54    is_last_run: bool,
55    mode: Mode,
56    /// Fine-grained lock over the state shared between clones of a
57    /// `TestCase`. Acquired briefly around each individual backend call
58    /// and around each mutation of the draw-tracking bookkeeping, not
59    /// around entire user-visible operations like a `draw`. The mutex is
60    /// non-reentrant; no method holds it while calling back into
61    /// `TestCase`.
62    shared: Mutex<SharedState>,
63}
64
65pub(crate) struct SharedState {
66    data_source: Box<dyn DataSource>,
67    draw_state: DrawState,
68}
69
70pub(crate) struct DrawState {
71    named_draw_counts: HashMap<String, usize>,
72    named_draw_repeatable: HashMap<String, bool>,
73    allocated_display_names: HashSet<String>,
74}
75
76#[derive(Clone)]
77pub(crate) struct TestCaseLocalData {
78    span_depth: usize,
79    indent: usize,
80    on_draw: OutputSink,
81}
82
83/// A handle to the current test case.
84///
85/// This is passed to `#[hegel::test]` functions and provides methods
86/// for drawing values, making assumptions, and recording notes.
87///
88/// # Example
89///
90/// ```no_run
91/// use hegel::generators as gs;
92///
93/// #[hegel::test]
94/// fn my_test(tc: hegel::TestCase) {
95///     let x: i32 = tc.draw(gs::integers());
96///     tc.assume(x > 0);
97///     tc.note(&format!("x = {}", x));
98/// }
99/// ```
100///
101/// # Threading
102///
103/// `TestCase` is `Send` but not `Sync`. To drive generation from another
104/// thread, clone the test case and move the clone. Clones share the same
105/// underlying backend connection — they are views onto one test case, not
106/// independent test cases.
107///
108/// ```no_run
109/// use hegel::generators as gs;
110///
111/// #[hegel::test]
112/// fn my_test(tc: hegel::TestCase) {
113///     let tc_worker = tc.clone();
114///     let handle = std::thread::spawn(move || {
115///         tc_worker.draw(gs::integers::<i32>())
116///     });
117///     let n = handle.join().unwrap();
118///     let _b: bool = tc.draw(gs::booleans());
119///     let _ = n;
120/// }
121/// ```
122///
123/// ## What is guaranteed
124///
125/// Individual backend operations (a single `generate`, `start_span`,
126/// `stop_span`, or pool/collection call) are serialised by a shared
127/// mutex, so the bytes on the wire to the backend stay well-formed no
128/// matter how clones are used across threads.
129///
130/// This is enough for patterns where threads do not race on generation —
131/// for example:
132///
133/// - Spawn a worker, let it draw, `join` it, then continue on the main
134///   thread.
135/// - Repeatedly spawn-and-join one worker at a time.
136/// - Any pattern where exactly one thread is drawing at a time, with a
137///   happens-before relationship (join, channel receive, barrier) between
138///   each thread's work.
139///
140/// ## What is not guaranteed
141///
142/// Concurrent generation will get progressively better over time, but
143/// right now should be considered a borderline-internal feature. If
144/// you do not know exactly what you're doing it probably won't work.
145///
146/// Two or more threads drawing concurrently from clones of the same
147/// `TestCase` is allowed by the type system but is **not deterministic**:
148/// the order in which draws interleave depends on thread scheduling, and
149/// the backend has no way to reproduce that order on replay. Composite
150/// draws are also not atomic with respect to other threads — another
151/// thread's draws can land between this thread's `start_span` and
152/// `stop_span`, corrupting the shrink-friendly span structure. In
153/// practice this means such tests may:
154///
155/// - Produce different values on successive runs of the same seed.
156/// - Shrink poorly or not at all.
157/// - Surface backend errors (e.g. `StopTest`) in one thread caused by
158///   another thread's draws exhausting the budget.
159///
160/// ## Panics inside spawned threads
161///
162/// If a worker thread panics with an assumption failure or a backend
163/// `StopTest`, that panic stays inside the thread's `JoinHandle` until
164/// the main thread joins it. The main thread is responsible for
165/// propagating (or suppressing) the panic — typically by calling
166/// `handle.join().unwrap()`, which resumes the panic on the main thread
167/// so Hegel's runner can observe it.
168pub struct TestCase {
169    global: Arc<TestCaseGlobalData>,
170    // RefCell makes `TestCase: !Sync`. Local data is per-clone: each clone gets
171    // its own span depth, indent, and on_draw. Concurrent use across threads
172    // therefore requires cloning, which is enforced by the `!Sync` bound.
173    local: RefCell<TestCaseLocalData>,
174}
175
176impl Clone for TestCase {
177    fn clone(&self) -> Self {
178        TestCase {
179            global: self.global.clone(),
180            local: RefCell::new(self.local.borrow().clone()),
181        }
182    }
183}
184
185impl std::fmt::Debug for TestCase {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        f.debug_struct("TestCase").finish_non_exhaustive()
188    }
189}
190
191/// A callback invoked for each line of draw/note output during the final replay.
192pub(crate) type OutputSink = Arc<dyn Fn(&str) + Send + Sync>;
193
194thread_local! {
195    static OUTPUT_OVERRIDE: RefCell<Option<OutputSink>> = const { RefCell::new(None) };
196}
197
198/// Install a custom output sink for the duration of `f`, replacing the usual
199/// `eprintln!` behavior of draw and note output. Intended for tests that want
200/// to capture what a test case would print.
201///
202/// While active, notes and draws from the final replay go to `sink` instead of
203/// stderr. Non-final test cases still drop their draw/note output as usual.
204#[doc(hidden)]
205pub fn with_output_override<R>(sink: OutputSink, f: impl FnOnce() -> R) -> R {
206    let prev = OUTPUT_OVERRIDE.with(|cell| cell.borrow_mut().replace(sink));
207    let result = f();
208    OUTPUT_OVERRIDE.with(|cell| *cell.borrow_mut() = prev);
209    result
210}
211
212fn panic_message(payload: &Box<dyn Any + Send>) -> String {
213    if let Some(s) = payload.downcast_ref::<&str>() {
214        s.to_string()
215    } else if let Some(s) = payload.downcast_ref::<String>() {
216        s.clone()
217    } else {
218        "Unknown panic".to_string() // nocov
219    }
220}
221
222impl TestCase {
223    pub(crate) fn new(data_source: Box<dyn DataSource>, is_last_run: bool, mode: Mode) -> Self {
224        let override_sink = OUTPUT_OVERRIDE.with(|cell| cell.borrow().clone());
225        let on_draw: OutputSink = match override_sink {
226            Some(sink) if is_last_run => sink,
227            _ if is_last_run => Arc::new(|msg| eprintln!("{}", msg)),
228            _ => Arc::new(|_| {}),
229        };
230        TestCase {
231            global: Arc::new(TestCaseGlobalData {
232                is_last_run,
233                mode,
234                shared: Mutex::new(SharedState {
235                    data_source,
236                    draw_state: DrawState {
237                        named_draw_counts: HashMap::new(),
238                        named_draw_repeatable: HashMap::new(),
239                        allocated_display_names: HashSet::new(),
240                    },
241                }),
242            }),
243            local: RefCell::new(TestCaseLocalData {
244                span_depth: 0,
245                indent: 0,
246                on_draw,
247            }),
248        }
249    }
250
251    pub(crate) fn mode(&self) -> Mode {
252        self.global.mode
253    }
254
255    /// Acquire the shared mutex for the duration of `f`.
256    ///
257    /// Held briefly around individual backend calls or draw-state updates,
258    /// never around whole user-visible operations. The mutex is
259    /// non-reentrant, so `f` must not call any other method that also
260    /// acquires the shared mutex.
261    fn with_shared<R>(&self, f: impl FnOnce(&mut SharedState) -> R) -> R {
262        let mut guard = self.global.shared.lock();
263        f(&mut guard)
264    }
265
266    /// Draw a value from a generator.
267    ///
268    /// # Example
269    ///
270    /// ```no_run
271    /// use hegel::generators as gs;
272    ///
273    /// #[hegel::test]
274    /// fn my_test(tc: hegel::TestCase) {
275    ///     let x: i32 = tc.draw(gs::integers());
276    ///     let s: String = tc.draw(gs::text());
277    /// }
278    /// ```
279    ///
280    /// Note: when run inside a `#[hegel::test]`, `draw()` will typically be
281    /// rewritten to `__draw_named()` with an appropriate variable name
282    /// in order to give better test output.
283    pub fn draw<T: std::fmt::Debug>(&self, generator: impl Generator<T>) -> T {
284        self.__draw_named(generator, "draw", true)
285    }
286
287    /// Draw a value from a generator with a specific name for output.
288    ///
289    /// When `repeatable` is true, a counter suffix is appended (e.g. `x_1`, `x_2`).
290    /// When `repeatable` is false, reusing the same name panics.
291    ///
292    /// Using the same name with different values of `repeatable` is an error.
293    ///
294    /// On the final replay of a failing test case, this prints:
295    /// - `let name = value;` (when not repeatable)
296    /// - `let name_N = value;` (when repeatable)
297    ///
298    /// Not intended for direct use. This is the target that `#[hegel::test]` rewrites `draw()`
299    /// calls to where appropriate.
300    pub fn __draw_named<T: std::fmt::Debug>(
301        &self,
302        generator: impl Generator<T>,
303        name: &str,
304        repeatable: bool,
305    ) -> T {
306        let value = generator.do_draw(self);
307        if self.local.borrow().span_depth == 0 {
308            self.record_named_draw(&value, name, repeatable);
309        }
310        value
311    }
312
313    /// Draw a value from a generator without recording it in the output.
314    ///
315    /// Unlike [`draw`](Self::draw), this does not require `T: Debug` and
316    /// will not print the value in the failing-test summary.
317    pub fn draw_silent<T>(&self, generator: impl Generator<T>) -> T {
318        generator.do_draw(self)
319    }
320
321    /// Assume a condition is true. If false, reject the current test input.
322    ///
323    /// # Example
324    ///
325    /// ```no_run
326    /// use hegel::generators as gs;
327    ///
328    /// #[hegel::test]
329    /// fn my_test(tc: hegel::TestCase) {
330    ///     let age: u32 = tc.draw(gs::integers());
331    ///     tc.assume(age >= 18);
332    /// }
333    /// ```
334    pub fn assume(&self, condition: bool) {
335        if !condition {
336            self.reject();
337        }
338    }
339
340    /// Reject the current test input unconditionally.
341    ///
342    /// Equivalent to `assume(false)`, but with a `!` return type so that code
343    /// following the call is statically known to be unreachable.
344    ///
345    /// # Example
346    ///
347    /// ```no_run
348    /// use hegel::generators as gs;
349    ///
350    /// #[hegel::test]
351    /// fn my_test(tc: hegel::TestCase) {
352    ///     let n: i32 = tc.draw(gs::integers());
353    ///     let positive: u32 = match u32::try_from(n) {
354    ///         Ok(v) => v,
355    ///         Err(_) => tc.reject(),
356    ///     };
357    ///     let _ = positive;
358    /// }
359    /// ```
360    pub fn reject(&self) -> ! {
361        panic!("{}", ASSUME_FAIL_STRING);
362    }
363
364    /// Note a message which will be displayed with the reported failing test case.
365    ///
366    /// Only prints during the final replay of a failing test case.
367    ///
368    /// # Example
369    ///
370    /// ```no_run
371    /// use hegel::generators as gs;
372    ///
373    /// #[hegel::test]
374    /// fn my_test(tc: hegel::TestCase) {
375    ///     let x: i32 = tc.draw(gs::integers());
376    ///     tc.note(&format!("Generated x = {}", x));
377    /// }
378    /// ```
379    pub fn note(&self, message: &str) {
380        if !self.global.is_last_run {
381            return;
382        }
383        let local = self.local.borrow();
384        let indent = local.indent;
385        (local.on_draw)(&format!("{:indent$}{}", "", message, indent = indent));
386    }
387
388    /// Run `body` in a loop that should runs "logically infinitely" or until
389    /// error. Roughly equivalent to a `loop` but with better interaction with
390    /// the test runner: This loop will never exit until the test case completes.
391    ///
392    /// At the start of each iteration a `// Loop iteration N` note is emitted
393    /// into the failing-test replay output.
394    ///
395    /// # Example
396    ///
397    /// ```no_run
398    /// use hegel::generators as gs;
399    ///
400    /// #[hegel::test]
401    /// fn my_test(tc: hegel::TestCase) {
402    ///     let mut total: i32 = 0;
403    ///     tc.repeat(|| {
404    ///         let n: i32 = tc.draw(gs::integers().min_value(0).max_value(10));
405    ///         total += n;
406    ///         assert!(total >= 0);
407    ///     });
408    /// }
409    /// ```
410    pub fn repeat<F: FnMut()>(&self, mut body: F) -> ! {
411        if self.global.mode == Mode::SingleTestCase {
412            self.repeat_single_test_case(&mut body);
413        }
414        self.repeat_property_test(&mut body);
415    }
416
417    fn repeat_single_test_case(&self, body: &mut dyn FnMut()) -> ! {
418        let mut iteration: u64 = 0;
419        loop {
420            iteration += 1;
421            self.note(&format!("// Repetition #{}", iteration));
422
423            let prev_indent = self.local.borrow().indent;
424            self.local.borrow_mut().indent = prev_indent + 2;
425            body();
426            self.local.borrow_mut().indent = prev_indent;
427        }
428    }
429
430    fn repeat_property_test(&self, body: &mut dyn FnMut()) -> ! {
431        use crate::generators::{booleans, integers};
432
433        const MAX_SAFE_MIN_SIZE: usize = 1 << 40;
434        let min_size = self.draw_silent(integers::<usize>().max_value(MAX_SAFE_MIN_SIZE));
435
436        let mut collection = Collection::new(self, min_size, None);
437        let mut iteration: u64 = 0;
438
439        while collection.more() {
440            iteration += 1;
441            self.note(&format!("// Repetition #{}", iteration));
442
443            let prev_indent = self.local.borrow().indent;
444            self.local.borrow_mut().indent = prev_indent + 2;
445            let result = catch_unwind(AssertUnwindSafe(&mut *body));
446            self.local.borrow_mut().indent = prev_indent;
447
448            match result {
449                Ok(()) => {}
450                Err(e) => {
451                    let msg = panic_message(&e);
452                    if msg == ASSUME_FAIL_STRING {
453                    } else if msg == STOP_TEST_STRING {
454                        resume_unwind(e);
455                    } else {
456                        self.draw_silent(booleans());
457                        resume_unwind(e);
458                    }
459                }
460            }
461        }
462
463        panic!("{}", LOOP_DONE_STRING);
464    }
465
466    pub(crate) fn child(&self, extra_indent: usize) -> Self {
467        let local = self.local.borrow();
468        TestCase {
469            global: self.global.clone(),
470            local: RefCell::new(TestCaseLocalData {
471                span_depth: 0,
472                indent: local.indent + extra_indent,
473                on_draw: local.on_draw.clone(),
474            }),
475        }
476    }
477
478    fn record_named_draw<T: std::fmt::Debug>(&self, value: &T, name: &str, repeatable: bool) {
479        let display_name = self.with_shared(|shared| {
480            let draw_state = &mut shared.draw_state;
481
482            match draw_state.named_draw_repeatable.get(name) {
483                Some(&prev) if prev != repeatable => {
484                    panic!(
485                        "__draw_named: name {:?} used with inconsistent repeatable flag (was {}, now {}). \
486                        If you have not called __draw_named deliberately yourself, this is likely a bug in \
487                        hegel. Please file a bug report at https://github.com/hegeldev/hegel-rust/issues",
488                        name, prev, repeatable
489                    );
490                }
491                _ => {
492                    draw_state
493                        .named_draw_repeatable
494                        .insert(name.to_string(), repeatable);
495                }
496            }
497
498            let count = draw_state
499                .named_draw_counts
500                .entry(name.to_string())
501                .or_insert(0);
502            *count += 1;
503            let current_count = *count;
504
505            if !repeatable && current_count > 1 {
506                panic!(
507                    "__draw_named: name {:?} used more than once but repeatable is false. \
508                    This is almost certainly a bug in hegel - please report it at https://github.com/hegeldev/hegel-rust/issues",
509                    name
510                );
511            }
512
513            if repeatable {
514                let mut candidate = current_count;
515                loop {
516                    let name = format!("{}_{}", name, candidate);
517                    if draw_state.allocated_display_names.insert(name.clone()) {
518                        break name;
519                    }
520                    candidate += 1;
521                }
522            } else {
523                let name = name.to_string();
524                draw_state.allocated_display_names.insert(name.clone());
525                name
526            }
527        });
528
529        let local = self.local.borrow();
530        let indent = local.indent;
531
532        (local.on_draw)(&format!(
533            "{:indent$}let {} = {:?};",
534            "",
535            display_name,
536            value,
537            indent = indent
538        ));
539    }
540
541    /// Run `f` with access to this test case's data source.
542    ///
543    /// Acquires the shared mutex for the duration of the call so
544    /// concurrent threads don't scramble backend traffic. The closure
545    /// must not call back into any other `TestCase` method that would
546    /// re-acquire the shared mutex.
547    pub(crate) fn with_data_source<R>(&self, f: impl FnOnce(&dyn DataSource) -> R) -> R {
548        self.with_shared(|shared| f(shared.data_source.as_ref()))
549    }
550
551    /// Report whether the test case has been aborted (StopTest/overflow).
552    ///
553    /// Used by the runner to decide whether to send `mark_complete`.
554    pub(crate) fn test_aborted(&self) -> bool {
555        self.with_data_source(|ds| ds.test_aborted())
556    }
557
558    /// Send `mark_complete` on this test case's data source.
559    pub(crate) fn mark_complete(&self, status: &str, origin: Option<&str>) {
560        self.with_data_source(|ds| ds.mark_complete(status, origin));
561    }
562
563    #[doc(hidden)]
564    pub fn start_span(&self, label: u64) {
565        self.local.borrow_mut().span_depth += 1;
566        if let Err(e) = self.with_data_source(|ds| ds.start_span(label)) {
567            // nocov start
568            let mut local = self.local.borrow_mut();
569            assert!(local.span_depth > 0);
570            local.span_depth -= 1;
571            drop(local);
572            panic_on_data_source_error(e);
573            // nocov end
574        }
575    }
576
577    #[doc(hidden)]
578    pub fn stop_span(&self, discard: bool) {
579        {
580            let mut local = self.local.borrow_mut();
581            assert!(local.span_depth > 0);
582            local.span_depth -= 1;
583        }
584        let _ = self.with_data_source(|ds| ds.stop_span(discard));
585    }
586}
587
588/// Send a schema to the backend and return the raw CBOR response.
589#[doc(hidden)]
590pub fn generate_raw(tc: &TestCase, schema: &Value) -> Value {
591    match tc.with_data_source(|ds| ds.generate(schema)) {
592        Ok(v) => v,
593        Err(e) => panic_on_data_source_error(e),
594    }
595}
596
597#[doc(hidden)]
598pub fn generate_from_schema<T: serde::de::DeserializeOwned>(tc: &TestCase, schema: &Value) -> T {
599    deserialize_value(generate_raw(tc, schema))
600}
601
602/// Deserialize a raw CBOR value into a Rust type.
603///
604/// This is a public helper for use by derived generators (proc macros)
605/// that need to deserialize individual field values from CBOR.
606pub fn deserialize_value<T: serde::de::DeserializeOwned>(raw: Value) -> T {
607    let hv = value::HegelValue::from(raw.clone());
608    value::from_hegel_value(hv).unwrap_or_else(|e| {
609        panic!("Failed to deserialize value: {}\nValue: {:?}", e, raw); // nocov
610    })
611}
612
613/// Uses the backend to determine collection sizing.
614///
615/// The backend-side collection object is created lazily on the first call to
616/// [`more()`](Collection::more).
617pub struct Collection<'a> {
618    tc: &'a TestCase,
619    min_size: usize,
620    max_size: Option<usize>,
621    handle: Option<String>,
622    finished: bool,
623}
624
625impl<'a> Collection<'a> {
626    /// Create a new backend-managed collection.
627    pub fn new(tc: &'a TestCase, min_size: usize, max_size: Option<usize>) -> Self {
628        Collection {
629            tc,
630            min_size,
631            max_size,
632            handle: None,
633            finished: false,
634        }
635    }
636
637    fn ensure_initialized(&mut self) -> &str {
638        if self.handle.is_none() {
639            let result = self.tc.with_data_source(|ds| {
640                ds.new_collection(self.min_size as u64, self.max_size.map(|m| m as u64))
641            });
642            let name = match result {
643                Ok(name) => name,
644                Err(e) => panic_on_data_source_error(e), // nocov
645            };
646            self.handle = Some(name);
647        }
648        self.handle.as_ref().unwrap()
649    }
650
651    /// Ask the backend whether to produce another element.
652    pub fn more(&mut self) -> bool {
653        if self.finished {
654            return false; // nocov
655        }
656        let handle = self.ensure_initialized().to_string();
657        let result = match self.tc.with_data_source(|ds| ds.collection_more(&handle)) {
658            Ok(b) => b,
659            Err(e) => {
660                self.finished = true;
661                panic_on_data_source_error(e);
662            }
663        };
664        if !result {
665            self.finished = true;
666        }
667        result
668    }
669
670    /// Reject the last element (don't count it towards the size budget).
671    pub fn reject(&mut self, why: Option<&str>) {
672        // nocov start
673        if self.finished {
674            return;
675        }
676        let handle = self.ensure_initialized().to_string();
677        let _ = self
678            .tc
679            .with_data_source(|ds| ds.collection_reject(&handle, why));
680        // nocov end
681    }
682}
683
684#[doc(hidden)]
685pub mod labels {
686    pub const LIST: u64 = 1;
687    pub const LIST_ELEMENT: u64 = 2;
688    pub const SET: u64 = 3;
689    pub const SET_ELEMENT: u64 = 4;
690    pub const MAP: u64 = 5;
691    pub const MAP_ENTRY: u64 = 6;
692    pub const TUPLE: u64 = 7;
693    pub const ONE_OF: u64 = 8;
694    pub const OPTIONAL: u64 = 9;
695    pub const FIXED_DICT: u64 = 10;
696    pub const FLAT_MAP: u64 = 11;
697    pub const FILTER: u64 = 12;
698    pub const MAPPED: u64 = 13;
699    pub const SAMPLED_FROM: u64 = 14;
700    pub const ENUM_VARIANT: u64 = 15;
701}