use depyler_hir::hir::{HirExpr, HirFunction, HirStmt};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct YieldPoint {
pub state_id: usize,
pub yield_expr: HirExpr,
pub live_vars: Vec<String>,
pub depth: usize,
}
#[derive(Debug, Clone)]
pub struct YieldAnalysis {
pub yield_points: Vec<YieldPoint>,
pub state_variables: Vec<String>,
pub resume_points: HashMap<usize, usize>,
}
impl YieldAnalysis {
pub fn new() -> Self {
Self {
yield_points: Vec::new(),
state_variables: Vec::new(),
resume_points: HashMap::new(),
}
}
pub fn analyze(func: &HirFunction) -> Self {
let mut analysis = Self::new();
let mut state_counter = 1;
for (idx, stmt) in func.body.iter().enumerate() {
Self::analyze_stmt(stmt, &mut analysis, &mut state_counter, 0, idx);
}
analysis.finalize();
analysis
}
fn analyze_stmt(
stmt: &HirStmt,
analysis: &mut YieldAnalysis,
state_counter: &mut usize,
depth: usize,
stmt_idx: usize,
) {
match stmt {
HirStmt::Expr(expr) => {
Self::analyze_expr_stmt(expr, analysis, state_counter, depth, stmt_idx);
}
HirStmt::If {
then_body,
else_body,
..
} => {
Self::analyze_if_stmt(
then_body,
else_body,
analysis,
state_counter,
depth,
stmt_idx,
);
}
HirStmt::While { body, .. } | HirStmt::For { body, .. } => {
Self::analyze_loop_stmt(body, analysis, state_counter, depth, stmt_idx);
}
HirStmt::Try {
body,
handlers,
orelse,
finalbody,
} => {
Self::analyze_try_stmt(
body,
handlers,
orelse,
finalbody,
analysis,
state_counter,
(depth, stmt_idx),
);
}
HirStmt::With { body, .. } => {
Self::analyze_with_stmt(body, analysis, state_counter, depth, stmt_idx);
}
_ => {
}
}
}
#[inline]
fn analyze_expr_stmt(
expr: &HirExpr,
analysis: &mut YieldAnalysis,
state_counter: &mut usize,
depth: usize,
stmt_idx: usize,
) {
if let Some(yield_expr) = Self::extract_yield_expr(expr) {
let yield_point = YieldPoint {
state_id: *state_counter,
yield_expr,
live_vars: Vec::new(),
depth,
};
analysis.yield_points.push(yield_point);
analysis.resume_points.insert(*state_counter, stmt_idx + 1);
*state_counter += 1;
}
}
#[inline]
fn analyze_if_stmt(
then_body: &[HirStmt],
else_body: &Option<Vec<HirStmt>>,
analysis: &mut YieldAnalysis,
state_counter: &mut usize,
depth: usize,
stmt_idx: usize,
) {
for s in then_body {
Self::analyze_stmt(s, analysis, state_counter, depth, stmt_idx);
}
if let Some(else_stmts) = else_body {
for s in else_stmts {
Self::analyze_stmt(s, analysis, state_counter, depth, stmt_idx);
}
}
}
#[inline]
fn analyze_loop_stmt(
body: &[HirStmt],
analysis: &mut YieldAnalysis,
state_counter: &mut usize,
depth: usize,
stmt_idx: usize,
) {
for s in body {
Self::analyze_stmt(s, analysis, state_counter, depth + 1, stmt_idx);
}
}
#[inline]
fn analyze_try_stmt(
body: &[HirStmt],
handlers: &[depyler_hir::hir::ExceptHandler],
orelse: &Option<Vec<HirStmt>>,
finalbody: &Option<Vec<HirStmt>>,
analysis: &mut YieldAnalysis,
state_counter: &mut usize,
context: (usize, usize), ) {
let (depth, stmt_idx) = context;
for s in body {
Self::analyze_stmt(s, analysis, state_counter, depth, stmt_idx);
}
for handler in handlers {
for s in &handler.body {
Self::analyze_stmt(s, analysis, state_counter, depth, stmt_idx);
}
}
if let Some(else_stmts) = orelse {
for s in else_stmts {
Self::analyze_stmt(s, analysis, state_counter, depth, stmt_idx);
}
}
if let Some(finally_stmts) = finalbody {
for s in finally_stmts {
Self::analyze_stmt(s, analysis, state_counter, depth, stmt_idx);
}
}
}
#[inline]
fn analyze_with_stmt(
body: &[HirStmt],
analysis: &mut YieldAnalysis,
state_counter: &mut usize,
depth: usize,
stmt_idx: usize,
) {
for s in body {
Self::analyze_stmt(s, analysis, state_counter, depth, stmt_idx);
}
}
fn extract_yield_expr(expr: &HirExpr) -> Option<HirExpr> {
match expr {
HirExpr::Yield { value } => value.as_ref().map(|v| *v.clone()),
_ => None,
}
}
fn finalize(&mut self) {
}
#[inline]
pub fn has_yields(&self) -> bool {
!self.yield_points.is_empty()
}
#[inline]
pub fn num_states(&self) -> usize {
self.yield_points.len() + 1
}
}
impl Default for YieldAnalysis {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use depyler_hir::hir::*;
use depyler_annotations::TranspilationAnnotations;
use smallvec::smallvec;
#[test]
fn test_yield_point_new() {
let yp = YieldPoint {
state_id: 5,
yield_expr: HirExpr::Literal(Literal::Int(42)),
live_vars: vec!["x".to_string(), "y".to_string()],
depth: 2,
};
assert_eq!(yp.state_id, 5);
assert_eq!(yp.live_vars.len(), 2);
assert_eq!(yp.depth, 2);
}
#[test]
fn test_yield_point_clone() {
let yp = YieldPoint {
state_id: 1,
yield_expr: HirExpr::Var("x".to_string()),
live_vars: vec!["x".to_string()],
depth: 0,
};
let cloned = yp.clone();
assert_eq!(cloned.state_id, yp.state_id);
assert_eq!(cloned.depth, yp.depth);
}
#[test]
fn test_yield_point_eq() {
let yp1 = YieldPoint {
state_id: 1,
yield_expr: HirExpr::Literal(Literal::Int(1)),
live_vars: vec![],
depth: 0,
};
let yp2 = YieldPoint {
state_id: 1,
yield_expr: HirExpr::Literal(Literal::Int(1)),
live_vars: vec![],
depth: 0,
};
assert_eq!(yp1, yp2);
}
#[test]
fn test_yield_point_debug() {
let yp = YieldPoint {
state_id: 3,
yield_expr: HirExpr::Literal(Literal::Int(99)),
live_vars: vec!["z".to_string()],
depth: 1,
};
let debug = format!("{:?}", yp);
assert!(debug.contains("state_id"));
assert!(debug.contains("3"));
}
#[test]
fn test_yield_analysis_new() {
let analysis = YieldAnalysis::new();
assert!(analysis.yield_points.is_empty());
assert!(analysis.state_variables.is_empty());
assert!(analysis.resume_points.is_empty());
}
#[test]
fn test_yield_analysis_default() {
let analysis = YieldAnalysis::default();
assert!(analysis.yield_points.is_empty());
}
#[test]
fn test_yield_analysis_has_yields_empty() {
let analysis = YieldAnalysis::new();
assert!(!analysis.has_yields());
}
#[test]
fn test_yield_analysis_has_yields_true() {
let mut analysis = YieldAnalysis::new();
analysis.yield_points.push(YieldPoint {
state_id: 1,
yield_expr: HirExpr::Literal(Literal::Int(1)),
live_vars: vec![],
depth: 0,
});
assert!(analysis.has_yields());
}
#[test]
fn test_yield_analysis_num_states_empty() {
let analysis = YieldAnalysis::new();
assert_eq!(analysis.num_states(), 1); }
#[test]
fn test_yield_analysis_num_states_with_yields() {
let mut analysis = YieldAnalysis::new();
for i in 1..=5 {
analysis.yield_points.push(YieldPoint {
state_id: i,
yield_expr: HirExpr::Literal(Literal::Int(i as i64)),
live_vars: vec![],
depth: 0,
});
}
assert_eq!(analysis.num_states(), 6); }
#[test]
fn test_yield_analysis_clone() {
let mut analysis = YieldAnalysis::new();
analysis.yield_points.push(YieldPoint {
state_id: 1,
yield_expr: HirExpr::Literal(Literal::Int(1)),
live_vars: vec!["a".to_string()],
depth: 0,
});
analysis.state_variables.push("x".to_string());
analysis.resume_points.insert(1, 2);
let cloned = analysis.clone();
assert_eq!(cloned.yield_points.len(), 1);
assert_eq!(cloned.state_variables.len(), 1);
assert_eq!(cloned.resume_points.len(), 1);
}
#[test]
fn test_analyze_empty_function() {
let func = HirFunction {
name: "empty".to_string(),
params: smallvec![],
ret_type: Type::None,
body: vec![],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert!(!analysis.has_yields());
assert_eq!(analysis.num_states(), 1);
}
#[test]
fn test_analyze_non_generator_function() {
let func = HirFunction {
name: "regular".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert!(!analysis.has_yields());
}
#[test]
fn test_analyze_yield_in_if_then() {
let func = HirFunction {
name: "if_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::If {
condition: HirExpr::Var("cond".to_string()),
then_body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(1)))),
})],
else_body: None,
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
}
#[test]
fn test_analyze_yield_in_if_else() {
let func = HirFunction {
name: "if_else_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::If {
condition: HirExpr::Var("cond".to_string()),
then_body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(1)))),
})],
else_body: Some(vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(2)))),
})]),
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 2);
}
#[test]
fn test_analyze_yield_in_for_loop() {
let func = HirFunction {
name: "for_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Var("range".to_string()),
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Var("i".to_string()))),
})],
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
assert_eq!(analysis.yield_points[0].depth, 1); }
#[test]
fn test_analyze_yield_in_nested_loops() {
let func = HirFunction {
name: "nested_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::Var("items".to_string()),
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Var("i".to_string()))),
})],
}],
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
assert_eq!(analysis.yield_points[0].depth, 2); }
#[test]
fn test_analyze_yield_in_try_body() {
let func = HirFunction {
name: "try_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Try {
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(1)))),
})],
handlers: vec![],
orelse: None,
finalbody: None,
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
}
#[test]
fn test_analyze_yield_in_except_handler() {
let func = HirFunction {
name: "except_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Try {
body: vec![],
handlers: vec![ExceptHandler {
exception_type: Some("Exception".to_string()),
name: Some("e".to_string()),
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(99)))),
})],
}],
orelse: None,
finalbody: None,
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
}
#[test]
fn test_analyze_yield_in_finally() {
let func = HirFunction {
name: "finally_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Try {
body: vec![],
handlers: vec![],
orelse: None,
finalbody: Some(vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(0)))),
})]),
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
}
#[test]
fn test_analyze_yield_in_with() {
let func = HirFunction {
name: "with_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::With {
context: HirExpr::Var("ctx".to_string()),
target: Some("f".to_string()),
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Var("f".to_string()))),
})],
is_async: false,
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
}
#[test]
fn test_resume_points_tracking() {
let func = HirFunction {
name: "resume".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![
HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(1)))),
}),
HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(2)))),
}),
],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.resume_points.len(), 2);
assert_eq!(analysis.resume_points.get(&1), Some(&1));
assert_eq!(analysis.resume_points.get(&2), Some(&2));
}
#[test]
fn test_yield_none_value() {
let func = HirFunction {
name: "yield_none".to_string(),
params: smallvec![],
ret_type: Type::None,
body: vec![HirStmt::Expr(HirExpr::Yield { value: None })],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 0);
}
#[test]
#[allow(non_snake_case)]
fn test_depyler_0262_simple_yield_detection() {
let func = HirFunction {
name: "simple".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(42)))),
})],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1, "Should find 1 yield");
assert_eq!(analysis.yield_points[0].state_id, 1);
assert_eq!(analysis.num_states(), 2); }
#[test]
#[allow(non_snake_case)]
fn test_depyler_0262_loop_with_yield() {
let func = HirFunction {
name: "counter".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Var("i".to_string()))),
})],
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
assert_eq!(analysis.yield_points[0].depth, 1, "Yield is at depth 1");
}
#[test]
#[allow(non_snake_case)]
fn test_depyler_0262_multiple_yields() {
let func = HirFunction {
name: "multi".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![
HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(1)))),
}),
HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(2)))),
}),
HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(3)))),
}),
],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 3);
assert_eq!(analysis.yield_points[0].state_id, 1);
assert_eq!(analysis.yield_points[1].state_id, 2);
assert_eq!(analysis.yield_points[2].state_id, 3);
assert_eq!(analysis.num_states(), 4); }
#[test]
fn test_extract_yield_expr_with_value() {
let expr = HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(42)))),
};
let result = YieldAnalysis::extract_yield_expr(&expr);
assert!(result.is_some());
if let Some(HirExpr::Literal(Literal::Int(n))) = result {
assert_eq!(n, 42);
} else {
panic!("Expected Int literal");
}
}
#[test]
fn test_extract_yield_expr_without_value() {
let expr = HirExpr::Yield { value: None };
let result = YieldAnalysis::extract_yield_expr(&expr);
assert!(result.is_none());
}
#[test]
fn test_extract_yield_expr_not_yield() {
let expr = HirExpr::Literal(Literal::Int(42));
let result = YieldAnalysis::extract_yield_expr(&expr);
assert!(result.is_none());
}
#[test]
fn test_analyze_yield_in_try_else() {
let func = HirFunction {
name: "try_else_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Try {
body: vec![],
handlers: vec![],
orelse: Some(vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(77)))),
})]),
finalbody: None,
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
}
#[test]
fn test_analyze_all_try_blocks_with_yields() {
let func = HirFunction {
name: "try_all_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Try {
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(1)))),
})],
handlers: vec![ExceptHandler {
exception_type: Some("Exception".to_string()),
name: None,
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(2)))),
})],
}],
orelse: Some(vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(3)))),
})]),
finalbody: Some(vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(4)))),
})]),
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 4);
}
#[test]
fn test_yield_analysis_debug() {
let analysis = YieldAnalysis::new();
let debug = format!("{:?}", analysis);
assert!(debug.contains("yield_points"));
assert!(debug.contains("state_variables"));
assert!(debug.contains("resume_points"));
}
#[test]
fn test_yield_point_different_state_ids() {
let yp1 = YieldPoint {
state_id: 1,
yield_expr: HirExpr::Literal(Literal::Int(1)),
live_vars: vec![],
depth: 0,
};
let yp2 = YieldPoint {
state_id: 2,
yield_expr: HirExpr::Literal(Literal::Int(1)),
live_vars: vec![],
depth: 0,
};
assert_ne!(yp1, yp2);
}
#[test]
fn test_yield_point_different_depths() {
let yp1 = YieldPoint {
state_id: 1,
yield_expr: HirExpr::Literal(Literal::Int(1)),
live_vars: vec![],
depth: 0,
};
let yp2 = YieldPoint {
state_id: 1,
yield_expr: HirExpr::Literal(Literal::Int(1)),
live_vars: vec![],
depth: 1,
};
assert_ne!(yp1, yp2);
}
#[test]
fn test_analyze_while_loop() {
let func = HirFunction {
name: "while_yield".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::While {
condition: HirExpr::Var("running".to_string()),
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(1)))),
})],
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
assert_eq!(analysis.yield_points[0].depth, 1);
}
#[test]
fn test_analyze_other_statements() {
let func = HirFunction {
name: "mixed".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![
HirStmt::Assign {
target: AssignTarget::Symbol("x".to_string()),
value: HirExpr::Literal(Literal::Int(1)),
type_annotation: None,
},
HirStmt::Return(Some(HirExpr::Var("x".to_string()))),
],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert!(!analysis.has_yields());
}
#[test]
fn test_finalize_method() {
let mut analysis = YieldAnalysis::new();
analysis.yield_points.push(YieldPoint {
state_id: 1,
yield_expr: HirExpr::Literal(Literal::Int(1)),
live_vars: vec![],
depth: 0,
});
analysis.finalize();
assert_eq!(analysis.yield_points.len(), 1);
}
#[test]
fn test_multiple_handlers_with_yields() {
let func = HirFunction {
name: "multi_handler".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Try {
body: vec![],
handlers: vec![
ExceptHandler {
exception_type: Some("ValueError".to_string()),
name: None,
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(1)))),
})],
},
ExceptHandler {
exception_type: Some("TypeError".to_string()),
name: None,
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Literal(Literal::Int(2)))),
})],
},
],
orelse: None,
finalbody: None,
}],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 2);
}
#[test]
fn test_yield_expr_variable() {
let func = HirFunction {
name: "yield_var".to_string(),
params: smallvec![],
ret_type: Type::Int,
body: vec![HirStmt::Expr(HirExpr::Yield {
value: Some(Box::new(HirExpr::Var("result".to_string()))),
})],
properties: FunctionProperties {
is_generator: true,
..Default::default()
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let analysis = YieldAnalysis::analyze(&func);
assert_eq!(analysis.yield_points.len(), 1);
if let HirExpr::Var(name) = &analysis.yield_points[0].yield_expr {
assert_eq!(name, "result");
} else {
panic!("Expected Var expression");
}
}
}