1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
//! Module-level code generation: imports, type aliases, and interned strings
//!
//! This module handles generation of top-level module items that appear
//! before function and struct definitions in the generated Rust output:
//!
//! - **Import deduplication** (`deduplicate_use_statements`): Prevents E0252
//! errors when multiple sources generate the same `use` statement.
//! - **Conditional imports** (`generate_conditional_imports`): Adds `use`
//! statements for collections, smart pointers, and external crates based
//! on what the generated code actually needs (NASA-mode aware).
//! - **Type aliases** (`generate_type_alias_tokens`): Maps Python type aliases
//! to Rust `type` aliases or newtype structs.
//! - **Import mapping** (`generate_import_tokens`): Translates Python imports
//! to Rust `use` statements via the module mapper.
//! - **Interned strings** (`generate_interned_string_tokens`): Emits constant
//! definitions for compile-time interned string literals.
use super::context::CodeGenContext;
use super::{keywords, type_gen};
use crate::hir::*;
use crate::string_optimization::StringOptimizer;
use quote::quote;
use std::collections::HashSet;
use syn::parse_quote;
/// Deduplicate use statements to avoid E0252 errors
///
/// DEPYLER-0335 FIX #1: Multiple sources can generate the same import.
/// For example, both generate_import_tokens and generate_conditional_imports
/// might add `use std::collections::HashMap;`.
///
/// # Complexity
/// ~6 (loop + if + string ops)
pub(super) fn deduplicate_use_statements(
items: Vec<proc_macro2::TokenStream>,
) -> Vec<proc_macro2::TokenStream> {
let mut seen = HashSet::new();
let mut deduped = Vec::new();
for item in items {
let item_str = item.to_string();
// Only deduplicate use statements
if item_str.starts_with("use ") {
if seen.insert(item_str) {
deduped.push(item);
}
// else: skip duplicate
} else {
// Non-import items: always keep
deduped.push(item);
}
}
deduped
}
/// Generate conditional imports based on code generation context
///
/// Adds imports for collections and smart pointers as needed.
/// Complexity: 1 (data-driven approach, well within <=10 target)
/// DEPYLER-1016: Added nasa_mode to skip external crate imports
pub(super) fn generate_conditional_imports(
ctx: &CodeGenContext,
) -> Vec<proc_macro2::TokenStream> {
let mut imports = Vec::new();
let nasa_mode = ctx.type_mapper.nasa_mode;
// DEPYLER-1016: Define std-only imports (always safe)
let std_imports = [
(ctx.needs_hashmap, quote! { use std::collections::HashMap; }),
(ctx.needs_hashset, quote! { use std::collections::HashSet; }),
(
ctx.needs_vecdeque,
quote! { use std::collections::VecDeque; },
),
(ctx.needs_arc, quote! { use std::sync::Arc; }),
(ctx.needs_rc, quote! { use std::rc::Rc; }),
(ctx.needs_cow, quote! { use std::borrow::Cow; }),
(ctx.needs_io_read, quote! { use std::io::Read; }), // DEPYLER-0458
(ctx.needs_io_write, quote! { use std::io::Write; }), // DEPYLER-0458
(ctx.needs_bufread, quote! { use std::io::BufRead; }), // DEPYLER-0522
(ctx.needs_lazy_lock, quote! { use std::sync::LazyLock; }), // DEPYLER-1016: NASA mode std-only
];
// DEPYLER-1016: External crate imports (skip in NASA mode)
let external_imports = [
(ctx.needs_fnv_hashmap, quote! { use fnv::FnvHashMap; }),
(ctx.needs_ahash_hashmap, quote! { use ahash::AHashMap; }),
(ctx.needs_serde_json, quote! { use serde_json; }),
(ctx.needs_base64, quote! { use base64::Engine; }), // DEPYLER-0664: Engine trait needed for .encode()/.decode() methods
(ctx.needs_once_cell, quote! { use once_cell::sync::Lazy; }), // DEPYLER-REARCH-001
(ctx.needs_trueno, quote! { use trueno::Vector; }), // Phase 3: NumPy->Trueno
// DEPYLER-1004: chrono methods like .month(), .minute() need Datelike/Timelike traits
(
ctx.needs_chrono,
quote! { use chrono::{Datelike, Timelike}; },
),
];
// Add std imports (always)
for (needed, import_tokens) in std_imports {
if needed {
imports.push(import_tokens);
}
}
// Add external imports only if not in NASA mode
if !nasa_mode {
for (needed, import_tokens) in external_imports {
if needed {
imports.push(import_tokens);
}
}
}
imports
}
/// DEPYLER-197: Generate Rust type aliases from Python type aliases
///
/// Maps Python type aliases like `EventHandler = Callable[[str], None]`
/// to Rust type aliases like `type EventHandler = Box<dyn Fn(String)>;`
///
/// # Arguments
/// * `type_aliases` - Vector of TypeAlias structs from HIR
/// * `type_mapper` - TypeMapper for converting Python types to Rust types
///
/// # Returns
/// Vector of TokenStreams containing type alias declarations
pub(super) fn generate_type_alias_tokens(
type_aliases: &[TypeAlias],
type_mapper: &crate::type_mapper::TypeMapper,
) -> Vec<proc_macro2::TokenStream> {
let mut items = Vec::new();
for type_alias in type_aliases {
// Get the Rust type for this alias
let rust_type = type_mapper.map_type(&type_alias.target_type);
let target_type = match type_gen::rust_type_to_syn(&rust_type) {
Ok(ty) => ty,
Err(_) => continue, // Skip if type conversion fails
};
// Create identifier for the alias name
let alias_name = if keywords::is_rust_keyword(&type_alias.name) {
syn::Ident::new_raw(&type_alias.name, proc_macro2::Span::call_site())
} else {
syn::Ident::new(&type_alias.name, proc_macro2::Span::call_site())
};
// Generate either a newtype struct or a type alias
let alias_item = if type_alias.is_newtype {
// Generate a NewType struct: pub struct UserId(pub i32);
quote! {
#[derive(Debug, Clone, PartialEq)]
pub struct #alias_name(pub #target_type);
}
} else {
// Generate a type alias: pub type UserId = i32;
quote! {
pub type #alias_name = #target_type;
}
};
items.push(alias_item);
}
items
}
/// Generate import token streams from Python imports
///
/// Maps Python imports to Rust use statements.
/// Complexity: ~7-8 (within <=10 target)
/// DEPYLER-1016: Added nasa_mode to skip external crate imports
pub(super) fn generate_import_tokens(
imports: &[Import],
module_mapper: &crate::module_mapper::ModuleMapper,
nasa_mode: bool,
) -> Vec<proc_macro2::TokenStream> {
let mut items = Vec::new();
let mut external_imports = Vec::new();
let mut std_imports = Vec::new();
// Categorize imports
for import in imports {
let rust_imports = module_mapper.map_import(import);
for rust_import in rust_imports {
if rust_import.path.starts_with("//") {
// Comment for unmapped imports
let comment = &rust_import.path;
items.push(quote! { #[doc = #comment] });
} else if rust_import.is_external {
// DEPYLER-1016: Skip external imports in NASA mode
if !nasa_mode {
external_imports.push(rust_import);
}
} else {
std_imports.push(rust_import);
}
}
}
// DEPYLER-0335 FIX #1: Deduplicate imports using HashSet
// Multiple Python imports can map to same Rust type (e.g., defaultdict + Counter -> HashMap)
let mut seen_paths = HashSet::new();
// Add external imports (deduplicated)
for import in external_imports {
// DEPYLER-0936: Skip hashlib module alias
// hashlib maps to sha2, but hashlib.md5() uses md-5 crate, hashlib.sha256() uses sha2, etc.
// The method calls are handled inline in expr_gen.rs with correct crate imports.
// Generating `use sha2 as hashlib;` causes E0432 when only md5 is used.
if import.alias.as_deref() == Some("hashlib") {
continue;
}
// Create unique key from path + alias
let key = format!("{}:{:?}", import.path, import.alias);
if !seen_paths.insert(key) {
continue; // Skip duplicate
}
let path: syn::Path =
syn::parse_str(&import.path).unwrap_or_else(|_| parse_quote! { unknown });
if let Some(alias) = import.alias {
let alias_ident = syn::Ident::new(&alias, proc_macro2::Span::call_site());
items.push(quote! { use #path as #alias_ident; });
} else {
items.push(quote! { use #path; });
}
}
// Add standard library imports (deduplicated)
for import in std_imports {
// Skip typing imports as they're handled by the type system
if import.path.starts_with("::") || import.path.is_empty() {
continue;
}
// DEPYLER-0593: Skip os/os.path module aliases
// These modules are handled specially in expr_gen.rs (try_convert_os_path_method)
// Generating `use std as os;` breaks the module recognition
// DEPYLER-0691: Also skip sys module aliases since we use fully qualified std::env, std::process paths
if import.alias.as_deref() == Some("os")
|| import.alias.as_deref() == Some("os_path")
|| import.alias.as_deref() == Some("sys")
{
continue;
}
// Create unique key from path + alias
let key = format!("{}:{:?}", import.path, import.alias);
if !seen_paths.insert(key) {
continue; // Skip duplicate
}
// DEPYLER-0702: Skip struct method imports that can't be valid `use` statements
// e.g., `from os.path import join` maps to `std::path::Path::join` which is invalid
// because Path is a struct, not a module. These are handled at call site.
// DEPYLER-0721: Also skip bare struct types like `std::path::Path` for inline-handled
// functions (splitext, normpath, etc.) that don't need imports
// DEPYLER-0771: Skip std::f64::isqrt - it doesn't exist; handled inline in expr_gen.rs
// Also skip any path ending with ::isqrt since Rust has no such function in std::f64
if import.path.contains("::Path::")
|| import.path.contains("::File::")
|| import.path.ends_with("::Path")
|| import.path == "std::f64::isqrt"
|| import.path.ends_with("::isqrt")
{
continue;
}
let path: syn::Path = syn::parse_str(&import.path).unwrap_or_else(|_| parse_quote! { std });
if let Some(alias) = import.alias {
let alias_ident = syn::Ident::new(&alias, proc_macro2::Span::call_site());
items.push(quote! { use #path as #alias_ident; });
} else {
items.push(quote! { use #path; });
}
}
items
}
/// Generate interned string constant tokens
///
/// Generates constant definitions for interned strings.
/// Complexity: 2 (well within <=10 target)
pub(super) fn generate_interned_string_tokens(
optimizer: &StringOptimizer,
) -> Vec<proc_macro2::TokenStream> {
let interned_constants = optimizer.generate_interned_constants();
interned_constants
.into_iter()
.filter_map(|constant| constant.parse().ok())
.collect()
}