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}