/home/noah/src/ruchy/src/backend/transpiler/result_type.rs
Line | Count | Source |
1 | | //! Result type support for Ruchy |
2 | | //! |
3 | | //! This module provides comprehensive `Result<T, E>` type support including: |
4 | | //! - Result constructors (Ok/Err) |
5 | | //! - Pattern matching on Results |
6 | | //! - ? operator for error propagation |
7 | | //! - Result combinators (map, `and_then`, etc.) |
8 | | |
9 | | use super::Transpiler; |
10 | | use crate::frontend::ast::Expr; |
11 | | use anyhow::Result; |
12 | | use proc_macro2::TokenStream; |
13 | | use quote::{format_ident, quote}; |
14 | | |
15 | | impl Transpiler { |
16 | | /// Generates Result type helpers and combinators |
17 | | /// |
18 | | /// # Examples |
19 | | /// |
20 | | /// ``` |
21 | | /// use ruchy::Transpiler; |
22 | | /// |
23 | | /// let helpers = Transpiler::generate_result_helpers(); |
24 | | /// let code = helpers.to_string(); |
25 | | /// assert!(code.contains("trait ResultExt")); |
26 | | /// assert!(code.contains("map_err_with")); |
27 | | /// assert!(code.contains("unwrap_or_else_with")); |
28 | | /// assert!(code.contains("and_then_with")); |
29 | | /// assert!(code.contains("or_else_with")); |
30 | | /// ``` |
31 | 2 | pub fn generate_result_helpers() -> TokenStream { |
32 | 2 | quote! { |
33 | | // Result extension trait for additional combinators |
34 | | trait ResultExt<T, E> { |
35 | | fn map_err_with<F, E2>(self, f: F) -> Result<T, E2> |
36 | | where |
37 | | F: FnOnce(E) -> E2; |
38 | | |
39 | | fn unwrap_or_else_with<F>(self, f: F) -> T |
40 | | where |
41 | | F: FnOnce(E) -> T; |
42 | | |
43 | | fn and_then_with<F, U>(self, f: F) -> Result<U, E> |
44 | | where |
45 | | F: FnOnce(T) -> Result<U, E>; |
46 | | |
47 | | fn or_else_with<F, E2>(self, f: F) -> Result<T, E2> |
48 | | where |
49 | | F: FnOnce(E) -> Result<T, E2>; |
50 | | } |
51 | | |
52 | | impl<T, E> ResultExt<T, E> for Result<T, E> { |
53 | | fn map_err_with<F, E2>(self, f: F) -> Result<T, E2> |
54 | | where |
55 | | F: FnOnce(E) -> E2 |
56 | | { |
57 | | self.map_err(f) |
58 | | } |
59 | | |
60 | | fn unwrap_or_else_with<F>(self, f: F) -> T |
61 | | where |
62 | | F: FnOnce(E) -> T |
63 | | { |
64 | | self.unwrap_or_else(f) |
65 | | } |
66 | | |
67 | | fn and_then_with<F, U>(self, f: F) -> Result<U, E> |
68 | | where |
69 | | F: FnOnce(T) -> Result<U, E> |
70 | | { |
71 | | self.and_then(f) |
72 | | } |
73 | | |
74 | | fn or_else_with<F, E2>(self, f: F) -> Result<T, E2> |
75 | | where |
76 | | F: FnOnce(E) -> Result<T, E2> |
77 | | { |
78 | | self.or_else(f) |
79 | | } |
80 | | } |
81 | | } |
82 | 2 | } |
83 | | |
84 | | /// Transpiles Result pattern matching |
85 | | /// |
86 | | /// # Examples |
87 | | /// |
88 | | /// ``` |
89 | | /// use ruchy::{Transpiler, Parser}; |
90 | | /// |
91 | | /// let transpiler = Transpiler::new(); |
92 | | /// let mut parser = Parser::new(r#"match result { Ok(val) => val, Err(e) => 0 }"#); |
93 | | /// let ast = parser.parse().unwrap(); |
94 | | /// |
95 | | /// let result = transpiler.transpile(&ast).unwrap(); |
96 | | /// let code = result.to_string(); |
97 | | /// assert!(code.contains("Ok")); |
98 | | /// assert!(code.contains("Err")); |
99 | | /// ``` |
100 | 0 | pub fn transpile_result_match( |
101 | 0 | &self, |
102 | 0 | expr: &Expr, |
103 | 0 | arms: &[(String, Expr)], |
104 | 0 | ) -> Result<TokenStream> { |
105 | 0 | let expr_tokens = self.transpile_expr(expr)?; |
106 | 0 | let mut match_arms = Vec::new(); |
107 | | |
108 | 0 | for (pattern, body) in arms { |
109 | 0 | let body_tokens = self.transpile_expr(body)?; |
110 | 0 | let arm_tokens = if pattern == "Ok" { |
111 | 0 | quote! { Ok(value) => #body_tokens } |
112 | 0 | } else if pattern == "Err" { |
113 | 0 | quote! { Err(error) => #body_tokens } |
114 | | } else { |
115 | 0 | quote! { _ => #body_tokens } |
116 | | }; |
117 | 0 | match_arms.push(arm_tokens); |
118 | | } |
119 | | |
120 | 0 | Ok(quote! { |
121 | | match #expr_tokens { |
122 | | #(#match_arms,)* |
123 | | } |
124 | | }) |
125 | 0 | } |
126 | | |
127 | | /// Transpiles Result chaining with ? operator |
128 | | /// |
129 | | /// # Examples |
130 | | /// |
131 | | /// ``` |
132 | | /// use ruchy::{Transpiler, Parser}; |
133 | | /// |
134 | | /// let transpiler = Transpiler::new(); |
135 | | /// let mut parser = Parser::new("result?"); |
136 | | /// let ast = parser.parse().unwrap(); |
137 | | /// |
138 | | /// let result = transpiler.transpile(&ast).unwrap(); |
139 | | /// let code = result.to_string(); |
140 | | /// assert!(code.contains("?")); |
141 | | /// ``` |
142 | 0 | pub fn transpile_result_chain(&self, operations: &[Expr]) -> Result<TokenStream> { |
143 | 0 | if operations.is_empty() { |
144 | 0 | return Ok(quote! { Ok(()) }); |
145 | 0 | } |
146 | | |
147 | 0 | let mut chain = self.transpile_expr(&operations[0])?; |
148 | | |
149 | 0 | for op in &operations[1..] { |
150 | 0 | let op_tokens = self.transpile_expr(op)?; |
151 | 0 | chain = quote! { #chain.and_then(|_| #op_tokens) }; |
152 | | } |
153 | | |
154 | 0 | Ok(chain) |
155 | 0 | } |
156 | | |
157 | | /// Transpiles Result unwrapping with default |
158 | 0 | pub fn transpile_result_unwrap_or(&self, result: &Expr, default: &Expr) -> Result<TokenStream> { |
159 | 0 | let result_tokens = self.transpile_expr(result)?; |
160 | 0 | let default_tokens = self.transpile_expr(default)?; |
161 | | |
162 | 0 | Ok(quote! { |
163 | 0 | #result_tokens.unwrap_or(#default_tokens) |
164 | 0 | }) |
165 | 0 | } |
166 | | |
167 | | /// Transpiles Result mapping |
168 | 0 | pub fn transpile_result_map(&self, result: &Expr, mapper: &Expr) -> Result<TokenStream> { |
169 | 0 | let result_tokens = self.transpile_expr(result)?; |
170 | 0 | let mapper_tokens = self.transpile_expr(mapper)?; |
171 | | |
172 | 0 | Ok(quote! { |
173 | 0 | #result_tokens.map(#mapper_tokens) |
174 | 0 | }) |
175 | 0 | } |
176 | | |
177 | | /// Transpiles custom error types |
178 | 1 | pub fn transpile_error_type( |
179 | 1 | &self, |
180 | 1 | name: &str, |
181 | 1 | variants: &[(String, Option<String>)], |
182 | 1 | ) -> TokenStream { |
183 | 1 | let error_name = format_ident!("{}", name); |
184 | 1 | let mut variant_tokens = Vec::new(); |
185 | | |
186 | 4 | for (variant3 , data3 ) in variants { |
187 | 3 | let variant_ident = format_ident!("{}", variant); |
188 | 3 | let variant_token = if let Some(data_type2 ) = data { |
189 | | // Parse the type string to handle paths like std::io::Error |
190 | 2 | let data_type_tokens: TokenStream = data_type.parse().unwrap_or_else(|_| {0 |
191 | | // If parsing fails, fall back to String |
192 | 0 | quote! { String } |
193 | 0 | }); |
194 | 2 | quote! { #variant_ident(#data_type_tokens) } |
195 | | } else { |
196 | 1 | quote! { #variant_ident } |
197 | | }; |
198 | 3 | variant_tokens.push(variant_token); |
199 | | } |
200 | | |
201 | 1 | quote! { |
202 | | #[derive(Debug, Clone)] |
203 | | enum #error_name { |
204 | | #(#variant_tokens,)* |
205 | | } |
206 | | |
207 | | impl std::fmt::Display for #error_name { |
208 | | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
209 | | write!(f, "{:?}", self) |
210 | | } |
211 | | } |
212 | | |
213 | | impl std::error::Error for #error_name {} |
214 | | } |
215 | 1 | } |
216 | | } |
217 | | |
218 | | /// Generate Result type test cases |
219 | | #[cfg(test)] |
220 | | mod tests { |
221 | | use super::*; |
222 | | |
223 | | #[test] |
224 | 1 | fn test_result_helpers_generation() { |
225 | 1 | let helpers = Transpiler::generate_result_helpers(); |
226 | 1 | let code = helpers.to_string(); |
227 | 1 | assert!(code.contains("ResultExt")); |
228 | 1 | assert!(code.contains("map_err_with")); |
229 | 1 | assert!(code.contains("unwrap_or_else_with")); |
230 | 1 | assert!(code.contains("and_then_with")); |
231 | 1 | assert!(code.contains("or_else_with")); |
232 | 1 | } |
233 | | |
234 | | #[test] |
235 | 1 | fn test_transpile_error_type() { |
236 | 1 | let transpiler = Transpiler::new(); |
237 | 1 | let variants = vec![ |
238 | 1 | ("NotFound".to_string(), None), |
239 | 1 | ("InvalidInput".to_string(), Some("String".to_string())), |
240 | 1 | ("NetworkError".to_string(), Some("std::io::Error".to_string())), |
241 | | ]; |
242 | | |
243 | 1 | let error_type = transpiler.transpile_error_type("AppError", &variants); |
244 | 1 | let code = error_type.to_string(); |
245 | | |
246 | | // Check enum definition |
247 | 1 | assert!(code.contains("enum AppError")); |
248 | 1 | assert!(code.contains("NotFound")); |
249 | 1 | assert!(code.contains("InvalidInput") && code.contains("String")); |
250 | 1 | assert!(code.contains("NetworkError") && code.contains("std") && code.contains("io") && code.contains("Error")); |
251 | | |
252 | | // Check trait implementations |
253 | 1 | assert!(code.contains("derive") && code.contains("Debug") && code.contains("Clone")); |
254 | 1 | assert!(code.contains("impl") && code.contains("std") && code.contains("fmt") && code.contains("Display")); |
255 | 1 | assert!(code.contains("impl") && code.contains("std") && code.contains("error") && code.contains("Error")); |
256 | 1 | } |
257 | | } |