Skip to main content

pass_lang/
pass.rs

1//! The plugin-seam trait and the per-run outcome it reports.
2
3use crate::error::PassError;
4
5/// Whether a pass changed the unit on a given run.
6///
7/// A pass returns [`Outcome::Changed`] when it modified the unit and
8/// [`Outcome::Unchanged`] when it left it untouched. The
9/// [`PassManager`](crate::PassManager) uses this signal to decide whether a
10/// fixpoint loop has settled (a full sweep with no `Changed` is a fixpoint) and
11/// to build the [`Report`](crate::Report). Reporting `Unchanged` after mutating
12/// the unit breaks fixpoint termination, so the value is taken at its word — keep
13/// it honest.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16pub enum Outcome {
17    /// The pass modified the unit.
18    Changed,
19    /// The pass left the unit unchanged.
20    Unchanged,
21}
22
23impl Outcome {
24    /// True if this is [`Outcome::Changed`].
25    ///
26    /// # Examples
27    ///
28    /// ```
29    /// use pass_lang::Outcome;
30    ///
31    /// assert!(Outcome::Changed.changed());
32    /// assert!(!Outcome::Unchanged.changed());
33    /// ```
34    #[must_use]
35    #[inline]
36    pub fn changed(self) -> bool {
37        matches!(self, Outcome::Changed)
38    }
39
40    /// Build an outcome from a "did it change?" flag.
41    ///
42    /// The common shape of a [`Pass::run`] body: compute the transform, then
43    /// report whether it actually altered the unit.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use pass_lang::Outcome;
49    ///
50    /// assert_eq!(Outcome::from_changed(true), Outcome::Changed);
51    /// assert_eq!(Outcome::from_changed(false), Outcome::Unchanged);
52    /// ```
53    #[must_use]
54    #[inline]
55    pub fn from_changed(changed: bool) -> Self {
56        if changed {
57            Outcome::Changed
58        } else {
59            Outcome::Unchanged
60        }
61    }
62}
63
64/// A single transform or analysis over a unit of type `T` — the plugin seam.
65///
66/// Implement this trait to define a pass. The unit `T` is whatever the pass
67/// rewrites: an intermediate representation, a single function, a module, an
68/// abstract syntax tree, or a struct bundling an IR with the diagnostics and
69/// analysis state the pass needs. The manager is generic over `T` and never
70/// inspects it — a pass is the only thing trusted to read or mutate the unit.
71///
72/// A pass is registered with [`PassManager::add`](crate::PassManager::add) and
73/// run in registration order. It must be `'static`: it may own state across
74/// runs, but it may not borrow from outside the manager.
75///
76/// # Contract
77///
78/// - [`name`](Self::name) returns a stable, static identifier used in the
79///   [`Report`](crate::Report) and in error context. It must not change between
80///   runs of the same pass.
81/// - [`run`](Self::run) transforms `unit` in place and reports an
82///   [`Outcome`]: [`Changed`](Outcome::Changed) only when the unit was actually
83///   modified, so a fixpoint loop can terminate. A pass that cannot proceed
84///   returns a [`PassError`] instead of panicking.
85///
86/// # Examples
87///
88/// A pass that drops zero entries from a list, reporting whether it removed any:
89///
90/// ```
91/// use pass_lang::{Outcome, Pass, PassError};
92///
93/// struct DropZeros;
94///
95/// impl Pass<Vec<i64>> for DropZeros {
96///     fn name(&self) -> &'static str {
97///         "drop-zeros"
98///     }
99///
100///     fn run(&mut self, unit: &mut Vec<i64>) -> Result<Outcome, PassError> {
101///         let before = unit.len();
102///         unit.retain(|&x| x != 0);
103///         Ok(Outcome::from_changed(unit.len() != before))
104///     }
105/// }
106///
107/// let mut unit = vec![0, 1, 0, 2];
108/// assert_eq!(DropZeros.run(&mut unit).unwrap(), Outcome::Changed);
109/// assert_eq!(unit, vec![1, 2]);
110/// // Running again changes nothing.
111/// assert_eq!(DropZeros.run(&mut unit).unwrap(), Outcome::Unchanged);
112/// ```
113pub trait Pass<T> {
114    /// A stable, static name for this pass, used in reports and error context.
115    fn name(&self) -> &'static str;
116
117    /// Run the pass over `unit`, transforming it in place.
118    ///
119    /// Return [`Outcome::Changed`] if and only if the unit was modified, so the
120    /// manager's fixpoint loop can tell when the pipeline has settled. Return a
121    /// [`PassError`] — never a panic — if the pass cannot proceed; the manager
122    /// stops the pipeline and reports which pass failed.
123    fn run(&mut self, unit: &mut T) -> Result<Outcome, PassError>;
124}
125
126#[cfg(test)]
127#[allow(clippy::expect_used, clippy::unwrap_used)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_changed_reports_variant() {
133        assert!(Outcome::Changed.changed());
134        assert!(!Outcome::Unchanged.changed());
135    }
136
137    #[test]
138    fn test_from_changed_maps_bool() {
139        assert_eq!(Outcome::from_changed(true), Outcome::Changed);
140        assert_eq!(Outcome::from_changed(false), Outcome::Unchanged);
141    }
142
143    struct Noop;
144    impl Pass<i64> for Noop {
145        fn name(&self) -> &'static str {
146            "noop"
147        }
148        fn run(&mut self, _unit: &mut i64) -> Result<Outcome, PassError> {
149            Ok(Outcome::Unchanged)
150        }
151    }
152
153    #[test]
154    fn test_trait_is_object_safe() {
155        // If `Pass<T>` were not object-safe this would not compile.
156        let _boxed: alloc::boxed::Box<dyn Pass<i64>> = alloc::boxed::Box::new(Noop);
157    }
158}