Skip to content

Commit 123f2f8

Browse files
noahgiftclaude
andcommitted
fix(codegen): DEPYLER-1168/1169 Call-Site Clone + Index Assignment
DEPYLER-1168: Call-Site Borrowing Heuristic - Fixes E0382 "use of moved value" when variable passed to ownership-taking function is used again later in the same scope - Added vars_used_later tracking to CodeGenContext - Populate during statement iteration in func_gen.rs - Insert .clone() at call site when variable is reused - Hero example: normalize_data(dataset.clone()) preserves dataset DEPYLER-1169: List Index Assignment - Fixed list[i] = x generating vec.insert(i, x) instead of vec[i] = x - insert() adds NEW element (shifts others), causing infinite growth - Changed to proper index operator assignment - Fixes bubble sort hang in data_analysis_combined Verification: - data_analysis_combined.py compiles and runs to completion - Output: Mean, StdDev, Percentiles, Monte Carlo simulation - Compile rate: 40.6% (130/320 files) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5dece78 commit 123f2f8

File tree

6 files changed

+50
-3
lines changed

6 files changed

+50
-3
lines changed

crates/depyler-core/src/cargo_toml_gen.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,7 @@ mod tests {
554554
type_query: None, // DEPYLER-1112
555555
last_external_call_return_type: None, // DEPYLER-1113
556556
type_overrides: std::collections::HashMap::new(), // DEPYLER-1101
557+
vars_used_later: std::collections::HashSet::new(), // DEPYLER-1168
557558
};
558559

559560
// Property: Calling extract_dependencies multiple times returns same result
@@ -723,6 +724,7 @@ mod tests {
723724
type_query: None, // DEPYLER-1112
724725
last_external_call_return_type: None, // DEPYLER-1113
725726
type_overrides: std::collections::HashMap::new(), // DEPYLER-1101
727+
vars_used_later: std::collections::HashSet::new(), // DEPYLER-1168
726728
};
727729

728730
let deps = extract_dependencies(&ctx);
@@ -889,6 +891,7 @@ mod tests {
889891
type_query: None, // DEPYLER-1112
890892
last_external_call_return_type: None, // DEPYLER-1113
891893
type_overrides: std::collections::HashMap::new(), // DEPYLER-1101
894+
vars_used_later: std::collections::HashSet::new(), // DEPYLER-1168
892895
};
893896

894897
let deps = extract_dependencies(&ctx);
@@ -1302,6 +1305,7 @@ mod tests {
13021305
type_query: None, // DEPYLER-1112
13031306
last_external_call_return_type: None, // DEPYLER-1113
13041307
type_overrides: std::collections::HashMap::new(), // DEPYLER-1101
1308+
vars_used_later: std::collections::HashSet::new(), // DEPYLER-1168
13051309
};
13061310

13071311
let deps = extract_dependencies(&ctx);

crates/depyler-core/src/rust_gen.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,7 @@ fn generate_rust_file_internal(
23672367
type_query: load_type_database(), // DEPYLER-1114: Auto-load Sovereign Type Database
23682368
last_external_call_return_type: None, // DEPYLER-1113: External call return type
23692369
type_overrides: HashMap::new(), // DEPYLER-1101: Oracle-learned type overrides
2370+
vars_used_later: HashSet::new(), // DEPYLER-1168: Call-site clone detection
23702371
};
23712372

23722373
// DEPYLER-1137: Enable DepylerValue enum when module aliases are present
@@ -5695,6 +5696,7 @@ mod tests {
56955696
type_query: None, // DEPYLER-1112
56965697
last_external_call_return_type: None, // DEPYLER-1113
56975698
type_overrides: HashMap::new(), // DEPYLER-1101: Oracle-learned type overrides
5699+
vars_used_later: HashSet::new(), // DEPYLER-1168: Call-site clone detection
56985700
}
56995701
}
57005702

crates/depyler-core/src/rust_gen/context.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,12 @@ pub struct CodeGenContext<'a> {
384384
/// Maps variable name -> correct Type. Overrides inferred types during codegen.
385385
/// Populated by repair_file_types() in utol.rs via transpile_with_constraints().
386386
pub type_overrides: HashMap<String, Type>,
387+
388+
/// DEPYLER-1168: Track variables that are used after the current statement
389+
/// When a function takes ownership of a parameter and the argument variable is used
390+
/// later in the same scope, we need to insert .clone() at the call site.
391+
/// Populated during statement iteration in stmt_gen.rs.
392+
pub vars_used_later: HashSet<String>,
387393
}
388394

389395
impl<'a> CodeGenContext<'a> {
@@ -781,6 +787,7 @@ pub mod test_helpers {
781787
type_query: None, // DEPYLER-1112
782788
last_external_call_return_type: None, // DEPYLER-1113
783789
type_overrides: HashMap::new(), // DEPYLER-1101
790+
vars_used_later: HashSet::new(), // DEPYLER-1168
784791
}
785792
}
786793
}

crates/depyler-core/src/rust_gen/expr_gen.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4779,6 +4779,27 @@ impl<'a, 'b> ExpressionConverter<'a, 'b> {
47794779
}
47804780
}
47814781

4782+
// DEPYLER-1168: Call-site clone insertion for variables used later
4783+
// When a function takes ownership (doesn't borrow) and the argument
4784+
// variable is used again later in the same scope, we need to clone it.
4785+
// This prevents E0382 "use of moved value" errors.
4786+
if let HirExpr::Var(var_name) = hir_arg {
4787+
// Only clone if:
4788+
// 1. Variable is used later in the same scope
4789+
// 2. Variable type is clonable (List, Dict, Set, String, Custom types)
4790+
let used_later = self.ctx.vars_used_later.contains(var_name);
4791+
let is_clonable_type = self.ctx.var_types.get(var_name)
4792+
.map(|ty| matches!(ty,
4793+
Type::List(_) | Type::Dict(_, _) | Type::Set(_) |
4794+
Type::String | Type::Tuple(_) | Type::Custom(_)
4795+
))
4796+
.unwrap_or(false);
4797+
4798+
if used_later && is_clonable_type {
4799+
return parse_quote! { #arg_expr.clone() };
4800+
}
4801+
}
4802+
47824803
arg_expr.clone()
47834804
}
47844805
})

crates/depyler-core/src/rust_gen/func_gen.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,17 @@ pub(crate) fn codegen_function_body(
800800
// Mark final statement for idiomatic expression-based return
801801
// (only if it's not a FunctionDef, as those are assignments not returns)
802802
ctx.is_final_statement = i == body_len - 1 && !matches!(stmt, HirStmt::FunctionDef { .. });
803+
804+
// DEPYLER-1168: Populate vars_used_later for call-site clone detection
805+
// Before processing each statement, compute which variables are used in remaining statements
806+
ctx.vars_used_later.clear();
807+
let remaining_stmts = &func.body[i + 1..];
808+
for var_name in ctx.var_types.keys() {
809+
if is_var_used_in_remaining_stmts(var_name, remaining_stmts) {
810+
ctx.vars_used_later.insert(var_name.clone());
811+
}
812+
}
813+
803814
let tokens = stmt.to_rust_tokens(ctx)?;
804815
body_stmts.push(tokens);
805816
}

crates/depyler-core/src/rust_gen/stmt_gen.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4826,9 +4826,11 @@ pub(crate) fn codegen_assign_index(
48264826
if indices.is_empty() {
48274827
// Simple assignment: d[k] = v OR list[i] = x
48284828
if is_numeric_index {
4829-
// DEPYLER-0314: Vec.insert(index as usize, value)
4830-
// Wrap in parentheses to ensure correct operator precedence
4831-
Ok(quote! { #base_expr.insert((#final_index) as usize, #final_value_expr); })
4829+
// DEPYLER-1169: List index assignment uses index operator, NOT insert()
4830+
// Python: list[i] = x → replaces element at index i
4831+
// Rust: vec[i] = x → replaces element at index i
4832+
// WRONG: vec.insert(i, x) → inserts NEW element, shifts others (causes bugs!)
4833+
Ok(quote! { #base_expr[(#final_index) as usize] = #final_value_expr; })
48324834
} else if needs_as_object_mut {
48334835
// DEPYLER-0449: serde_json::Value needs .as_object_mut() for insert
48344836
// DEPYLER-0473: Clone key to avoid move-after-use errors

0 commit comments

Comments
 (0)