Skip to main content

ryo_source/pure/to_syn/
mod.rs

1//! Conversion from Pure types back to syn types.
2//!
3//! This enables code generation from PureFile after parallel modifications.
4//! All spans are set to `Span::call_site()` since PureFile doesn't preserve spans.
5//!
6//! # Module Structure
7//!
8//! - `helpers` - Common helper functions (ident, parse_path)
9//! - `attr` - PureAttribute, PureVis
10//! - `item` - PureItem, PureUse, PureUseTree
11//! - `function` - PureFn, PureParam, PureGenerics, PureBlock, PureStmt
12//! - `expr` - PureExpr, PurePattern, PureMatchArm
13//! - `types` - PureType
14//! - `structs` - PureStruct, PureFields, PureField, PureEnum, PureVariant
15//! - `impls` - PureImpl, PureImplItem
16//! - `other` - PureConst, PureStatic, PureTypeAlias, PureMod, PureTrait, PureTraitItem, PureMacro
17
18pub mod helpers;
19
20mod attr;
21mod expr;
22mod function;
23mod impls;
24mod item;
25mod other;
26mod structs;
27mod types;
28
29use super::ast::PureFile;
30
31/// Format multiple files in-place using a single rustfmt invocation.
32///
33/// This is much faster than formatting each file individually, as it avoids
34/// spawning a new process for each file.
35///
36/// Returns Ok(()) if rustfmt succeeds, Err with message otherwise.
37pub fn format_files_with_rustfmt<P: AsRef<std::path::Path>>(files: &[P]) -> Result<(), String> {
38    use std::process::Command;
39
40    if files.is_empty() {
41        return Ok(());
42    }
43
44    let output = Command::new("rustfmt")
45        .args(["--edition", "2021"])
46        .args(files.iter().map(|p| p.as_ref()))
47        .output()
48        .map_err(|e| format!("Failed to spawn rustfmt: {}", e))?;
49
50    if output.status.success() {
51        Ok(())
52    } else {
53        Err(String::from_utf8_lossy(&output.stderr).to_string())
54    }
55}
56
57/// Trait for converting Pure types back to syn types.
58///
59/// All conversions are fallible because they may involve `syn::parse_str`
60/// for `Other` variants (expressions, types, patterns stored as strings).
61pub trait ToSyn {
62    /// `syn` output type produced from `self`.
63    type Output;
64    /// Convert `self` back into its `syn` representation.
65    fn to_syn(&self) -> Result<Self::Output, ToSynError>;
66}
67
68/// Error type for ToSyn conversions.
69#[derive(Debug, Clone)]
70pub enum ToSynError {
71    /// Failed to parse a path string (e.g., invalid syntax)
72    ParsePath {
73        /// Original input string that failed to parse.
74        input: String,
75        /// Underlying parser message.
76        message: String,
77    },
78    /// Failed to parse an expression
79    ParseExpr {
80        /// Original input string that failed to parse.
81        input: String,
82        /// Underlying parser message.
83        message: String,
84    },
85    /// Failed to parse a pattern
86    ParsePattern {
87        /// Original input string that failed to parse.
88        input: String,
89        /// Underlying parser message.
90        message: String,
91    },
92    /// Failed to parse a type
93    ParseType {
94        /// Original input string that failed to parse.
95        input: String,
96        /// Underlying parser message.
97        message: String,
98    },
99    /// Missing required value
100    MissingValue {
101        /// Context describing which value was missing.
102        context: String,
103    },
104    /// Other conversion error
105    Other {
106        /// Free-form error message.
107        message: String,
108    },
109}
110
111impl std::fmt::Display for ToSynError {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        match self {
114            Self::ParsePath { input, message } => {
115                write!(f, "Failed to parse path '{}': {}", input, message)
116            }
117            Self::ParseExpr { input, message } => {
118                write!(f, "Failed to parse expression '{}': {}", input, message)
119            }
120            Self::ParsePattern { input, message } => {
121                write!(f, "Failed to parse pattern '{}': {}", input, message)
122            }
123            Self::ParseType { input, message } => {
124                write!(f, "Failed to parse type '{}': {}", input, message)
125            }
126            Self::MissingValue { context } => {
127                write!(f, "Missing required value: {}", context)
128            }
129            Self::Other { message } => write!(f, "Conversion error: {}", message),
130        }
131    }
132}
133
134impl std::error::Error for ToSynError {}
135
136impl PureFile {
137    /// Convert to syn::File for code generation.
138    pub fn to_syn_file(&self) -> Result<syn::File, ToSynError> {
139        self.to_syn()
140    }
141
142    /// Generate source code string.
143    ///
144    /// Returns formatted source code using prettyplease. Falls back to
145    /// quote-based stringification if prettyplease panics.
146    ///
147    /// Note: This method does NOT run rustfmt. For final output, use
148    /// `format_files_with_rustfmt()` to batch-format written files.
149    pub fn to_source(&self) -> Result<String, ToSynError> {
150        let file = self.to_syn()?;
151
152        // Try prettyplease first, fall back to quote if it panics
153        Ok(
154            catch_unwind_silent(|| prettyplease::unparse(&file)).unwrap_or_else(|_| {
155                use quote::ToTokens;
156                file.to_token_stream().to_string()
157            }),
158        )
159    }
160}
161
162/// Execute a closure, catching any panics without printing panic messages.
163///
164/// This is useful for handling expected panics from third-party libraries
165/// (like prettyplease's Expr::Verbatim) without polluting stderr.
166fn catch_unwind_silent<F, R>(f: F) -> std::thread::Result<R>
167where
168    F: FnOnce() -> R + std::panic::UnwindSafe,
169{
170    let prev_hook = std::panic::take_hook();
171    std::panic::set_hook(Box::new(|_| {}));
172    let result = std::panic::catch_unwind(f);
173    std::panic::set_hook(prev_hook);
174    result
175}
176
177impl ToSyn for PureFile {
178    type Output = syn::File;
179
180    fn to_syn(&self) -> Result<syn::File, ToSynError> {
181        Ok(syn::File {
182            shebang: None,
183            attrs: self
184                .attrs
185                .iter()
186                .map(|a| a.to_syn())
187                .collect::<Result<Vec<_>, _>>()?,
188            items: self
189                .items
190                .iter()
191                .map(|i| i.to_syn())
192                .collect::<Result<Vec<_>, _>>()?,
193        })
194    }
195}
196
197#[cfg(test)]
198mod tests;