1use tracing::debug;
2use proc_macro2::TokenStream;
3use pyo3::FromPyObject;
4use quote::quote;
5use serde::{Deserialize, Serialize};
6
7use crate::{CodeGen, CodeGenContext, PythonOptions, SymbolTableNode, SymbolTableScopes};
8
9pub(crate) fn is_stdpython_module(name: &str) -> bool {
13 matches!(
14 name,
15 "os" | "sys"
16 | "re"
17 | "io"
18 | "argparse"
19 | "json"
20 | "math"
21 | "random"
22 | "datetime"
23 | "time"
24 | "collections"
25 | "itertools"
26 | "functools"
27 | "heapq"
28 | "copy"
29 | "textwrap"
30 | "hashlib"
31 | "csv"
32 | "glob"
33 | "pathlib"
34 | "tempfile"
35 | "subprocess"
36 | "string"
37 | "sysconfig"
38 | "venv"
39 )
40}
41
42pub(crate) fn is_std_only_module(name: &str) -> bool {
47 matches!(
48 name,
49 "os" | "sys"
50 | "re"
51 | "io"
52 | "argparse"
53 | "math"
54 | "random"
55 | "datetime"
56 | "time"
57 | "glob"
58 | "pathlib"
59 | "tempfile"
60 | "subprocess"
61 | "sysconfig"
62 | "venv"
63 )
64}
65
66fn std_only_import_error(module: &str) -> Box<dyn std::error::Error> {
70 format!(
71 "`import {}` requires stdpython's std tier (it needs the OS), which the \
72 no_std profile does not provide; remove the import or convert without \
73 the no_std profile",
74 module
75 )
76 .into()
77}
78
79#[derive(Clone, Debug, FromPyObject, Serialize, Deserialize, PartialEq)]
80pub struct Alias {
81 pub name: String,
82 pub asname: Option<String>,
83}
84
85#[derive(Clone, Debug, FromPyObject, Serialize, Deserialize, PartialEq)]
86pub struct Import {
87 pub names: Vec<Alias>,
88}
89
90impl CodeGen for Import {
95 type Context = CodeGenContext;
96 type Options = PythonOptions;
97 type SymbolTable = SymbolTableScopes;
98
99 fn find_symbols(self, symbols: Self::SymbolTable) -> Self::SymbolTable {
100 let mut symbols = symbols;
101 for alias in self.names.iter() {
102 symbols.insert(alias.name.clone(), SymbolTableNode::Import(self.clone()));
103 if let Some(a) = alias.asname.clone() {
104 symbols.insert(a, SymbolTableNode::Alias(alias.name.clone()))
105 }
106 }
107 symbols
108 }
109
110 fn to_rust(
111 self,
112 ctx: Self::Context,
113 options: Self::Options,
114 _symbols: Self::SymbolTable,
115 ) -> Result<TokenStream, Box<dyn std::error::Error>> {
116 let mut tokens = TokenStream::new();
117 for alias in self.names.iter() {
118 if options.no_std {
119 let root = alias.name.split('.').next().unwrap_or(&alias.name);
120 if is_std_only_module(root) {
121 return Err(std_only_import_error(&alias.name));
122 }
123 }
124 let rust_import = match alias.name.as_str() {
126 name if is_stdpython_module(name) => {
131 if let Some(asname) = &alias.asname {
132 return Err(format!(
136 "`import {} as {}`: aliasing runtime modules is not \
137 supported yet; use `import {}`",
138 name, asname, name
139 )
140 .into());
141 }
142 quote! {}
143 }
144 "urllib" | "xml" | "asyncio" => {
146 quote! {
149 }
151 }
152 "os.path" => {
153 quote! {
154 }
156 }
157 _ => {
158 let names = if alias.name.contains('.') {
160 let parts: Vec<&str> = alias.name.split('.').collect();
161 let idents: Vec<_> = parts.iter().map(|part| crate::safe_ident(part)).collect();
162 quote!(#(#idents)::*)
163 } else {
164 let single_name = crate::safe_ident(&alias.name);
165 quote!(#single_name)
166 };
167
168 match &alias.asname {
169 None => {
170 quote! {use #names;}
171 }
172 Some(n) => {
173 let name = crate::safe_ident(n);
174 quote! {use #names as #name;}
175 }
176 }
177 }
178 };
179
180 tokens.extend(rust_import);
181 }
182 debug!("context: {:?}", ctx);
183 debug!("options: {:?}", options);
184 debug!("tokens: {}", tokens);
185 Ok(tokens)
186 }
187}
188
189#[derive(Clone, Debug, FromPyObject, Serialize, Deserialize, PartialEq)]
190pub struct ImportFrom {
191 pub module: String,
192 pub names: Vec<Alias>,
193 pub level: usize,
194}
195
196impl CodeGen for ImportFrom {
197 type Context = CodeGenContext;
198 type Options = PythonOptions;
199 type SymbolTable = SymbolTableScopes;
200
201 fn find_symbols(self, symbols: Self::SymbolTable) -> Self::SymbolTable {
202 let mut symbols = symbols;
203 for alias in self.names.iter() {
204 symbols.insert(
205 alias.name.clone(),
206 SymbolTableNode::ImportFrom(self.clone()),
207 );
208 }
209 symbols
210 }
211
212 fn to_rust(
213 self,
214 ctx: Self::Context,
215 _options: Self::Options,
216 _symbols: Self::SymbolTable,
217 ) -> Result<TokenStream, Box<dyn std::error::Error>> {
218 debug!("ctx: {:?}", ctx);
219
220 if self.module.split('.').next() == Some("typing") {
224 return Ok(TokenStream::new());
225 }
226
227 if _options.no_std {
228 let root = self.module.split('.').next().unwrap_or(&self.module);
229 if is_std_only_module(root) {
230 return Err(std_only_import_error(&self.module));
231 }
232 }
233
234 let parts: Vec<&str> = self
241 .module
242 .split('.')
243 .filter(|part| !part.is_empty())
244 .collect();
245 let module_path: Vec<_> = parts.iter().map(|part| crate::safe_ident(part)).collect();
246 let root = if parts.first().is_some_and(|first| is_stdpython_module(first)) {
247 let runtime = crate::safe_ident(&_options.stdpython);
248 quote!(#runtime)
249 } else {
250 quote!(crate)
251 };
252
253 let mut tokens = TokenStream::new();
254 for alias in self.names.iter() {
255 if self.module == "functools"
261 && matches!(alias.name.as_str(), "partial" | "lru_cache" | "cache")
262 {
263 continue;
264 }
265 if alias.name == "*" {
266 tokens.extend(quote! { use #root #(::#module_path)*::*; });
267 continue;
268 }
269 let variants: &[&str] = match (self.module.as_str(), alias.name.as_str()) {
280 ("itertools", "accumulate") => &[
281 "accumulate_sum",
282 "accumulate_func",
283 "accumulate_sum_initial",
284 "accumulate_func_initial",
285 ],
286 ("itertools", "product") => &[
287 "product2",
288 "product3",
289 "product_repeat2",
290 "product_repeat3",
291 ],
292 ("itertools", "zip_longest") => &["zip_longest_fill"],
293 ("itertools", "groupby") => &["groupby_key"],
294 ("functools", "reduce") => &["reduce_initial"],
295 ("re", "findall") => &["findall2", "findall3"],
296 ("io", "StringIO") => &["StringIO_seeded"],
297 ("hashlib", "md5") => &["md5_new"],
298 ("hashlib", "sha1") => &["sha1_new"],
299 ("hashlib", "sha256") => &["sha256_new"],
300 ("hashlib", "sha512") => &["sha512_new"],
301 _ => &[],
302 };
303
304 let name = crate::safe_ident(&alias.name);
305 let import = match &alias.asname {
306 None if variants.is_empty() => {
307 quote! { use #root #(::#module_path)*::#name; }
308 }
309 None => quote! {
310 #[allow(unused_imports)]
311 use #root #(::#module_path)*::#name;
312 },
313 Some(asname) => {
314 let asname = crate::safe_ident(asname);
315 quote! { use #root #(::#module_path)*::#name as #asname; }
316 }
317 };
318 tokens.extend(import);
319
320 for variant in variants {
321 let v = crate::safe_ident(variant);
322 tokens.extend(quote! {
323 #[allow(unused_imports)]
324 use #root #(::#module_path)*::#v;
325 });
326 }
327 }
328 Ok(tokens)
329 }
330}