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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
//! Index and slice conversion for ExprConverter
use crate::hir::*;
use anyhow::Result;
use syn::parse_quote;
use super::ExprConverter;
impl<'a> ExprConverter<'a> {
pub(super) fn convert_index(&self, base: &HirExpr, index: &HirExpr) -> Result<syn::Expr> {
let base_expr = self.convert(base)?;
// DEPYLER-0735: Check if base is a tuple type (from param_types) and index is integer literal
// Rust tuples use .0, .1 syntax, not [0], [1]
if let HirExpr::Literal(Literal::Int(idx)) = index {
if *idx >= 0 {
// Check if base variable has tuple type from param_types or class_field_types
let is_tuple = if let HirExpr::Var(var_name) = base {
matches!(self.param_types.get(var_name), Some(Type::Tuple(_)))
|| matches!(self.class_field_types.get(var_name), Some(Type::Tuple(_)))
} else {
false
};
if is_tuple {
let field_idx = syn::Index::from(*idx as usize);
return Ok(parse_quote! { #base_expr.#field_idx });
}
}
}
// DEPYLER-0200: Detect dict vs list access
// String literal index = dict access, use .get()
// Numeric index = list access, use [idx as usize]
let is_dict_access = match index {
HirExpr::Literal(Literal::String(_)) => true,
HirExpr::Var(name) => {
// Heuristic: variable names that look like string keys
let n = name.as_str();
n == "key"
|| n.ends_with("_key")
|| n.starts_with("key_")
|| n == "name"
|| n == "field"
|| n == "attr"
}
_ => false,
};
// Also check if base looks like a dict
let base_is_dict = match base {
HirExpr::Var(name) => {
let n = name.as_str();
n.contains("dict")
|| n.contains("map")
|| n.contains("data")
|| n == "result"
|| n == "config"
|| n == "settings"
|| n == "params"
|| n == "options"
|| n == "env"
}
HirExpr::Call { func, .. } => {
// Functions returning dicts
func.contains("dict")
|| func.contains("json")
|| func.contains("config")
|| func == "calculate_age"
|| func.contains("result")
}
_ => false,
};
// DEPYLER-1123: Check if base has bare dict type with DepylerValue keys
// DEPYLER-1214: Type::Dict(Unknown, Unknown) generates HashMap<String, DepylerValue> (String keys!)
// per type_mapper.rs DEPYLER-0776. Only an explicit DepylerValue key type would need wrapping,
// which is rare in practice. So is_depyler_value_dict is almost always false.
let is_depyler_value_dict = if let HirExpr::Var(var_name) = base {
match self.param_types.get(var_name) {
// Only wrap keys if key type is explicitly DepylerValue, not Unknown
Some(Type::Dict(k, _)) => {
matches!(k.as_ref(), Type::Custom(n) if n == "DepylerValue")
}
// Custom types like "dict" use String keys
Some(Type::Custom(_)) => false,
_ => false,
}
} else {
false
};
if is_dict_access || base_is_dict {
// DEPYLER-1320: Check if NASA mode (DepylerValue values need .into() for type conversion)
let needs_into = self.type_mapper.nasa_mode;
// DEPYLER-1123: For bare dict types (HashMap<DepylerValue, DepylerValue>),
// wrap key in DepylerValue::Str and add .into() for type conversion
if is_depyler_value_dict {
// String literal key - wrap in DepylerValue::Str
if let HirExpr::Literal(Literal::String(_)) = index {
let index_expr = self.convert(index)?;
// DEPYLER-99MODE-S9: String literal needs .to_string() for DepylerValue::Str(String)
return Ok(parse_quote! {
#base_expr.get(&DepylerValue::Str(#index_expr.to_string())).cloned().unwrap_or_default().into()
});
}
}
// DEPYLER-1320: For string literal keys, use the literal directly without .to_string()
// This prevents &"key".to_string() which creates unnecessary allocation
if let HirExpr::Literal(Literal::String(s)) = index {
if needs_into {
return Ok(parse_quote! {
#base_expr.get(#s).cloned().unwrap_or_default().into()
});
} else {
return Ok(parse_quote! {
#base_expr.get(#s).cloned().unwrap_or_default()
});
}
}
// Non-literal index - convert and borrow
let index_expr = self.convert(index)?;
// Standard dict access (HashMap<String, T> or HashMap<K, V> with known types)
if needs_into {
Ok(parse_quote! {
#base_expr.get(&#index_expr).cloned().unwrap_or_default().into()
})
} else {
Ok(parse_quote! {
#base_expr.get(&#index_expr).cloned().unwrap_or_default()
})
}
} else {
// DEPYLER-1095: Vec/List access with numeric index - handle negative indices
// Python supports list[-1] to get last element, list[-2] for second-to-last, etc.
// Check if index is a negative literal
let is_negative_literal = match index {
HirExpr::Literal(Literal::Int(idx)) => *idx < 0,
HirExpr::Unary {
op: UnaryOp::Neg, ..
} => true,
_ => false,
};
let index_expr = self.convert(index)?;
if is_negative_literal {
// For negative literals, use runtime-safe indexing:
// base.get(base.len().wrapping_add(idx as usize)).cloned().unwrap()
// This handles -1 → len-1, -2 → len-2, etc.
// DEPYLER-1140: Cast to isize first to handle usize indices correctly
Ok(parse_quote! {
{
let _base = &#base_expr;
let _idx = (#index_expr) as isize;
let _actual_idx = if _idx < 0 {
_base.len().wrapping_sub((-_idx) as usize)
} else {
_idx as usize
};
_base[_actual_idx].clone()
}
})
} else {
// For non-negative or variable indices, generate simpler code
// but still handle potential negatives at runtime
// DEPYLER-1140: Cast to isize first to handle usize indices correctly
Ok(parse_quote! {
{
let _base = &#base_expr;
let _idx = (#index_expr) as isize;
if _idx < 0 {
_base[_base.len().wrapping_sub((-_idx) as usize)].clone()
} else {
_base[_idx as usize].clone()
}
}
})
}
}
}
/// DEPYLER-1177: Check if expression has a List type (for proper slice codegen)
pub(super) fn is_list_type(&self, expr: &HirExpr) -> bool {
match expr {
HirExpr::Var(name) => {
matches!(
self.param_types.get(name),
Some(Type::List(_)) | Some(Type::Set(_))
) || matches!(
self.class_field_types.get(name),
Some(Type::List(_)) | Some(Type::Set(_))
)
}
HirExpr::Attribute { value, attr } => {
if matches!(value.as_ref(), HirExpr::Var(name) if name == "self") {
matches!(
self.class_field_types.get(attr),
Some(Type::List(_)) | Some(Type::Set(_))
)
} else {
false
}
}
HirExpr::List(_) => true,
_ => false,
}
}
/// DEPYLER-0596: Convert slice expression (e.g., value[1:-1])
pub(super) fn convert_slice(
&self,
base: &HirExpr,
start: &Option<Box<HirExpr>>,
stop: &Option<Box<HirExpr>>,
step: &Option<Box<HirExpr>>,
) -> Result<syn::Expr> {
let base_expr = self.convert(base)?;
// DEPYLER-1177: Check if base is a Vec type - use Vec slicing instead of String
if self.is_list_type(base) {
return self.convert_vec_slice(base_expr, start, stop, step);
}
// Convert start/stop/step expressions
let start_expr = start.as_ref().map(|e| self.convert(e)).transpose()?;
let stop_expr = stop.as_ref().map(|e| self.convert(e)).transpose()?;
let _step_expr = step.as_ref().map(|e| self.convert(e)).transpose()?;
// For strings: use chars().skip().take() pattern with negative index handling
// This handles cases like value[1:-1] (remove first and last chars)
match (start_expr, stop_expr) {
(Some(start), Some(stop)) => {
// value[start:stop] - handles negative indices
// DEPYLER-0603: Wrap expressions in parens to ensure proper type casting
// Without parens, `a + b as isize` parses as `a + (b as isize)`
Ok(parse_quote! {
{
let s = &#base_expr;
let len = s.chars().count() as isize;
let start_idx = (#start) as isize;
let stop_idx = (#stop) as isize;
let start = if start_idx < 0 {
(len + start_idx).max(0) as usize
} else {
start_idx as usize
};
let stop = if stop_idx < 0 {
(len + stop_idx).max(0) as usize
} else {
stop_idx as usize
};
if stop > start {
s.chars().skip(start).take(stop - start).collect::<String>()
} else {
String::new()
}
}
})
}
(Some(start), None) => {
// value[start:] - from start to end
// DEPYLER-0603: Wrap expression in parens for type casting
Ok(parse_quote! {
{
let s = &#base_expr;
let len = s.chars().count() as isize;
let start_idx = (#start) as isize;
let start = if start_idx < 0 {
(len + start_idx).max(0) as usize
} else {
start_idx as usize
};
s.chars().skip(start).collect::<String>()
}
})
}
(None, Some(stop)) => {
// value[:stop] - from beginning to stop
// DEPYLER-0603: Wrap expression in parens for type casting
Ok(parse_quote! {
{
let s = &#base_expr;
let len = s.chars().count() as isize;
let stop_idx = (#stop) as isize;
let stop = if stop_idx < 0 {
(len + stop_idx).max(0) as usize
} else {
stop_idx as usize
};
s.chars().take(stop).collect::<String>()
}
})
}
(None, None) => {
// value[:] - full clone
Ok(parse_quote! { #base_expr.clone() })
}
}
}
/// DEPYLER-1177: Convert Vec slice expression (e.g., list[start:stop])
pub(super) fn convert_vec_slice(
&self,
base_expr: syn::Expr,
start: &Option<Box<HirExpr>>,
stop: &Option<Box<HirExpr>>,
_step: &Option<Box<HirExpr>>,
) -> Result<syn::Expr> {
let start_expr = start.as_ref().map(|e| self.convert(e)).transpose()?;
let stop_expr = stop.as_ref().map(|e| self.convert(e)).transpose()?;
match (start_expr, stop_expr) {
(Some(start), Some(stop)) => {
// list[start:stop] - handles negative indices
Ok(parse_quote! {
{
let v = &#base_expr;
let len = v.len() as isize;
let start_idx = (#start) as isize;
let stop_idx = (#stop) as isize;
let start = if start_idx < 0 {
(len + start_idx).max(0) as usize
} else {
start_idx as usize
};
let stop = if stop_idx < 0 {
(len + stop_idx).max(0) as usize
} else {
stop_idx.min(len) as usize
};
if stop > start {
v[start..stop].to_vec()
} else {
vec![]
}
}
})
}
(Some(start), None) => {
// list[start:] - from start to end
Ok(parse_quote! {
{
let v = &#base_expr;
let len = v.len() as isize;
let start_idx = (#start) as isize;
let start = if start_idx < 0 {
(len + start_idx).max(0) as usize
} else {
start_idx as usize
};
v[start..].to_vec()
}
})
}
(None, Some(stop)) => {
// list[:stop] - from beginning to stop
Ok(parse_quote! {
{
let v = &#base_expr;
let len = v.len() as isize;
let stop_idx = (#stop) as isize;
let stop = if stop_idx < 0 {
(len + stop_idx).max(0) as usize
} else {
stop_idx.min(len) as usize
};
v[..stop].to_vec()
}
})
}
(None, None) => {
// list[:] - full clone
Ok(parse_quote! { #base_expr.clone() })
}
}
}
}