use crate::annotation_aware_type_mapper::AnnotationAwareTypeMapper;
use crate::generator_state::GeneratorStateInfo;
use crate::hir::*;
use crate::lifetime_analysis::LifetimeInference;
use crate::string_optimization::{StringContext, StringOptimizer};
use anyhow::{bail, Result};
use quote::{quote, ToTokens};
use std::collections::HashSet;
use syn::{self, parse_quote};
pub struct CodeGenContext<'a> {
pub type_mapper: &'a crate::type_mapper::TypeMapper,
pub annotation_aware_mapper: AnnotationAwareTypeMapper,
pub string_optimizer: StringOptimizer,
pub union_enum_generator: crate::union_enum_gen::UnionEnumGenerator,
pub generated_enums: Vec<proc_macro2::TokenStream>,
pub needs_hashmap: bool,
pub needs_hashset: bool,
pub needs_fnv_hashmap: bool,
pub needs_ahash_hashmap: bool,
pub needs_arc: bool,
pub needs_rc: bool,
pub needs_cow: bool,
pub declared_vars: Vec<HashSet<String>>,
pub current_function_can_fail: bool,
pub current_return_type: Option<Type>,
pub module_mapper: crate::module_mapper::ModuleMapper,
pub imported_modules: std::collections::HashMap<String, crate::module_mapper::ModuleMapping>,
pub imported_items: std::collections::HashMap<String, String>,
pub mutable_vars: HashSet<String>,
pub needs_zerodivisionerror: bool,
pub needs_indexerror: bool,
pub is_classmethod: bool,
pub in_generator: bool,
pub generator_state_vars: HashSet<String>,
}
impl<'a> CodeGenContext<'a> {
fn enter_scope(&mut self) {
self.declared_vars.push(HashSet::new());
}
fn exit_scope(&mut self) {
self.declared_vars.pop();
}
fn is_declared(&self, var_name: &str) -> bool {
self.declared_vars
.iter()
.any(|scope| scope.contains(var_name))
}
fn declare_var(&mut self, var_name: &str) {
if let Some(current_scope) = self.declared_vars.last_mut() {
current_scope.insert(var_name.to_string());
}
}
pub fn process_union_type(&mut self, types: &[crate::hir::Type]) -> String {
let (enum_name, enum_def) = self.union_enum_generator.generate_union_enum(types);
if !enum_def.is_empty() {
self.generated_enums.push(enum_def);
}
enum_name
}
}
pub trait RustCodeGen {
fn to_rust_tokens(&self, ctx: &mut CodeGenContext) -> Result<proc_macro2::TokenStream>;
}
fn process_whole_module_import(
import: &Import,
module_mapper: &crate::module_mapper::ModuleMapper,
imported_modules: &mut std::collections::HashMap<String, crate::module_mapper::ModuleMapping>,
) {
if let Some(mapping) = module_mapper.get_mapping(&import.module) {
imported_modules.insert(import.module.clone(), mapping.clone());
}
}
fn process_import_item(
import_module: &str,
item_name: &str,
import_key: &str, mapping: &crate::module_mapper::ModuleMapping,
imported_items: &mut std::collections::HashMap<String, String>,
) {
if let Some(rust_name) = mapping.item_map.get(item_name) {
if import_module == "typing" && !rust_name.is_empty() {
imported_items.insert(import_key.to_string(), rust_name.clone());
} else if !mapping.rust_path.is_empty() {
imported_items.insert(
import_key.to_string(),
format!("{}::{}", mapping.rust_path, rust_name),
);
}
}
}
fn process_specific_items_import(
import: &Import,
module_mapper: &crate::module_mapper::ModuleMapper,
imported_items: &mut std::collections::HashMap<String, String>,
) {
if let Some(mapping) = module_mapper.get_mapping(&import.module) {
for item in &import.items {
match item {
ImportItem::Named(name) => {
process_import_item(&import.module, name, name, mapping, imported_items);
}
ImportItem::Aliased { name, alias } => {
process_import_item(&import.module, name, alias, mapping, imported_items);
}
}
}
}
}
fn process_module_imports(
imports: &[Import],
module_mapper: &crate::module_mapper::ModuleMapper,
) -> (
std::collections::HashMap<String, crate::module_mapper::ModuleMapping>,
std::collections::HashMap<String, String>,
) {
let mut imported_modules = std::collections::HashMap::new();
let mut imported_items = std::collections::HashMap::new();
for import in imports {
if import.items.is_empty() {
process_whole_module_import(import, module_mapper, &mut imported_modules);
} else {
process_specific_items_import(import, module_mapper, &mut imported_items);
}
}
(imported_modules, imported_items)
}
fn analyze_string_optimization(ctx: &mut CodeGenContext, functions: &[HirFunction]) {
for func in functions {
ctx.string_optimizer.analyze_function(func);
}
}
fn analyze_mutable_vars(stmts: &[HirStmt], ctx: &mut CodeGenContext) {
let mut declared = HashSet::new();
fn analyze_stmt(stmt: &HirStmt, declared: &mut HashSet<String>, mutable: &mut HashSet<String>) {
match stmt {
HirStmt::Assign { target, .. } => {
match target {
AssignTarget::Symbol(name) => {
if declared.contains(name) {
mutable.insert(name.clone());
} else {
declared.insert(name.clone());
}
}
AssignTarget::Tuple(targets) => {
for t in targets {
if let AssignTarget::Symbol(name) = t {
if declared.contains(name) {
mutable.insert(name.clone());
} else {
declared.insert(name.clone());
}
}
}
}
_ => {}
}
}
HirStmt::If {
then_body,
else_body,
..
} => {
for stmt in then_body {
analyze_stmt(stmt, declared, mutable);
}
if let Some(else_stmts) = else_body {
for stmt in else_stmts {
analyze_stmt(stmt, declared, mutable);
}
}
}
HirStmt::While { body, .. } | HirStmt::For { body, .. } => {
for stmt in body {
analyze_stmt(stmt, declared, mutable);
}
}
_ => {}
}
}
for stmt in stmts {
analyze_stmt(stmt, &mut declared, &mut ctx.mutable_vars);
}
}
fn convert_classes_to_rust(
classes: &[HirClass],
type_mapper: &crate::type_mapper::TypeMapper,
) -> Result<Vec<proc_macro2::TokenStream>> {
let mut class_items = Vec::new();
for class in classes {
let items = crate::direct_rules::convert_class_to_struct(class, type_mapper)?;
for item in items {
let tokens = item.to_token_stream();
class_items.push(tokens);
}
}
Ok(class_items)
}
fn convert_functions_to_rust(
functions: &[HirFunction],
ctx: &mut CodeGenContext,
) -> Result<Vec<proc_macro2::TokenStream>> {
functions
.iter()
.map(|f| f.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()
}
fn generate_conditional_imports(ctx: &CodeGenContext) -> Vec<proc_macro2::TokenStream> {
let mut imports = Vec::new();
let conditional_imports = [
(ctx.needs_hashmap, quote! { use std::collections::HashMap; }),
(ctx.needs_hashset, quote! { use std::collections::HashSet; }),
(ctx.needs_fnv_hashmap, quote! { use fnv::FnvHashMap; }),
(ctx.needs_ahash_hashmap, quote! { use ahash::AHashMap; }),
(ctx.needs_arc, quote! { use std::sync::Arc; }),
(ctx.needs_rc, quote! { use std::rc::Rc; }),
(ctx.needs_cow, quote! { use std::borrow::Cow; }),
];
for (needed, import_tokens) in conditional_imports {
if needed {
imports.push(import_tokens);
}
}
imports
}
fn generate_error_type_definitions(ctx: &CodeGenContext) -> Vec<proc_macro2::TokenStream> {
let mut definitions = Vec::new();
if ctx.needs_zerodivisionerror {
definitions.push(quote! {
#[derive(Debug, Clone)]
pub struct ZeroDivisionError {
message: String,
}
impl std::fmt::Display for ZeroDivisionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "division by zero: {}", self.message)
}
}
impl std::error::Error for ZeroDivisionError {}
impl ZeroDivisionError {
pub fn new(message: impl Into<String>) -> Self {
Self { message: message.into() }
}
}
});
}
if ctx.needs_indexerror {
definitions.push(quote! {
#[derive(Debug, Clone)]
pub struct IndexError {
message: String,
}
impl std::fmt::Display for IndexError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "index out of range: {}", self.message)
}
}
impl std::error::Error for IndexError {}
impl IndexError {
pub fn new(message: impl Into<String>) -> Self {
Self { message: message.into() }
}
}
});
}
definitions
}
fn generate_import_tokens(
imports: &[Import],
module_mapper: &crate::module_mapper::ModuleMapper,
) -> Vec<proc_macro2::TokenStream> {
let mut items = Vec::new();
let mut external_imports = Vec::new();
let mut std_imports = Vec::new();
for import in imports {
let rust_imports = module_mapper.map_import(import);
for rust_import in rust_imports {
if rust_import.path.starts_with("//") {
let comment = &rust_import.path;
items.push(quote! { #[doc = #comment] });
} else if rust_import.is_external {
external_imports.push(rust_import);
} else {
std_imports.push(rust_import);
}
}
}
for import in external_imports {
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; });
}
}
for import in std_imports {
if import.path.starts_with("::") || import.path.is_empty() {
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
}
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()
}
pub fn generate_rust_file(
module: &HirModule,
type_mapper: &crate::type_mapper::TypeMapper,
) -> Result<String> {
let module_mapper = crate::module_mapper::ModuleMapper::new();
let (imported_modules, imported_items) =
process_module_imports(&module.imports, &module_mapper);
let mut ctx = CodeGenContext {
type_mapper,
annotation_aware_mapper: AnnotationAwareTypeMapper::with_base_mapper(type_mapper.clone()),
string_optimizer: StringOptimizer::new(),
union_enum_generator: crate::union_enum_gen::UnionEnumGenerator::new(),
generated_enums: Vec::new(),
needs_hashmap: false,
needs_hashset: false,
needs_fnv_hashmap: false,
needs_ahash_hashmap: false,
needs_arc: false,
needs_rc: false,
needs_cow: false,
declared_vars: vec![HashSet::new()],
current_function_can_fail: false,
current_return_type: None,
module_mapper,
imported_modules,
imported_items,
mutable_vars: HashSet::new(),
needs_zerodivisionerror: false,
in_generator: false,
needs_indexerror: false,
is_classmethod: false,
generator_state_vars: HashSet::new(),
};
analyze_string_optimization(&mut ctx, &module.functions);
let classes = convert_classes_to_rust(&module.classes, ctx.type_mapper)?;
let functions = convert_functions_to_rust(&module.functions, &mut ctx)?;
let mut items = Vec::new();
let import_mapper = crate::module_mapper::ModuleMapper::new();
items.extend(generate_import_tokens(&module.imports, &import_mapper));
items.extend(generate_interned_string_tokens(&ctx.string_optimizer));
items.extend(generate_conditional_imports(&ctx));
items.extend(generate_error_type_definitions(&ctx));
items.extend(ctx.generated_enums.clone());
items.extend(classes);
items.extend(functions);
let test_gen = crate::test_generation::TestGenerator::new(Default::default());
let mut test_modules = Vec::new();
for func in &module.functions {
if let Some(test_module) = test_gen.generate_tests(func)? {
test_modules.push(test_module);
}
}
items.extend(test_modules);
let file = quote! {
#(#items)*
};
Ok(format_rust_code(file.to_string()))
}
fn generate_state_fields(
state_info: &crate::generator_state::GeneratorStateInfo,
ctx: &mut CodeGenContext,
) -> Result<Vec<proc_macro2::TokenStream>> {
state_info
.state_variables
.iter()
.map(|var| {
let field_name = syn::Ident::new(&var.name, proc_macro2::Span::call_site());
let rust_type = ctx.type_mapper.map_type(&var.ty);
let field_type = rust_type_to_syn(&rust_type)?;
Ok(quote! { #field_name: #field_type })
})
.collect()
}
fn generate_param_fields(
func: &HirFunction,
state_info: &crate::generator_state::GeneratorStateInfo,
ctx: &mut CodeGenContext,
) -> Result<Vec<proc_macro2::TokenStream>> {
func.params
.iter()
.filter(|p| state_info.captured_params.contains(&p.name))
.map(|param| {
let field_name = syn::Ident::new(¶m.name, proc_macro2::Span::call_site());
let rust_type = ctx.type_mapper.map_type(¶m.ty);
let field_type = rust_type_to_syn(&rust_type)?;
Ok(quote! { #field_name: #field_type })
})
.collect()
}
fn extract_generator_item_type(
rust_ret_type: &crate::type_mapper::RustType,
) -> Result<syn::Type> {
rust_type_to_syn(rust_ret_type)
}
fn generate_state_initializers(
state_info: &crate::generator_state::GeneratorStateInfo,
) -> Vec<proc_macro2::TokenStream> {
state_info
.state_variables
.iter()
.map(|var| {
let field_name = syn::Ident::new(&var.name, proc_macro2::Span::call_site());
let default_value = get_default_value_for_type(&var.ty);
quote! { #field_name: #default_value }
})
.collect()
}
fn generate_param_initializers(
func: &HirFunction,
state_info: &crate::generator_state::GeneratorStateInfo,
) -> Vec<proc_macro2::TokenStream> {
func.params
.iter()
.filter(|p| state_info.captured_params.contains(&p.name))
.map(|param| {
let field_name = syn::Ident::new(¶m.name, proc_macro2::Span::call_site());
quote! { #field_name: #field_name }
})
.collect()
}
fn get_default_value_for_type(ty: &Type) -> proc_macro2::TokenStream {
match ty {
Type::Int => quote! { 0 },
Type::Float => quote! { 0.0 },
Type::Bool => quote! { false },
Type::String => quote! { String::new() },
_ => quote! { Default::default() },
}
}
#[inline]
fn codegen_generic_params(
type_params: &[crate::generic_inference::TypeParameter],
lifetime_params: &[String],
) -> proc_macro2::TokenStream {
if type_params.is_empty() && lifetime_params.is_empty() {
return quote! {};
}
let mut all_params = Vec::new();
for lt in lifetime_params {
let lt_ident = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
all_params.push(quote! { #lt_ident });
}
for type_param in type_params {
let param_name = syn::Ident::new(&type_param.name, proc_macro2::Span::call_site());
if type_param.bounds.is_empty() {
all_params.push(quote! { #param_name });
} else {
let bounds: Vec<_> = type_param
.bounds
.iter()
.map(|b| {
let bound: syn::Path =
syn::parse_str(b).unwrap_or_else(|_| parse_quote! { Clone });
quote! { #bound }
})
.collect();
all_params.push(quote! { #param_name: #(#bounds)+* });
}
}
quote! { <#(#all_params),*> }
}
#[inline]
fn codegen_where_clause(lifetime_bounds: &[(String, String)]) -> proc_macro2::TokenStream {
if lifetime_bounds.is_empty() {
return quote! {};
}
let bounds: Vec<_> = lifetime_bounds
.iter()
.map(|(from, to)| {
let from_lt = syn::Lifetime::new(from, proc_macro2::Span::call_site());
let to_lt = syn::Lifetime::new(to, proc_macro2::Span::call_site());
quote! { #from_lt: #to_lt }
})
.collect();
quote! { where #(#bounds),* }
}
#[inline]
fn codegen_function_attrs(
docstring: &Option<String>,
properties: &crate::hir::FunctionProperties,
) -> Vec<proc_macro2::TokenStream> {
let mut attrs = vec![];
if let Some(docstring) = docstring {
attrs.push(quote! {
#[doc = #docstring]
});
}
if properties.panic_free {
attrs.push(quote! {
#[doc = " Depyler: verified panic-free"]
});
}
if properties.always_terminates {
attrs.push(quote! {
#[doc = " Depyler: proven to terminate"]
});
}
attrs
}
#[inline]
fn codegen_function_body(
func: &HirFunction,
can_fail: bool,
ctx: &mut CodeGenContext,
) -> Result<Vec<proc_macro2::TokenStream>> {
ctx.enter_scope();
ctx.current_function_can_fail = can_fail;
ctx.current_return_type = Some(func.ret_type.clone());
for param in &func.params {
ctx.declare_var(¶m.name);
}
analyze_mutable_vars(&func.body, ctx);
let body_stmts: Vec<_> = func
.body
.iter()
.map(|stmt| stmt.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
ctx.exit_scope();
ctx.current_function_can_fail = false;
ctx.current_return_type = None;
Ok(body_stmts)
}
#[inline]
fn codegen_function_params(
func: &HirFunction,
lifetime_result: &crate::lifetime_analysis::LifetimeResult,
ctx: &mut CodeGenContext,
) -> Result<Vec<proc_macro2::TokenStream>> {
func.params
.iter()
.map(|param| codegen_single_param(param, func, lifetime_result, ctx))
.collect()
}
fn codegen_single_param(
param: &HirParam,
func: &HirFunction,
lifetime_result: &crate::lifetime_analysis::LifetimeResult,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let param_ident = syn::Ident::new(¶m.name, proc_macro2::Span::call_site());
let is_param_mutated = matches!(
lifetime_result.borrowing_strategies.get(¶m.name),
Some(crate::borrowing_context::BorrowingStrategy::TakeOwnership)
) && func.body.iter().any(
|stmt| matches!(stmt, HirStmt::Assign { target: AssignTarget::Symbol(s), .. } if s == ¶m.name),
);
if let Some(inferred) = lifetime_result.param_lifetimes.get(¶m.name) {
let rust_type = &inferred.rust_type;
let actual_rust_type = if let crate::type_mapper::RustType::Enum { name, variants: _ } = rust_type {
if name == "UnionType" {
if let Type::Union(types) = ¶m.ty {
let enum_name = ctx.process_union_type(types);
crate::type_mapper::RustType::Custom(enum_name)
} else {
rust_type.clone()
}
} else {
rust_type.clone()
}
} else {
rust_type.clone()
};
update_import_needs(ctx, &actual_rust_type);
let ty = apply_param_borrowing_strategy(
¶m.name,
&actual_rust_type,
inferred,
lifetime_result,
ctx,
)?;
Ok(if is_param_mutated {
quote! { mut #param_ident: #ty }
} else {
quote! { #param_ident: #ty }
})
} else {
let rust_type = ctx
.annotation_aware_mapper
.map_type_with_annotations(¶m.ty, &func.annotations);
update_import_needs(ctx, &rust_type);
let ty = rust_type_to_syn(&rust_type)?;
Ok(if is_param_mutated {
quote! { mut #param_ident: #ty }
} else {
quote! { #param_ident: #ty }
})
}
}
fn apply_param_borrowing_strategy(
param_name: &str,
rust_type: &crate::type_mapper::RustType,
inferred: &crate::lifetime_analysis::InferredParam,
lifetime_result: &crate::lifetime_analysis::LifetimeResult,
ctx: &mut CodeGenContext,
) -> Result<syn::Type> {
let mut ty = rust_type_to_syn(rust_type)?;
if let Some(strategy) = lifetime_result.borrowing_strategies.get(param_name) {
match strategy {
crate::borrowing_context::BorrowingStrategy::UseCow { lifetime } => {
ctx.needs_cow = true;
let lt = syn::Lifetime::new(lifetime, proc_macro2::Span::call_site());
ty = parse_quote! { Cow<#lt, str> };
}
_ => {
if inferred.should_borrow {
ty = apply_borrowing_to_type(ty, rust_type, inferred)?;
}
}
}
} else {
if inferred.should_borrow {
ty = apply_borrowing_to_type(ty, rust_type, inferred)?;
}
}
Ok(ty)
}
fn apply_borrowing_to_type(
mut ty: syn::Type,
rust_type: &crate::type_mapper::RustType,
inferred: &crate::lifetime_analysis::InferredParam,
) -> Result<syn::Type> {
if matches!(rust_type, crate::type_mapper::RustType::String) {
if let Some(ref lifetime) = inferred.lifetime {
let lt = syn::Lifetime::new(lifetime.as_str(), proc_macro2::Span::call_site());
ty = if inferred.needs_mut {
parse_quote! { &#lt mut str }
} else {
parse_quote! { &#lt str }
};
} else {
ty = if inferred.needs_mut {
parse_quote! { &mut str }
} else {
parse_quote! { &str }
};
}
} else {
if let Some(ref lifetime) = inferred.lifetime {
let lt = syn::Lifetime::new(lifetime.as_str(), proc_macro2::Span::call_site());
ty = if inferred.needs_mut {
parse_quote! { &#lt mut #ty }
} else {
parse_quote! { &#lt #ty }
};
} else {
ty = if inferred.needs_mut {
parse_quote! { &mut #ty }
} else {
parse_quote! { &#ty }
};
}
}
Ok(ty)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StringMethodReturnType {
Owned,
Borrowed,
}
fn classify_string_method(method_name: &str) -> StringMethodReturnType {
match method_name {
"upper" | "lower" | "strip" | "lstrip" | "rstrip"
| "replace" | "format" | "title" | "capitalize"
| "swapcase" | "expandtabs" | "center" | "ljust"
| "rjust" | "zfill" => StringMethodReturnType::Owned,
"startswith" | "endswith" | "isalpha" | "isdigit"
| "isalnum" | "isspace" | "islower" | "isupper"
| "istitle" | "isascii" | "isprintable"
| "find" | "rfind" | "index" | "rindex"
| "count" => StringMethodReturnType::Borrowed,
_ => StringMethodReturnType::Owned,
}
}
fn contains_owned_string_method(expr: &HirExpr) -> bool {
match expr {
HirExpr::MethodCall { method, .. } => {
classify_string_method(method) == StringMethodReturnType::Owned
}
HirExpr::Binary { left, right, .. } => {
contains_owned_string_method(left) || contains_owned_string_method(right)
}
HirExpr::Unary { operand, .. } => {
contains_owned_string_method(operand)
}
HirExpr::IfExpr { body, orelse, .. } => {
contains_owned_string_method(body) || contains_owned_string_method(orelse)
}
HirExpr::Call { .. }
| HirExpr::Var(_)
| HirExpr::Literal(_)
| HirExpr::List(_)
| HirExpr::Dict(_)
| HirExpr::Tuple(_)
| HirExpr::Set(_)
| HirExpr::FrozenSet(_)
| HirExpr::Index { .. }
| HirExpr::Slice { .. }
| HirExpr::Attribute { .. }
| HirExpr::Borrow { .. }
| HirExpr::ListComp { .. }
| HirExpr::SetComp { .. }
| HirExpr::Lambda { .. }
| HirExpr::Await { .. }
| HirExpr::FString { .. }
| HirExpr::Yield { .. }
| HirExpr::SortByKey { .. }
| HirExpr::GeneratorExp { .. } => false,
}
}
fn function_returns_owned_string(func: &HirFunction) -> bool {
for stmt in &func.body {
if let HirStmt::Return(Some(expr)) = stmt {
if contains_owned_string_method(expr) {
return true;
}
}
}
false
}
fn return_type_expects_float(ty: &Type) -> bool {
match ty {
Type::Float => true,
Type::Optional(inner) => return_type_expects_float(inner),
Type::List(inner) => return_type_expects_float(inner),
Type::Tuple(types) => types.iter().any(return_type_expects_float),
_ => false,
}
}
#[inline]
fn codegen_return_type(
func: &HirFunction,
lifetime_result: &crate::lifetime_analysis::LifetimeResult,
ctx: &mut CodeGenContext,
) -> Result<(proc_macro2::TokenStream, crate::type_mapper::RustType, bool)> {
let mapped_ret_type = ctx
.annotation_aware_mapper
.map_return_type_with_annotations(&func.ret_type, &func.annotations);
let rust_ret_type =
if let crate::type_mapper::RustType::Enum { name, .. } = &mapped_ret_type {
if name == "UnionType" {
if let Type::Union(types) = &func.ret_type {
let enum_name = ctx.process_union_type(types);
crate::type_mapper::RustType::Custom(enum_name)
} else {
mapped_ret_type
}
} else {
mapped_ret_type
}
} else {
mapped_ret_type
};
let rust_ret_type = if matches!(func.ret_type, Type::String)
&& function_returns_owned_string(func)
{
crate::type_mapper::RustType::String
} else {
rust_ret_type
};
update_import_needs(ctx, &rust_ret_type);
let can_fail = func.properties.can_fail;
let error_type_str = if can_fail && !func.properties.error_types.is_empty() {
if func.properties.error_types.len() == 1 {
func.properties.error_types[0].clone()
} else {
"Box<dyn std::error::Error>".to_string()
}
} else {
"Box<dyn std::error::Error>".to_string()
};
if error_type_str.contains("ZeroDivisionError") {
ctx.needs_zerodivisionerror = true;
}
if error_type_str.contains("IndexError") {
ctx.needs_indexerror = true;
}
let return_type = if matches!(rust_ret_type, crate::type_mapper::RustType::Unit) {
if can_fail {
let error_type: syn::Type = syn::parse_str(&error_type_str)
.unwrap_or_else(|_| parse_quote! { Box<dyn std::error::Error> });
quote! { -> Result<(), #error_type> }
} else {
quote! {}
}
} else {
let mut ty = rust_type_to_syn(&rust_ret_type)?;
let mut uses_cow_return = false;
for param in &func.params {
if let Some(strategy) = lifetime_result.borrowing_strategies.get(¶m.name) {
if matches!(
strategy,
crate::borrowing_context::BorrowingStrategy::UseCow { .. }
) {
if let Some(_usage) = lifetime_result.param_lifetimes.get(¶m.name) {
if matches!(func.ret_type, crate::hir::Type::String) {
uses_cow_return = true;
break;
}
}
}
}
}
if uses_cow_return {
ctx.needs_cow = true;
if let Some(ref return_lt) = lifetime_result.return_lifetime {
let lt = syn::Lifetime::new(return_lt.as_str(), proc_macro2::Span::call_site());
ty = parse_quote! { Cow<#lt, str> };
} else {
ty = parse_quote! { Cow<'static, str> };
}
} else {
let returns_owned_string = matches!(func.ret_type, Type::String)
&& function_returns_owned_string(func);
if let Some(ref return_lt) = lifetime_result.return_lifetime {
if matches!(
rust_ret_type,
crate::type_mapper::RustType::Str { .. }
| crate::type_mapper::RustType::Reference { .. }
) && !returns_owned_string {
let lt = syn::Lifetime::new(return_lt.as_str(), proc_macro2::Span::call_site());
match &rust_ret_type {
crate::type_mapper::RustType::Str { .. } => {
ty = parse_quote! { &#lt str };
}
crate::type_mapper::RustType::Reference { mutable, inner, .. } => {
let inner_ty = rust_type_to_syn(inner)?;
ty = if *mutable {
parse_quote! { &#lt mut #inner_ty }
} else {
parse_quote! { &#lt #inner_ty }
};
}
_ => {}
}
}
}
}
if can_fail {
let error_type: syn::Type = syn::parse_str(&error_type_str)
.unwrap_or_else(|_| parse_quote! { Box<dyn std::error::Error> });
quote! { -> Result<#ty, #error_type> }
} else {
quote! { -> #ty }
}
};
Ok((return_type, rust_ret_type, can_fail))
}
#[inline]
#[allow(clippy::too_many_arguments)] fn codegen_generator_function(
func: &HirFunction,
name: &syn::Ident,
generic_params: &proc_macro2::TokenStream,
where_clause: &proc_macro2::TokenStream,
params: &[proc_macro2::TokenStream],
attrs: &[proc_macro2::TokenStream],
rust_ret_type: &crate::type_mapper::RustType,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let state_info = GeneratorStateInfo::analyze(func);
let state_struct_name = format!(
"{}State",
name.to_string()
.chars()
.next()
.map(|c| c.to_uppercase().to_string())
.unwrap_or_default()
+ &name.to_string()[1..]
);
let state_ident = syn::Ident::new(&state_struct_name, name.span());
let state_fields = generate_state_fields(&state_info, ctx)?;
let param_fields = generate_param_fields(func, &state_info, ctx)?;
let all_fields = [state_fields, param_fields].concat();
let state_inits = generate_state_initializers(&state_info);
let param_inits = generate_param_initializers(func, &state_info);
let all_inits = [state_inits, param_inits].concat();
let state_machine_field = quote! {
state: usize
};
let item_type = extract_generator_item_type(rust_ret_type)?;
ctx.generator_state_vars.clear();
for var in &state_info.state_variables {
ctx.generator_state_vars.insert(var.name.clone());
}
for param in &state_info.captured_params {
ctx.generator_state_vars.insert(param.clone());
}
ctx.in_generator = true;
let generator_body_stmts: Vec<_> = func
.body
.iter()
.map(|stmt| stmt.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
ctx.in_generator = false;
ctx.generator_state_vars.clear();
Ok(quote! {
#(#attrs)*
#[doc = " Generator state struct"]
#[derive(Debug)]
struct #state_ident {
#state_machine_field,
#(#all_fields),*
}
#[doc = " Generator function - returns Iterator"]
pub fn #name #generic_params(#(#params),*) -> impl Iterator<Item = #item_type> #where_clause {
#state_ident {
state: 0,
#(#all_inits),*
}
}
impl Iterator for #state_ident {
type Item = #item_type;
fn next(&mut self) -> Option<Self::Item> {
match self.state {
0 => {
self.state = 1;
#(#generator_body_stmts)*
None
}
_ => None
}
}
}
})
}
impl RustCodeGen for HirFunction {
fn to_rust_tokens(&self, ctx: &mut CodeGenContext) -> Result<proc_macro2::TokenStream> {
let name = syn::Ident::new(&self.name, proc_macro2::Span::call_site());
let mut generic_registry = crate::generic_inference::TypeVarRegistry::new();
let type_params = generic_registry.infer_function_generics(self)?;
let mut lifetime_inference = LifetimeInference::new();
let lifetime_result = lifetime_inference.analyze_function(self, ctx.type_mapper);
let generic_params = codegen_generic_params(&type_params, &lifetime_result.lifetime_params);
let where_clause = codegen_where_clause(&lifetime_result.lifetime_bounds);
let params = codegen_function_params(self, &lifetime_result, ctx)?;
let (return_type, rust_ret_type, can_fail) = codegen_return_type(self, &lifetime_result, ctx)?;
let body_stmts = codegen_function_body(self, can_fail, ctx)?;
let attrs = codegen_function_attrs(&self.docstring, &self.properties);
let func_tokens = if self.properties.is_generator {
codegen_generator_function(
self,
&name,
&generic_params,
&where_clause,
¶ms,
&attrs,
&rust_ret_type,
ctx,
)?
} else if self.properties.is_async {
quote! {
#(#attrs)*
pub async fn #name #generic_params(#(#params),*) #return_type #where_clause {
#(#body_stmts)*
}
}
} else {
quote! {
#(#attrs)*
pub fn #name #generic_params(#(#params),*) #return_type #where_clause {
#(#body_stmts)*
}
}
};
Ok(func_tokens)
}
}
fn extract_nested_indices_tokens(
expr: &HirExpr,
ctx: &mut CodeGenContext,
) -> Result<(syn::Expr, Vec<syn::Expr>)> {
let mut indices = Vec::new();
let mut current = expr;
loop {
match current {
HirExpr::Index { base, index } => {
let index_expr = index.to_rust_expr(ctx)?;
indices.push(index_expr);
current = base;
}
_ => {
let base_expr = current.to_rust_expr(ctx)?;
indices.reverse(); return Ok((base_expr, indices));
}
}
}
}
fn needs_type_conversion(target_type: &Type) -> bool {
matches!(target_type, Type::Int)
}
fn apply_type_conversion(value_expr: syn::Expr, target_type: &Type) -> syn::Expr {
match target_type {
Type::Int => {
parse_quote! { #value_expr as i32 }
}
_ => value_expr,
}
}
#[inline]
fn codegen_pass_stmt() -> Result<proc_macro2::TokenStream> {
Ok(quote! {})
}
#[inline]
fn codegen_break_stmt(label: &Option<String>) -> Result<proc_macro2::TokenStream> {
if let Some(label_name) = label {
let label_ident = syn::Lifetime::new(
&format!("'{}", label_name),
proc_macro2::Span::call_site(),
);
Ok(quote! { break #label_ident; })
} else {
Ok(quote! { break; })
}
}
#[inline]
fn codegen_continue_stmt(label: &Option<String>) -> Result<proc_macro2::TokenStream> {
if let Some(label_name) = label {
let label_ident = syn::Lifetime::new(
&format!("'{}", label_name),
proc_macro2::Span::call_site(),
);
Ok(quote! { continue #label_ident; })
} else {
Ok(quote! { continue; })
}
}
#[inline]
fn codegen_expr_stmt(expr: &HirExpr, ctx: &mut CodeGenContext) -> Result<proc_macro2::TokenStream> {
let expr_tokens = expr.to_rust_expr(ctx)?;
Ok(quote! { #expr_tokens; })
}
#[inline]
fn codegen_return_stmt(
expr: &Option<HirExpr>,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
if let Some(e) = expr {
let expr_tokens = e.to_rust_expr(ctx)?;
let is_optional_return =
matches!(ctx.current_return_type.as_ref(), Some(Type::Optional(_)));
let is_none_literal = matches!(e, HirExpr::Literal(Literal::None));
if ctx.current_function_can_fail {
if is_optional_return && !is_none_literal {
Ok(quote! { return Ok(Some(#expr_tokens)); })
} else {
Ok(quote! { return Ok(#expr_tokens); })
}
} else if is_optional_return && !is_none_literal {
Ok(quote! { return Some(#expr_tokens); })
} else {
Ok(quote! { return #expr_tokens; })
}
} else if ctx.current_function_can_fail {
let is_optional_return =
matches!(ctx.current_return_type.as_ref(), Some(Type::Optional(_)));
if is_optional_return {
Ok(quote! { return Ok(None); })
} else {
Ok(quote! { return Ok(()); })
}
} else {
Ok(quote! { return; })
}
}
#[inline]
fn codegen_while_stmt(
condition: &HirExpr,
body: &[HirStmt],
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let cond = condition.to_rust_expr(ctx)?;
ctx.enter_scope();
let body_stmts: Vec<_> = body
.iter()
.map(|s| s.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
ctx.exit_scope();
Ok(quote! {
while #cond {
#(#body_stmts)*
}
})
}
#[inline]
fn codegen_raise_stmt(
exception: &Option<HirExpr>,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
if let Some(exc) = exception {
let exc_expr = exc.to_rust_expr(ctx)?;
Ok(quote! { return Err(#exc_expr); })
} else {
Ok(quote! { return Err("Exception raised".into()); })
}
}
#[inline]
fn codegen_with_stmt(
context: &HirExpr,
target: &Option<String>,
body: &[HirStmt],
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let context_expr = context.to_rust_expr(ctx)?;
let body_stmts: Vec<_> = body
.iter()
.map(|stmt| stmt.to_rust_tokens(ctx))
.collect::<Result<_>>()?;
if let Some(var_name) = target {
let var_ident = syn::Ident::new(var_name, proc_macro2::Span::call_site());
ctx.declare_var(var_name);
Ok(quote! {
{
let mut #var_ident = #context_expr;
#(#body_stmts)*
}
})
} else {
Ok(quote! {
{
let _context = #context_expr;
#(#body_stmts)*
}
})
}
}
#[inline]
fn codegen_if_stmt(
condition: &HirExpr,
then_body: &[HirStmt],
else_body: &Option<Vec<HirStmt>>,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let cond = condition.to_rust_expr(ctx)?;
ctx.enter_scope();
let then_stmts: Vec<_> = then_body
.iter()
.map(|s| s.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
ctx.exit_scope();
if let Some(else_stmts) = else_body {
ctx.enter_scope();
let else_tokens: Vec<_> = else_stmts
.iter()
.map(|s| s.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
ctx.exit_scope();
Ok(quote! {
if #cond {
#(#then_stmts)*
} else {
#(#else_tokens)*
}
})
} else {
Ok(quote! {
if #cond {
#(#then_stmts)*
}
})
}
}
#[inline]
fn codegen_for_stmt(
target: &str,
iter: &HirExpr,
body: &[HirStmt],
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let target_ident = syn::Ident::new(target, proc_macro2::Span::call_site());
let mut iter_expr = iter.to_rust_expr(ctx)?;
if let HirExpr::Var(_var_name) = iter {
iter_expr = parse_quote! { #iter_expr.iter() };
}
ctx.enter_scope();
ctx.declare_var(target); let body_stmts: Vec<_> = body
.iter()
.map(|s| s.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
ctx.exit_scope();
Ok(quote! {
for #target_ident in #iter_expr {
#(#body_stmts)*
}
})
}
#[inline]
fn codegen_assign_stmt(
target: &AssignTarget,
value: &HirExpr,
type_annotation: &Option<Type>,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let mut value_expr = value.to_rust_expr(ctx)?;
let type_annotation_tokens = if let Some(target_type) = type_annotation {
let target_rust_type = ctx.type_mapper.map_type(target_type);
let target_syn_type = rust_type_to_syn(&target_rust_type)?;
if needs_type_conversion(target_type) {
value_expr = apply_type_conversion(value_expr, target_type);
}
Some(quote! { : #target_syn_type })
} else {
None
};
match target {
AssignTarget::Symbol(symbol) => {
codegen_assign_symbol(symbol, value_expr, type_annotation_tokens, ctx)
}
AssignTarget::Index { base, index } => {
codegen_assign_index(base, index, value_expr, ctx)
}
AssignTarget::Attribute { value, attr } => {
codegen_assign_attribute(value, attr, value_expr, ctx)
}
AssignTarget::Tuple(targets) => {
codegen_assign_tuple(targets, value_expr, type_annotation_tokens, ctx)
}
}
}
#[inline]
fn codegen_assign_symbol(
symbol: &str,
value_expr: syn::Expr,
type_annotation_tokens: Option<proc_macro2::TokenStream>,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let target_ident = syn::Ident::new(symbol, proc_macro2::Span::call_site());
if ctx.in_generator && ctx.generator_state_vars.contains(symbol) {
Ok(quote! { self.#target_ident = #value_expr; })
} else if ctx.is_declared(symbol) {
Ok(quote! { #target_ident = #value_expr; })
} else {
ctx.declare_var(symbol);
if ctx.mutable_vars.contains(symbol) {
if let Some(type_ann) = type_annotation_tokens {
Ok(quote! { let mut #target_ident #type_ann = #value_expr; })
} else {
Ok(quote! { let mut #target_ident = #value_expr; })
}
} else if let Some(type_ann) = type_annotation_tokens {
Ok(quote! { let #target_ident #type_ann = #value_expr; })
} else {
Ok(quote! { let #target_ident = #value_expr; })
}
}
}
#[inline]
fn codegen_assign_index(
base: &HirExpr,
index: &HirExpr,
value_expr: syn::Expr,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let final_index = index.to_rust_expr(ctx)?;
let (base_expr, indices) = extract_nested_indices_tokens(base, ctx)?;
if indices.is_empty() {
Ok(quote! { #base_expr.insert(#final_index, #value_expr); })
} else {
let mut chain = quote! { #base_expr };
for idx in &indices {
chain = quote! {
#chain.get_mut(&#idx).unwrap()
};
}
Ok(quote! { #chain.insert(#final_index, #value_expr); })
}
}
#[inline]
fn codegen_assign_attribute(
base: &HirExpr,
attr: &str,
value_expr: syn::Expr,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let base_expr = base.to_rust_expr(ctx)?;
let attr_ident = syn::Ident::new(attr, proc_macro2::Span::call_site());
Ok(quote! { #base_expr.#attr_ident = #value_expr; })
}
#[inline]
fn codegen_assign_tuple(
targets: &[AssignTarget],
value_expr: syn::Expr,
_type_annotation_tokens: Option<proc_macro2::TokenStream>,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let all_symbols: Option<Vec<&str>> = targets
.iter()
.map(|t| match t {
AssignTarget::Symbol(s) => Some(s.as_str()),
_ => None,
})
.collect();
match all_symbols {
Some(symbols) => {
let all_declared = symbols.iter().all(|s| ctx.is_declared(s));
if all_declared {
let idents: Vec<_> = symbols
.iter()
.map(|s| syn::Ident::new(s, proc_macro2::Span::call_site()))
.collect();
Ok(quote! { (#(#idents),*) = #value_expr; })
} else {
symbols.iter().for_each(|s| ctx.declare_var(s));
let idents_with_mut: Vec<_> = symbols
.iter()
.map(|s| {
let ident = syn::Ident::new(s, proc_macro2::Span::call_site());
if ctx.mutable_vars.contains(*s) {
quote! { mut #ident }
} else {
quote! { #ident }
}
})
.collect();
Ok(quote! { let (#(#idents_with_mut),*) = #value_expr; })
}
}
None => {
bail!("Complex tuple unpacking not yet supported")
}
}
}
#[inline]
fn codegen_try_stmt(
body: &[HirStmt],
handlers: &[ExceptHandler],
finalbody: &Option<Vec<HirStmt>>,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
ctx.enter_scope();
let try_stmts: Vec<_> = body
.iter()
.map(|s| s.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
ctx.exit_scope();
let mut handler_tokens = Vec::new();
for handler in handlers {
ctx.enter_scope();
if let Some(var_name) = &handler.name {
ctx.declare_var(var_name);
}
let handler_stmts: Vec<_> = handler
.body
.iter()
.map(|s| s.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
ctx.exit_scope();
handler_tokens.push(quote! { #(#handler_stmts)* });
}
let finally_stmts = if let Some(finally_body) = finalbody {
let stmts: Vec<_> = finally_body
.iter()
.map(|s| s.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
Some(quote! { #(#stmts)* })
} else {
None
};
if handlers.is_empty() {
if let Some(finally_code) = finally_stmts {
Ok(quote! {
{
#(#try_stmts)*
#finally_code
}
})
} else {
Ok(quote! { #(#try_stmts)* })
}
} else if handlers.len() == 1 {
let handler_code = &handler_tokens[0];
if let Some(finally_code) = finally_stmts {
Ok(quote! {
{
let _result = (|| -> Result<(), Box<dyn std::error::Error>> {
#(#try_stmts)*
Ok(())
})();
if let Err(_e) = _result {
#handler_code
}
#finally_code
}
})
} else {
Ok(quote! {
{
let _result = (|| -> Result<(), Box<dyn std::error::Error>> {
#(#try_stmts)*
Ok(())
})();
if let Err(_e) = _result {
#handler_code
}
}
})
}
} else {
if let Some(finally_code) = finally_stmts {
Ok(quote! {
{
let _result = (|| -> Result<(), Box<dyn std::error::Error>> {
#(#try_stmts)*
Ok(())
})();
if let Err(_e) = _result {
#(#handler_tokens)*
}
#finally_code
}
})
} else {
Ok(quote! {
{
let _result = (|| -> Result<(), Box<dyn std::error::Error>> {
#(#try_stmts)*
Ok(())
})();
if let Err(_e) = _result {
#(#handler_tokens)*
}
}
})
}
}
}
impl RustCodeGen for HirStmt {
fn to_rust_tokens(&self, ctx: &mut CodeGenContext) -> Result<proc_macro2::TokenStream> {
match self {
HirStmt::Assign {
target,
value,
type_annotation,
} => codegen_assign_stmt(target, value, type_annotation, ctx),
HirStmt::Return(expr) => codegen_return_stmt(expr, ctx),
HirStmt::If {
condition,
then_body,
else_body,
} => codegen_if_stmt(condition, then_body, else_body, ctx),
HirStmt::While { condition, body } => codegen_while_stmt(condition, body, ctx),
HirStmt::For { target, iter, body } => codegen_for_stmt(target, iter, body, ctx),
HirStmt::Expr(expr) => codegen_expr_stmt(expr, ctx),
HirStmt::Raise {
exception,
cause: _,
} => codegen_raise_stmt(exception, ctx),
HirStmt::Break { label } => codegen_break_stmt(label),
HirStmt::Continue { label } => codegen_continue_stmt(label),
HirStmt::With {
context,
target,
body,
} => codegen_with_stmt(context, target, body, ctx),
HirStmt::Try {
body,
handlers,
orelse: _,
finalbody,
} => codegen_try_stmt(body, handlers, finalbody, ctx),
HirStmt::Pass => codegen_pass_stmt(),
}
}
}
trait ToRustExpr {
fn to_rust_expr(&self, ctx: &mut CodeGenContext) -> Result<syn::Expr>;
}
struct ExpressionConverter<'a, 'b> {
ctx: &'a mut CodeGenContext<'b>,
}
impl<'a, 'b> ExpressionConverter<'a, 'b> {
fn new(ctx: &'a mut CodeGenContext<'b>) -> Self {
Self { ctx }
}
fn convert_variable(&self, name: &str) -> Result<syn::Expr> {
if self.ctx.in_generator && self.ctx.generator_state_vars.contains(name) {
let ident = syn::Ident::new(name, proc_macro2::Span::call_site());
Ok(parse_quote! { self.#ident })
} else {
let ident = syn::Ident::new(name, proc_macro2::Span::call_site());
Ok(parse_quote! { #ident })
}
}
fn convert_binary(&mut self, op: BinOp, left: &HirExpr, right: &HirExpr) -> Result<syn::Expr> {
let left_expr = left.to_rust_expr(self.ctx)?;
let right_expr = right.to_rust_expr(self.ctx)?;
match op {
BinOp::In => {
if matches!(left, HirExpr::Literal(Literal::String(_))) {
Ok(parse_quote! { #right_expr.contains_key(#left_expr) })
} else {
Ok(parse_quote! { #right_expr.contains_key(&#left_expr) })
}
}
BinOp::NotIn => {
if matches!(left, HirExpr::Literal(Literal::String(_))) {
Ok(parse_quote! { !#right_expr.contains_key(#left_expr) })
} else {
Ok(parse_quote! { !#right_expr.contains_key(&#left_expr) })
}
}
BinOp::Add => {
let is_definitely_string = matches!(left, HirExpr::Literal(Literal::String(_)))
|| matches!(right, HirExpr::Literal(Literal::String(_)));
if is_definitely_string {
Ok(parse_quote! { format!("{}{}", #left_expr, #right_expr) })
} else {
let rust_op = convert_binop(op)?;
Ok(parse_quote! { #left_expr #rust_op #right_expr })
}
}
BinOp::FloorDiv => {
Ok(parse_quote! {
{
let a = #left_expr;
let b = #right_expr;
let q = a / b;
let r = a % b;
let r_negative = r < 0;
let b_negative = b < 0;
let r_nonzero = r != 0;
let signs_differ = r_negative != b_negative;
let needs_adjustment = r_nonzero && signs_differ;
if needs_adjustment { q - 1 } else { q }
}
})
}
BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor
if self.is_set_expr(left) && self.is_set_expr(right) =>
{
self.convert_set_operation(op, left_expr, right_expr)
}
BinOp::Sub if self.is_set_expr(left) && self.is_set_expr(right) => {
self.convert_set_operation(op, left_expr, right_expr)
}
BinOp::Sub => {
if self.is_len_call(left) {
Ok(parse_quote! { #left_expr.saturating_sub(#right_expr) })
} else {
let rust_op = convert_binop(op)?;
Ok(parse_quote! { #left_expr #rust_op #right_expr })
}
}
BinOp::Mul => {
match (left, right) {
(HirExpr::List(elts), HirExpr::Literal(Literal::Int(size)))
if elts.len() == 1 && *size > 0 && *size <= 32 =>
{
let elem = elts[0].to_rust_expr(self.ctx)?;
let size_lit =
syn::LitInt::new(&size.to_string(), proc_macro2::Span::call_site());
Ok(parse_quote! { [#elem; #size_lit] })
}
(HirExpr::Literal(Literal::Int(size)), HirExpr::List(elts))
if elts.len() == 1 && *size > 0 && *size <= 32 =>
{
let elem = elts[0].to_rust_expr(self.ctx)?;
let size_lit =
syn::LitInt::new(&size.to_string(), proc_macro2::Span::call_site());
Ok(parse_quote! { [#elem; #size_lit] })
}
_ => {
let rust_op = convert_binop(op)?;
Ok(parse_quote! { #left_expr #rust_op #right_expr })
}
}
}
BinOp::Div => {
let needs_float_division = self.ctx.current_return_type.as_ref()
.map(return_type_expects_float)
.unwrap_or(false);
if needs_float_division {
Ok(parse_quote! { (#left_expr as f64) / (#right_expr as f64) })
} else {
let rust_op = convert_binop(op)?;
Ok(parse_quote! { #left_expr #rust_op #right_expr })
}
}
BinOp::Pow => {
match (left, right) {
(HirExpr::Literal(Literal::Int(_)), HirExpr::Literal(Literal::Int(exp))) => {
if *exp < 0 {
Ok(parse_quote! {
(#left_expr as f64).powf(#right_expr as f64)
})
} else {
Ok(parse_quote! {
#left_expr.checked_pow(#right_expr as u32)
.expect("Power operation overflowed")
})
}
}
(HirExpr::Literal(Literal::Float(_)), _) => Ok(parse_quote! {
#left_expr.powf(#right_expr as f64)
}),
(_, HirExpr::Literal(Literal::Float(_))) => Ok(parse_quote! {
(#left_expr as f64).powf(#right_expr)
}),
_ => {
Ok(parse_quote! {
{
if #right_expr >= 0 && #right_expr <= u32::MAX as i64 {
#left_expr.checked_pow(#right_expr as u32)
.expect("Power operation overflowed")
} else {
(#left_expr as f64).powf(#right_expr as f64) as i64
}
}
})
}
}
}
_ => {
let rust_op = convert_binop(op)?;
Ok(parse_quote! { #left_expr #rust_op #right_expr })
}
}
}
fn convert_unary(&mut self, op: &UnaryOp, operand: &HirExpr) -> Result<syn::Expr> {
let operand_expr = operand.to_rust_expr(self.ctx)?;
match op {
UnaryOp::Not => Ok(parse_quote! { !#operand_expr }),
UnaryOp::Neg => Ok(parse_quote! { -#operand_expr }),
UnaryOp::Pos => Ok(operand_expr), UnaryOp::BitNot => Ok(parse_quote! { !#operand_expr }),
}
}
fn convert_call(&mut self, func: &str, args: &[HirExpr]) -> Result<syn::Expr> {
if func == "cls" && self.ctx.is_classmethod {
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| arg.to_rust_expr(self.ctx))
.collect::<Result<Vec<_>>>()?;
return Ok(parse_quote! { Self::new(#(#arg_exprs),*) });
}
if func == "map" && args.len() >= 2 {
if let Some(result) = self.try_convert_map_with_zip(args)? {
return Ok(result);
}
}
if func == "sum" && args.len() == 1
&& matches!(args[0], HirExpr::GeneratorExp { .. }) {
let gen_expr = args[0].to_rust_expr(self.ctx)?;
return Ok(parse_quote! { #gen_expr.sum() });
}
if func == "max" && args.len() == 1
&& matches!(args[0], HirExpr::GeneratorExp { .. }) {
let gen_expr = args[0].to_rust_expr(self.ctx)?;
return Ok(parse_quote! { #gen_expr.max() });
}
if func == "enumerate" && args.len() == 1 {
let items_expr = args[0].to_rust_expr(self.ctx)?;
return Ok(parse_quote! { #items_expr.into_iter().enumerate() });
}
if func == "zip" && args.len() >= 2 {
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| arg.to_rust_expr(self.ctx))
.collect::<Result<Vec<_>>>()?;
let first = &arg_exprs[0];
let mut chain: syn::Expr = parse_quote! { #first.iter() };
for arg in &arg_exprs[1..] {
chain = parse_quote! { #chain.zip(#arg.iter()) };
}
return Ok(chain);
}
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| arg.to_rust_expr(self.ctx))
.collect::<Result<Vec<_>>>()?;
match func {
"int" => self.convert_int_cast(&arg_exprs),
"float" => self.convert_float_cast(&arg_exprs),
"str" => self.convert_str_conversion(&arg_exprs),
"bool" => self.convert_bool_cast(&arg_exprs),
"len" => self.convert_len_call(&arg_exprs),
"range" => self.convert_range_call(&arg_exprs),
"zeros" | "ones" | "full" => self.convert_array_init_call(func, args, &arg_exprs),
"set" => self.convert_set_constructor(&arg_exprs),
"frozenset" => self.convert_frozenset_constructor(&arg_exprs),
_ => self.convert_generic_call(func, &arg_exprs),
}
}
fn try_convert_map_with_zip(&mut self, args: &[HirExpr]) -> Result<Option<syn::Expr>> {
if let HirExpr::Lambda { params, body } = &args[0] {
let num_iterables = args.len() - 1;
if params.len() != num_iterables {
bail!(
"Lambda has {} parameters but map() called with {} iterables",
params.len(),
num_iterables
);
}
let mut iterable_exprs: Vec<syn::Expr> = Vec::new();
for iterable in &args[1..] {
iterable_exprs.push(iterable.to_rust_expr(self.ctx)?);
}
let param_idents: Vec<syn::Ident> = params
.iter()
.map(|p| syn::Ident::new(p, proc_macro2::Span::call_site()))
.collect();
let body_expr = body.to_rust_expr(self.ctx)?;
if num_iterables == 1 {
let iter_expr = &iterable_exprs[0];
let param = ¶m_idents[0];
Ok(Some(parse_quote! {
#iter_expr.iter().map(|#param| #body_expr).collect::<Vec<_>>()
}))
} else {
let first_iter = &iterable_exprs[0];
let mut zip_expr: syn::Expr = parse_quote! { #first_iter.iter() };
for iter_expr in &iterable_exprs[1..] {
zip_expr = parse_quote! { #zip_expr.zip(#iter_expr.iter()) };
}
let tuple_pat: syn::Pat = if param_idents.len() == 2 {
let p0 = ¶m_idents[0];
let p1 = ¶m_idents[1];
parse_quote! { (#p0, #p1) }
} else if param_idents.len() == 3 {
let p0 = ¶m_idents[0];
let p1 = ¶m_idents[1];
let p2 = ¶m_idents[2];
parse_quote! { ((#p0, #p1), #p2) }
} else {
bail!("map() with more than 3 iterables is not yet supported");
};
Ok(Some(parse_quote! {
#zip_expr.map(|#tuple_pat| #body_expr).collect::<Vec<_>>()
}))
}
} else {
Ok(None)
}
}
fn convert_len_call(&self, args: &[syn::Expr]) -> Result<syn::Expr> {
if args.len() != 1 {
bail!("len() requires exactly one argument");
}
let arg = &args[0];
Ok(parse_quote! { #arg.len() as i32 })
}
fn convert_int_cast(&self, args: &[syn::Expr]) -> Result<syn::Expr> {
if args.is_empty() || args.len() > 2 {
bail!("int() requires 1-2 arguments");
}
let arg = &args[0];
Ok(arg.clone())
}
fn convert_float_cast(&self, args: &[syn::Expr]) -> Result<syn::Expr> {
if args.len() != 1 {
bail!("float() requires exactly one argument");
}
let arg = &args[0];
Ok(parse_quote! { (#arg) as f64 })
}
fn convert_str_conversion(&self, args: &[syn::Expr]) -> Result<syn::Expr> {
if args.len() != 1 {
bail!("str() requires exactly one argument");
}
let arg = &args[0];
Ok(parse_quote! { #arg.to_string() })
}
fn convert_bool_cast(&self, args: &[syn::Expr]) -> Result<syn::Expr> {
if args.len() != 1 {
bail!("bool() requires exactly one argument");
}
let arg = &args[0];
Ok(parse_quote! { (#arg) as bool })
}
fn convert_range_call(&self, args: &[syn::Expr]) -> Result<syn::Expr> {
match args.len() {
1 => {
let end = &args[0];
Ok(parse_quote! { 0..#end })
}
2 => {
let start = &args[0];
let end = &args[1];
Ok(parse_quote! { #start..#end })
}
3 => {
let start = &args[0];
let end = &args[1];
let step = &args[2];
let is_negative_step = if let syn::Expr::Unary(unary) = step {
matches!(unary.op, syn::UnOp::Neg(_))
} else {
false
};
if is_negative_step {
Ok(parse_quote! {
{
let step = (#step).abs() as usize;
if step == 0 {
panic!("range() arg 3 must not be zero");
}
if step == 1 {
(#end..#start).rev()
} else {
(#end..#start).rev().step_by(step)
}
}
})
} else {
Ok(parse_quote! {
{
let step = #step as usize;
if step == 0 {
panic!("range() arg 3 must not be zero");
}
(#start..#end).step_by(step)
}
})
}
}
_ => bail!("Invalid number of arguments for range()"),
}
}
fn convert_array_init_call(
&mut self,
func: &str,
args: &[HirExpr],
_arg_exprs: &[syn::Expr],
) -> Result<syn::Expr> {
if args.is_empty() {
bail!("{} requires at least one argument", func);
}
if let HirExpr::Literal(Literal::Int(size)) = &args[0] {
if *size > 0 && *size <= 32 {
let size_lit = syn::LitInt::new(&size.to_string(), proc_macro2::Span::call_site());
match func {
"zeros" => Ok(parse_quote! { [0; #size_lit] }),
"ones" => Ok(parse_quote! { [1; #size_lit] }),
"full" => {
if args.len() >= 2 {
let value = args[1].to_rust_expr(self.ctx)?;
Ok(parse_quote! { [#value; #size_lit] })
} else {
bail!("full() requires a value argument");
}
}
_ => unreachable!(),
}
} else {
match func {
"zeros" => {
let size_expr = args[0].to_rust_expr(self.ctx)?;
Ok(parse_quote! { vec![0; #size_expr as usize] })
}
"ones" => {
let size_expr = args[0].to_rust_expr(self.ctx)?;
Ok(parse_quote! { vec![1; #size_expr as usize] })
}
"full" => {
if args.len() >= 2 {
let size_expr = args[0].to_rust_expr(self.ctx)?;
let value = args[1].to_rust_expr(self.ctx)?;
Ok(parse_quote! { vec![#value; #size_expr as usize] })
} else {
bail!("full() requires a value argument");
}
}
_ => unreachable!(),
}
}
} else {
let size_expr = args[0].to_rust_expr(self.ctx)?;
match func {
"zeros" => Ok(parse_quote! { vec![0; #size_expr as usize] }),
"ones" => Ok(parse_quote! { vec![1; #size_expr as usize] }),
"full" => {
if args.len() >= 2 {
let value = args[1].to_rust_expr(self.ctx)?;
Ok(parse_quote! { vec![#value; #size_expr as usize] })
} else {
bail!("full() requires a value argument");
}
}
_ => unreachable!(),
}
}
}
fn convert_set_constructor(&mut self, args: &[syn::Expr]) -> Result<syn::Expr> {
self.ctx.needs_hashset = true;
if args.is_empty() {
Ok(parse_quote! { HashSet::new() })
} else if args.len() == 1 {
let arg = &args[0];
Ok(parse_quote! {
#arg.into_iter().collect::<HashSet<_>>()
})
} else {
bail!("set() takes at most 1 argument ({} given)", args.len())
}
}
fn convert_frozenset_constructor(&mut self, args: &[syn::Expr]) -> Result<syn::Expr> {
self.ctx.needs_hashset = true;
if args.is_empty() {
Ok(parse_quote! { std::sync::Arc::new(HashSet::new()) })
} else if args.len() == 1 {
let arg = &args[0];
Ok(parse_quote! {
std::sync::Arc::new(#arg.into_iter().collect::<HashSet<_>>())
})
} else {
bail!(
"frozenset() takes at most 1 argument ({} given)",
args.len()
)
}
}
fn convert_generic_call(&self, func: &str, args: &[syn::Expr]) -> Result<syn::Expr> {
if let Some(rust_path) = self.ctx.imported_items.get(func) {
let path_parts: Vec<&str> = rust_path.split("::").collect();
let mut path = quote! {};
for (i, part) in path_parts.iter().enumerate() {
let part_ident = syn::Ident::new(part, proc_macro2::Span::call_site());
if i == 0 {
path = quote! { #part_ident };
} else {
path = quote! { #path::#part_ident };
}
}
if args.is_empty() {
return Ok(parse_quote! { #path() });
} else {
return Ok(parse_quote! { #path(#(#args),*) });
}
}
if func
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
{
let class_ident = syn::Ident::new(func, proc_macro2::Span::call_site());
if args.is_empty() {
match func {
"Counter" => Ok(parse_quote! { #class_ident::new(0) }),
_ => Ok(parse_quote! { #class_ident::new() }),
}
} else {
Ok(parse_quote! { #class_ident::new(#(#args),*) })
}
} else {
let func_ident = syn::Ident::new(func, proc_macro2::Span::call_site());
Ok(parse_quote! { #func_ident(#(#args),*) })
}
}
#[inline]
fn try_convert_classmethod(
&mut self,
object: &HirExpr,
method: &str,
args: &[HirExpr],
) -> Result<Option<syn::Expr>> {
if let HirExpr::Var(var_name) = object {
if var_name == "cls" && self.ctx.is_classmethod {
let method_ident = syn::Ident::new(method, proc_macro2::Span::call_site());
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| arg.to_rust_expr(self.ctx))
.collect::<Result<Vec<_>>>()?;
return Ok(Some(parse_quote! { Self::#method_ident(#(#arg_exprs),*) }));
}
}
Ok(None)
}
#[inline]
fn try_convert_module_method(
&mut self,
object: &HirExpr,
method: &str,
args: &[HirExpr],
) -> Result<Option<syn::Expr>> {
if let HirExpr::Var(module_name) = object {
let rust_name_opt = self
.ctx
.imported_modules
.get(module_name)
.and_then(|mapping| mapping.item_map.get(method).cloned());
if let Some(rust_name) = rust_name_opt {
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| arg.to_rust_expr(self.ctx))
.collect::<Result<Vec<_>>>()?;
let path_parts: Vec<&str> = rust_name.split("::").collect();
let mut path = quote! { std };
for part in path_parts {
let part_ident = syn::Ident::new(part, proc_macro2::Span::call_site());
path = quote! { #path::#part_ident };
}
let result = match rust_name.as_str() {
"env::current_dir" => {
parse_quote! {
#path().unwrap().to_string_lossy().to_string()
}
}
"Regex::new" => {
if arg_exprs.is_empty() {
bail!("re.compile() requires a pattern argument");
}
let pattern = &arg_exprs[0];
parse_quote! {
regex::Regex::new(#pattern).unwrap()
}
}
_ => {
if arg_exprs.is_empty() {
parse_quote! { #path() }
} else {
parse_quote! { #path(#(#arg_exprs),*) }
}
}
};
return Ok(Some(result));
}
}
Ok(None)
}
#[inline]
fn convert_list_method(
&mut self,
object_expr: &syn::Expr,
object: &HirExpr,
method: &str,
arg_exprs: &[syn::Expr],
) -> Result<syn::Expr> {
match method {
"append" => {
if arg_exprs.len() != 1 {
bail!("append() requires exactly one argument");
}
let arg = &arg_exprs[0];
Ok(parse_quote! { #object_expr.push(#arg) })
}
"extend" => {
if arg_exprs.len() != 1 {
bail!("extend() requires exactly one argument");
}
let arg = &arg_exprs[0];
Ok(parse_quote! { #object_expr.extend(#arg) })
}
"pop" => {
if self.is_set_expr(object) {
if !arg_exprs.is_empty() {
bail!("pop() takes no arguments for sets");
}
Ok(parse_quote! {
#object_expr.iter().next().cloned().map(|x| {
#object_expr.remove(&x);
x
}).expect("pop from empty set")
})
} else if arg_exprs.is_empty() {
Ok(parse_quote! { #object_expr.pop().unwrap_or_default() })
} else {
bail!("pop() with index not supported in V1");
}
}
"insert" => {
if arg_exprs.len() != 2 {
bail!("insert() requires exactly two arguments");
}
let index = &arg_exprs[0];
let value = &arg_exprs[1];
Ok(parse_quote! { #object_expr.insert(#index as usize, #value) })
}
"remove" => {
if arg_exprs.len() != 1 {
bail!("remove() requires exactly one argument");
}
let value = &arg_exprs[0];
if self.is_set_expr(object) {
Ok(parse_quote! {
if !#object_expr.remove(&#value) {
panic!("KeyError: element not in set");
}
})
} else {
Ok(parse_quote! {
if let Some(pos) = #object_expr.iter().position(|x| x == &#value) {
#object_expr.remove(pos)
} else {
panic!("ValueError: list.remove(x): x not in list")
}
})
}
}
_ => bail!("Unknown list method: {}", method),
}
}
#[inline]
fn convert_dict_method(
&mut self,
object_expr: &syn::Expr,
method: &str,
arg_exprs: &[syn::Expr],
) -> Result<syn::Expr> {
match method {
"get" => {
if arg_exprs.len() == 1 {
let key = &arg_exprs[0];
Ok(parse_quote! { #object_expr.get(&#key).cloned() })
} else if arg_exprs.len() == 2 {
let key = &arg_exprs[0];
let default = &arg_exprs[1];
Ok(parse_quote! { #object_expr.get(&#key).cloned().unwrap_or(#default) })
} else {
bail!("get() requires 1 or 2 arguments");
}
}
"keys" => {
if !arg_exprs.is_empty() {
bail!("keys() takes no arguments");
}
Ok(parse_quote! { #object_expr.keys().cloned().collect::<Vec<_>>() })
}
"values" => {
if !arg_exprs.is_empty() {
bail!("values() takes no arguments");
}
Ok(parse_quote! { #object_expr.values().cloned().collect::<Vec<_>>() })
}
"items" => {
if !arg_exprs.is_empty() {
bail!("items() takes no arguments");
}
Ok(parse_quote! { #object_expr.iter().map(|(k, v)| (k.clone(), v.clone())).collect::<Vec<_>>() })
}
"update" => {
if arg_exprs.len() != 1 {
bail!("update() requires exactly one argument");
}
let arg = &arg_exprs[0];
Ok(parse_quote! {
for (k, v) in #arg {
#object_expr.insert(k, v);
}
})
}
_ => bail!("Unknown dict method: {}", method),
}
}
#[inline]
fn convert_string_method(
&mut self,
object_expr: &syn::Expr,
method: &str,
arg_exprs: &[syn::Expr],
) -> Result<syn::Expr> {
match method {
"upper" => {
if !arg_exprs.is_empty() {
bail!("upper() takes no arguments");
}
Ok(parse_quote! { #object_expr.to_uppercase() })
}
"lower" => {
if !arg_exprs.is_empty() {
bail!("lower() takes no arguments");
}
Ok(parse_quote! { #object_expr.to_lowercase() })
}
"strip" => {
if !arg_exprs.is_empty() {
bail!("strip() with arguments not supported in V1");
}
Ok(parse_quote! { #object_expr.trim().to_string() })
}
"startswith" => {
if arg_exprs.len() != 1 {
bail!("startswith() requires exactly one argument");
}
let prefix = &arg_exprs[0];
Ok(parse_quote! { #object_expr.starts_with(#prefix) })
}
"endswith" => {
if arg_exprs.len() != 1 {
bail!("endswith() requires exactly one argument");
}
let suffix = &arg_exprs[0];
Ok(parse_quote! { #object_expr.ends_with(#suffix) })
}
"split" => {
if arg_exprs.is_empty() {
Ok(parse_quote! { #object_expr.split_whitespace().map(|s| s.to_string()).collect::<Vec<String>>() })
} else if arg_exprs.len() == 1 {
let sep = &arg_exprs[0];
Ok(parse_quote! { #object_expr.split(#sep).map(|s| s.to_string()).collect::<Vec<String>>() })
} else {
bail!("split() with maxsplit not supported in V1");
}
}
"join" => {
if arg_exprs.len() != 1 {
bail!("join() requires exactly one argument");
}
let iterable = &arg_exprs[0];
Ok(parse_quote! { #iterable.join(#object_expr) })
}
_ => bail!("Unknown string method: {}", method),
}
}
#[inline]
fn convert_set_method(
&mut self,
object_expr: &syn::Expr,
method: &str,
arg_exprs: &[syn::Expr],
) -> Result<syn::Expr> {
match method {
"add" => {
if arg_exprs.len() != 1 {
bail!("add() requires exactly one argument");
}
let arg = &arg_exprs[0];
Ok(parse_quote! { #object_expr.insert(#arg) })
}
"discard" => {
if arg_exprs.len() != 1 {
bail!("discard() requires exactly one argument");
}
let arg = &arg_exprs[0];
Ok(parse_quote! { #object_expr.remove(&#arg) })
}
"clear" => {
if !arg_exprs.is_empty() {
bail!("clear() takes no arguments");
}
Ok(parse_quote! { #object_expr.clear() })
}
_ => bail!("Unknown set method: {}", method),
}
}
#[inline]
fn convert_regex_method(
&mut self,
object_expr: &syn::Expr,
method: &str,
arg_exprs: &[syn::Expr],
) -> Result<syn::Expr> {
match method {
"findall" => {
if arg_exprs.is_empty() {
bail!("findall() requires at least one argument");
}
let text = &arg_exprs[0];
Ok(parse_quote! {
#object_expr.find_iter(#text)
.map(|m| m.as_str().to_string())
.collect::<Vec<String>>()
})
}
_ => bail!("Unknown regex method: {}", method),
}
}
#[inline]
fn convert_instance_method(
&mut self,
object: &HirExpr,
object_expr: &syn::Expr,
method: &str,
arg_exprs: &[syn::Expr],
) -> Result<syn::Expr> {
match method {
"append" | "extend" | "pop" | "insert" | "remove" => {
self.convert_list_method(object_expr, object, method, arg_exprs)
}
"get" | "keys" | "values" | "items" | "update" => {
self.convert_dict_method(object_expr, method, arg_exprs)
}
"upper" | "lower" | "strip" | "startswith" | "endswith" | "split" | "join" => {
self.convert_string_method(object_expr, method, arg_exprs)
}
"add" | "discard" | "clear" => {
self.convert_set_method(object_expr, method, arg_exprs)
}
"findall" => {
self.convert_regex_method(object_expr, method, arg_exprs)
}
_ => {
let method_ident = syn::Ident::new(method, proc_macro2::Span::call_site());
Ok(parse_quote! { #object_expr.#method_ident(#(#arg_exprs),*) })
}
}
}
fn convert_method_call(
&mut self,
object: &HirExpr,
method: &str,
args: &[HirExpr],
) -> Result<syn::Expr> {
if let Some(result) = self.try_convert_classmethod(object, method, args)? {
return Ok(result);
}
if let Some(result) = self.try_convert_module_method(object, method, args)? {
return Ok(result);
}
let object_expr = object.to_rust_expr(self.ctx)?;
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| arg.to_rust_expr(self.ctx))
.collect::<Result<Vec<_>>>()?;
self.convert_instance_method(object, &object_expr, method, &arg_exprs)
}
fn convert_index(&mut self, base: &HirExpr, index: &HirExpr) -> Result<syn::Expr> {
let base_expr = base.to_rust_expr(self.ctx)?;
let is_string_key = self.is_string_index(base, index)?;
if is_string_key {
match index {
HirExpr::Literal(Literal::String(s)) => {
Ok(parse_quote! {
#base_expr.get(#s).cloned().unwrap_or_default()
})
}
_ => {
let index_expr = index.to_rust_expr(self.ctx)?;
Ok(parse_quote! {
#base_expr.get(#index_expr).cloned().unwrap_or_default()
})
}
}
} else {
let index_expr = index.to_rust_expr(self.ctx)?;
Ok(parse_quote! {
#base_expr.get(#index_expr as usize).copied().unwrap_or_default()
})
}
}
fn is_string_index(&self, base: &HirExpr, index: &HirExpr) -> Result<bool> {
if matches!(index, HirExpr::Literal(Literal::String(_))) {
return Ok(true);
}
if let HirExpr::Var(sym) = base {
let name = sym.as_str();
if (name.contains("dict") || name.contains("data") || name.contains("map"))
&& !self.is_numeric_index(index)
{
return Ok(true);
}
}
if self.is_string_variable(index) {
return Ok(true);
}
Ok(false)
}
fn is_string_variable(&self, expr: &HirExpr) -> bool {
match expr {
HirExpr::Var(sym) => {
let name = sym.as_str();
name == "key"
|| name == "name"
|| name == "id"
|| name == "word"
|| name == "text"
|| name.ends_with("_key")
|| name.ends_with("_name")
}
_ => false,
}
}
fn is_numeric_index(&self, expr: &HirExpr) -> bool {
match expr {
HirExpr::Literal(Literal::Int(_)) => true,
HirExpr::Var(sym) => {
let name = sym.as_str();
name == "i"
|| name == "j"
|| name == "k"
|| name == "idx"
|| name == "index"
|| name.starts_with("idx_")
|| name.ends_with("_idx")
|| name.ends_with("_index")
}
HirExpr::Binary { .. } => true, HirExpr::Call { .. } => false, _ => false,
}
}
fn convert_slice(
&mut self,
base: &HirExpr,
start: &Option<Box<HirExpr>>,
stop: &Option<Box<HirExpr>>,
step: &Option<Box<HirExpr>>,
) -> Result<syn::Expr> {
let base_expr = base.to_rust_expr(self.ctx)?;
let start_expr = if let Some(s) = start {
Some(s.to_rust_expr(self.ctx)?)
} else {
None
};
let stop_expr = if let Some(s) = stop {
Some(s.to_rust_expr(self.ctx)?)
} else {
None
};
let step_expr = if let Some(s) = step {
Some(s.to_rust_expr(self.ctx)?)
} else {
None
};
match (start_expr, stop_expr, step_expr) {
(None, None, Some(step)) => {
Ok(parse_quote! {
{
let step = #step;
if step == 1 {
#base_expr.clone()
} else if step > 0 {
#base_expr.iter().step_by(step as usize).cloned().collect::<Vec<_>>()
} else if step == -1 {
#base_expr.iter().rev().cloned().collect::<Vec<_>>()
} else {
let abs_step = (-step) as usize;
#base_expr.iter().rev().step_by(abs_step).cloned().collect::<Vec<_>>()
}
}
})
}
(Some(start), Some(stop), None) => Ok(parse_quote! {
{
let start = (#start).max(0) as usize;
let stop = (#stop).max(0) as usize;
if start < #base_expr.len() {
#base_expr[start..stop.min(#base_expr.len())].to_vec()
} else {
Vec::new()
}
}
}),
(Some(start), None, None) => Ok(parse_quote! {
{
let start = (#start).max(0) as usize;
if start < #base_expr.len() {
#base_expr[start..].to_vec()
} else {
Vec::new()
}
}
}),
(None, Some(stop), None) => Ok(parse_quote! {
{
let stop = (#stop).max(0) as usize;
#base_expr[..stop.min(#base_expr.len())].to_vec()
}
}),
(None, None, None) => Ok(parse_quote! { #base_expr.clone() }),
(Some(start), Some(stop), Some(step)) => {
Ok(parse_quote! {
{
let start = (#start).max(0) as usize;
let stop = (#stop).max(0) as usize;
let step = #step;
if step == 1 {
if start < #base_expr.len() {
#base_expr[start..stop.min(#base_expr.len())].to_vec()
} else {
Vec::new()
}
} else if step > 0 {
#base_expr[start..stop.min(#base_expr.len())]
.iter()
.step_by(step as usize)
.cloned()
.collect::<Vec<_>>()
} else {
let abs_step = (-step) as usize;
if start < #base_expr.len() {
#base_expr[start..stop.min(#base_expr.len())]
.iter()
.rev()
.step_by(abs_step)
.cloned()
.collect::<Vec<_>>()
} else {
Vec::new()
}
}
}
})
}
(Some(start), None, Some(step)) => Ok(parse_quote! {
{
let start = (#start).max(0) as usize;
let step = #step;
if start < #base_expr.len() {
if step == 1 {
#base_expr[start..].to_vec()
} else if step > 0 {
#base_expr[start..]
.iter()
.step_by(step as usize)
.cloned()
.collect::<Vec<_>>()
} else if step == -1 {
#base_expr[start..]
.iter()
.rev()
.cloned()
.collect::<Vec<_>>()
} else {
let abs_step = (-step) as usize;
#base_expr[start..]
.iter()
.rev()
.step_by(abs_step)
.cloned()
.collect::<Vec<_>>()
}
} else {
Vec::new()
}
}
}),
(None, Some(stop), Some(step)) => Ok(parse_quote! {
{
let stop = (#stop).max(0) as usize;
let step = #step;
if step == 1 {
#base_expr[..stop.min(#base_expr.len())].to_vec()
} else if step > 0 {
#base_expr[..stop.min(#base_expr.len())]
.iter()
.step_by(step as usize)
.cloned()
.collect::<Vec<_>>()
} else if step == -1 {
#base_expr[..stop.min(#base_expr.len())]
.iter()
.rev()
.cloned()
.collect::<Vec<_>>()
} else {
let abs_step = (-step) as usize;
#base_expr[..stop.min(#base_expr.len())]
.iter()
.rev()
.step_by(abs_step)
.cloned()
.collect::<Vec<_>>()
}
}
}),
}
}
fn convert_list(&mut self, elts: &[HirExpr]) -> Result<syn::Expr> {
let elt_exprs: Vec<syn::Expr> = elts
.iter()
.map(|e| e.to_rust_expr(self.ctx))
.collect::<Result<Vec<_>>>()?;
Ok(parse_quote! { vec![#(#elt_exprs),*] })
}
fn convert_dict(&mut self, items: &[(HirExpr, HirExpr)]) -> Result<syn::Expr> {
self.ctx.needs_hashmap = true;
let mut insert_stmts = Vec::new();
for (key, value) in items {
let key_expr = key.to_rust_expr(self.ctx)?;
let val_expr = value.to_rust_expr(self.ctx)?;
insert_stmts.push(quote! { map.insert(#key_expr, #val_expr); });
}
Ok(parse_quote! {
{
let mut map = HashMap::new();
#(#insert_stmts)*
map
}
})
}
fn convert_tuple(&mut self, elts: &[HirExpr]) -> Result<syn::Expr> {
let elt_exprs: Vec<syn::Expr> = elts
.iter()
.map(|e| e.to_rust_expr(self.ctx))
.collect::<Result<Vec<_>>>()?;
Ok(parse_quote! { (#(#elt_exprs),*) })
}
fn convert_set(&mut self, elts: &[HirExpr]) -> Result<syn::Expr> {
self.ctx.needs_hashset = true;
let mut insert_stmts = Vec::new();
for elem in elts {
let elem_expr = elem.to_rust_expr(self.ctx)?;
insert_stmts.push(quote! { set.insert(#elem_expr); });
}
Ok(parse_quote! {
{
let mut set = HashSet::new();
#(#insert_stmts)*
set
}
})
}
fn convert_frozenset(&mut self, elts: &[HirExpr]) -> Result<syn::Expr> {
self.ctx.needs_hashset = true;
self.ctx.needs_arc = true;
let mut insert_stmts = Vec::new();
for elem in elts {
let elem_expr = elem.to_rust_expr(self.ctx)?;
insert_stmts.push(quote! { set.insert(#elem_expr); });
}
Ok(parse_quote! {
{
let mut set = HashSet::new();
#(#insert_stmts)*
std::sync::Arc::new(set)
}
})
}
fn convert_attribute(&mut self, value: &HirExpr, attr: &str) -> Result<syn::Expr> {
if let HirExpr::Var(var_name) = value {
if var_name == "cls" && self.ctx.is_classmethod {
let attr_ident = syn::Ident::new(attr, proc_macro2::Span::call_site());
return Ok(parse_quote! { Self::#attr_ident });
}
}
if let HirExpr::Var(module_name) = value {
let rust_name_opt = self
.ctx
.imported_modules
.get(module_name)
.and_then(|mapping| mapping.item_map.get(attr).cloned());
if let Some(rust_name) = rust_name_opt {
let path_parts: Vec<&str> = rust_name.split("::").collect();
if path_parts.len() > 1 {
let mut path = quote! { std };
for part in path_parts {
let part_ident = syn::Ident::new(part, proc_macro2::Span::call_site());
path = quote! { #path::#part_ident };
}
return Ok(parse_quote! { #path });
} else {
let ident = syn::Ident::new(&rust_name, proc_macro2::Span::call_site());
return Ok(parse_quote! { #ident });
}
}
}
let value_expr = value.to_rust_expr(self.ctx)?;
let attr_ident = syn::Ident::new(attr, proc_macro2::Span::call_site());
Ok(parse_quote! { #value_expr.#attr_ident })
}
fn convert_borrow(&mut self, expr: &HirExpr, mutable: bool) -> Result<syn::Expr> {
let expr_tokens = expr.to_rust_expr(self.ctx)?;
if mutable {
Ok(parse_quote! { &mut #expr_tokens })
} else {
Ok(parse_quote! { &#expr_tokens })
}
}
fn convert_list_comp(
&mut self,
element: &HirExpr,
target: &str,
iter: &HirExpr,
condition: &Option<Box<HirExpr>>,
) -> Result<syn::Expr> {
let target_ident = syn::Ident::new(target, proc_macro2::Span::call_site());
let iter_expr = iter.to_rust_expr(self.ctx)?;
let element_expr = element.to_rust_expr(self.ctx)?;
if let Some(cond) = condition {
let cond_expr = cond.to_rust_expr(self.ctx)?;
Ok(parse_quote! {
#iter_expr
.into_iter()
.filter(|#target_ident| #cond_expr)
.map(|#target_ident| #element_expr)
.collect::<Vec<_>>()
})
} else {
Ok(parse_quote! {
#iter_expr
.into_iter()
.map(|#target_ident| #element_expr)
.collect::<Vec<_>>()
})
}
}
fn is_set_expr(&self, expr: &HirExpr) -> bool {
match expr {
HirExpr::Set(_) | HirExpr::FrozenSet(_) => true,
HirExpr::Call { func, .. } if func == "set" || func == "frozenset" => true,
HirExpr::Var(_name) => {
false
}
_ => false,
}
}
fn convert_set_operation(
&self,
op: BinOp,
left: syn::Expr,
right: syn::Expr,
) -> Result<syn::Expr> {
match op {
BinOp::BitAnd => Ok(parse_quote! {
#left.intersection(&#right).cloned().collect()
}),
BinOp::BitOr => Ok(parse_quote! {
#left.union(&#right).cloned().collect()
}),
BinOp::Sub => Ok(parse_quote! {
#left.difference(&#right).cloned().collect()
}),
BinOp::BitXor => Ok(parse_quote! {
#left.symmetric_difference(&#right).cloned().collect()
}),
_ => bail!("Invalid set operator"),
}
}
fn convert_set_comp(
&mut self,
element: &HirExpr,
target: &str,
iter: &HirExpr,
condition: &Option<Box<HirExpr>>,
) -> Result<syn::Expr> {
self.ctx.needs_hashset = true;
let target_ident = syn::Ident::new(target, proc_macro2::Span::call_site());
let iter_expr = iter.to_rust_expr(self.ctx)?;
let element_expr = element.to_rust_expr(self.ctx)?;
if let Some(cond) = condition {
let cond_expr = cond.to_rust_expr(self.ctx)?;
Ok(parse_quote! {
#iter_expr
.into_iter()
.filter(|#target_ident| #cond_expr)
.map(|#target_ident| #element_expr)
.collect::<HashSet<_>>()
})
} else {
Ok(parse_quote! {
#iter_expr
.into_iter()
.map(|#target_ident| #element_expr)
.collect::<HashSet<_>>()
})
}
}
fn convert_lambda(&mut self, params: &[String], body: &HirExpr) -> Result<syn::Expr> {
let param_pats: Vec<syn::Pat> = params
.iter()
.map(|p| {
let ident = syn::Ident::new(p, proc_macro2::Span::call_site());
parse_quote! { #ident }
})
.collect();
let body_expr = body.to_rust_expr(self.ctx)?;
if params.is_empty() {
Ok(parse_quote! { || #body_expr })
} else if params.len() == 1 {
let param = ¶m_pats[0];
Ok(parse_quote! { |#param| #body_expr })
} else {
Ok(parse_quote! { |#(#param_pats),*| #body_expr })
}
}
fn is_len_call(&self, expr: &HirExpr) -> bool {
matches!(expr, HirExpr::Call { func, args } if func == "len" && args.len() == 1)
}
fn convert_await(&mut self, value: &HirExpr) -> Result<syn::Expr> {
let value_expr = value.to_rust_expr(self.ctx)?;
Ok(parse_quote! { #value_expr.await })
}
fn convert_yield(&mut self, value: &Option<Box<HirExpr>>) -> Result<syn::Expr> {
if self.ctx.in_generator {
if let Some(v) = value {
let value_expr = v.to_rust_expr(self.ctx)?;
Ok(parse_quote! { return Some(#value_expr) })
} else {
Ok(parse_quote! { return None })
}
} else {
if let Some(v) = value {
let value_expr = v.to_rust_expr(self.ctx)?;
Ok(parse_quote! { yield #value_expr })
} else {
Ok(parse_quote! { yield })
}
}
}
fn convert_fstring(&mut self, parts: &[FStringPart]) -> Result<syn::Expr> {
if parts.is_empty() {
return Ok(parse_quote! { "".to_string() });
}
let has_expressions = parts.iter().any(|p| matches!(p, FStringPart::Expr(_)));
if !has_expressions {
let mut result = String::new();
for part in parts {
if let FStringPart::Literal(s) = part {
result.push_str(s);
}
}
return Ok(parse_quote! { #result.to_string() });
}
let mut template = String::new();
let mut args = Vec::new();
for part in parts {
match part {
FStringPart::Literal(s) => {
template.push_str(s);
}
FStringPart::Expr(expr) => {
template.push_str("{}");
let arg_expr = expr.to_rust_expr(self.ctx)?;
args.push(arg_expr);
}
}
}
if args.is_empty() {
Ok(parse_quote! { #template.to_string() })
} else {
Ok(parse_quote! { format!(#template, #(#args),*) })
}
}
fn convert_ifexpr(&mut self, test: &HirExpr, body: &HirExpr, orelse: &HirExpr) -> Result<syn::Expr> {
let test_expr = test.to_rust_expr(self.ctx)?;
let body_expr = body.to_rust_expr(self.ctx)?;
let orelse_expr = orelse.to_rust_expr(self.ctx)?;
Ok(parse_quote! {
if #test_expr { #body_expr } else { #orelse_expr }
})
}
fn convert_sort_by_key(
&mut self,
iterable: &HirExpr,
key_params: &[String],
key_body: &HirExpr,
reverse: bool,
) -> Result<syn::Expr> {
let iter_expr = iterable.to_rust_expr(self.ctx)?;
let body_expr = key_body.to_rust_expr(self.ctx)?;
let param_pat: syn::Pat = if key_params.len() == 1 {
let param = syn::Ident::new(&key_params[0], proc_macro2::Span::call_site());
parse_quote! { #param }
} else {
bail!("sorted() key lambda must have exactly one parameter");
};
if reverse {
Ok(parse_quote! {
{
let mut __sorted_result = #iter_expr.clone();
__sorted_result.sort_by_key(|#param_pat| #body_expr);
__sorted_result.reverse();
__sorted_result
}
})
} else {
Ok(parse_quote! {
{
let mut __sorted_result = #iter_expr.clone();
__sorted_result.sort_by_key(|#param_pat| #body_expr);
__sorted_result
}
})
}
}
fn convert_generator_expression(
&mut self,
element: &HirExpr,
generators: &[crate::hir::HirComprehension],
) -> Result<syn::Expr> {
if generators.is_empty() {
bail!("Generator expression must have at least one generator");
}
if generators.len() == 1 {
let gen = &generators[0];
let iter_expr = gen.iter.to_rust_expr(self.ctx)?;
let element_expr = element.to_rust_expr(self.ctx)?;
let target_pat = self.parse_target_pattern(&gen.target)?;
let mut chain: syn::Expr = parse_quote! { #iter_expr.into_iter() };
for cond in &gen.conditions {
let cond_expr = cond.to_rust_expr(self.ctx)?;
chain = parse_quote! { #chain.filter(|#target_pat| #cond_expr) };
}
chain = parse_quote! { #chain.map(|#target_pat| #element_expr) };
return Ok(chain);
}
self.convert_nested_generators(element, generators)
}
fn convert_nested_generators(
&mut self,
element: &HirExpr,
generators: &[crate::hir::HirComprehension],
) -> Result<syn::Expr> {
let first_gen = &generators[0];
let first_iter = first_gen.iter.to_rust_expr(self.ctx)?;
let first_pat = self.parse_target_pattern(&first_gen.target)?;
let inner_expr = self.build_nested_chain(element, generators, 1)?;
let mut chain: syn::Expr = parse_quote! { #first_iter.into_iter() };
for cond in &first_gen.conditions {
let cond_expr = cond.to_rust_expr(self.ctx)?;
chain = parse_quote! { #chain.filter(|#first_pat| #cond_expr) };
}
chain = parse_quote! { #chain.flat_map(|#first_pat| #inner_expr) };
Ok(chain)
}
fn build_nested_chain(
&mut self,
element: &HirExpr,
generators: &[crate::hir::HirComprehension],
depth: usize,
) -> Result<syn::Expr> {
if depth >= generators.len() {
let element_expr = element.to_rust_expr(self.ctx)?;
return Ok(element_expr);
}
let gen = &generators[depth];
let iter_expr = gen.iter.to_rust_expr(self.ctx)?;
let target_pat = self.parse_target_pattern(&gen.target)?;
let inner_expr = self.build_nested_chain(element, generators, depth + 1)?;
let mut chain: syn::Expr = parse_quote! { #iter_expr.into_iter() };
for cond in &gen.conditions {
let cond_expr = cond.to_rust_expr(self.ctx)?;
chain = parse_quote! { #chain.filter(|#target_pat| #cond_expr) };
}
if depth < generators.len() - 1 {
chain = parse_quote! { #chain.flat_map(move |#target_pat| #inner_expr) };
} else {
chain = parse_quote! { #chain.map(move |#target_pat| #inner_expr) };
}
Ok(chain)
}
fn parse_target_pattern(&self, target: &str) -> Result<syn::Pat> {
if target.starts_with('(') && target.ends_with(')') {
let inner = &target[1..target.len() - 1];
let parts: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
let idents: Vec<syn::Ident> = parts
.iter()
.map(|s| syn::Ident::new(s, proc_macro2::Span::call_site()))
.collect();
Ok(parse_quote! { ( #(#idents),* ) })
} else {
let ident = syn::Ident::new(target, proc_macro2::Span::call_site());
Ok(parse_quote! { #ident })
}
}
}
impl ToRustExpr for HirExpr {
fn to_rust_expr(&self, ctx: &mut CodeGenContext) -> Result<syn::Expr> {
let mut converter = ExpressionConverter::new(ctx);
match self {
HirExpr::Literal(lit) => {
let expr = literal_to_rust_expr(lit, &ctx.string_optimizer, &ctx.needs_cow, ctx);
if let Literal::String(s) = lit {
let context = StringContext::Literal(s.clone());
if matches!(
ctx.string_optimizer.get_optimal_type(&context),
crate::string_optimization::OptimalStringType::CowStr
) {
ctx.needs_cow = true;
}
}
Ok(expr)
}
HirExpr::Var(name) => converter.convert_variable(name),
HirExpr::Binary { op, left, right } => converter.convert_binary(*op, left, right),
HirExpr::Unary { op, operand } => converter.convert_unary(op, operand),
HirExpr::Call { func, args } => converter.convert_call(func, args),
HirExpr::MethodCall {
object,
method,
args,
} => converter.convert_method_call(object, method, args),
HirExpr::Index { base, index } => converter.convert_index(base, index),
HirExpr::Slice {
base,
start,
stop,
step,
} => converter.convert_slice(base, start, stop, step),
HirExpr::List(elts) => converter.convert_list(elts),
HirExpr::Dict(items) => converter.convert_dict(items),
HirExpr::Tuple(elts) => converter.convert_tuple(elts),
HirExpr::Set(elts) => converter.convert_set(elts),
HirExpr::FrozenSet(elts) => converter.convert_frozenset(elts),
HirExpr::Attribute { value, attr } => converter.convert_attribute(value, attr),
HirExpr::Borrow { expr, mutable } => converter.convert_borrow(expr, *mutable),
HirExpr::ListComp {
element,
target,
iter,
condition,
} => converter.convert_list_comp(element, target, iter, condition),
HirExpr::Lambda { params, body } => converter.convert_lambda(params, body),
HirExpr::SetComp {
element,
target,
iter,
condition,
} => converter.convert_set_comp(element, target, iter, condition),
HirExpr::Await { value } => converter.convert_await(value),
HirExpr::Yield { value } => converter.convert_yield(value),
HirExpr::FString { parts } => converter.convert_fstring(parts),
HirExpr::IfExpr { test, body, orelse } => converter.convert_ifexpr(test, body, orelse),
HirExpr::SortByKey {
iterable,
key_params,
key_body,
reverse,
} => converter.convert_sort_by_key(iterable, key_params, key_body, *reverse),
HirExpr::GeneratorExp { element, generators } => {
converter.convert_generator_expression(element, generators)
}
}
}
}
fn literal_to_rust_expr(
lit: &Literal,
string_optimizer: &StringOptimizer,
_needs_cow: &bool,
ctx: &CodeGenContext,
) -> syn::Expr {
match lit {
Literal::Int(n) => {
let lit = syn::LitInt::new(&n.to_string(), proc_macro2::Span::call_site());
parse_quote! { #lit }
}
Literal::Float(f) => {
let s = f.to_string();
let float_str = if s.contains('.') || s.contains('e') || s.contains('E') {
s
} else {
format!("{}.0", s)
};
let lit = syn::LitFloat::new(&float_str, proc_macro2::Span::call_site());
parse_quote! { #lit }
}
Literal::String(s) => {
if let Some(interned_name) = string_optimizer.get_interned_name(s) {
let ident = syn::Ident::new(&interned_name, proc_macro2::Span::call_site());
parse_quote! { #ident }
} else {
let lit = syn::LitStr::new(s, proc_macro2::Span::call_site());
let context = StringContext::Literal(s.clone());
match string_optimizer.get_optimal_type(&context) {
crate::string_optimization::OptimalStringType::StaticStr => {
parse_quote! { #lit }
}
crate::string_optimization::OptimalStringType::BorrowedStr { .. } => {
parse_quote! { #lit }
}
crate::string_optimization::OptimalStringType::CowStr => {
if let Some(Type::String) = &ctx.current_return_type {
parse_quote! { #lit.to_string() }
} else {
parse_quote! { std::borrow::Cow::Borrowed(#lit) }
}
}
crate::string_optimization::OptimalStringType::OwnedString => {
parse_quote! { #lit.to_string() }
}
}
}
}
Literal::Bool(b) => {
let lit = syn::LitBool::new(*b, proc_macro2::Span::call_site());
parse_quote! { #lit }
}
Literal::None => parse_quote! { None },
}
}
fn convert_binop(op: BinOp) -> Result<syn::BinOp> {
use BinOp::*;
match op {
Add => Ok(parse_quote! { + }),
Sub => Ok(parse_quote! { - }),
Mul => Ok(parse_quote! { * }),
Div => Ok(parse_quote! { / }),
Mod => Ok(parse_quote! { % }),
FloorDiv => {
bail!("Floor division handled by convert_binary with Python semantics")
}
Pow => bail!("Power operator handled by convert_binary with type-specific logic"),
Eq => Ok(parse_quote! { == }),
NotEq => Ok(parse_quote! { != }),
Lt => Ok(parse_quote! { < }),
LtEq => Ok(parse_quote! { <= }),
Gt => Ok(parse_quote! { > }),
GtEq => Ok(parse_quote! { >= }),
And => Ok(parse_quote! { && }),
Or => Ok(parse_quote! { || }),
BitAnd => Ok(parse_quote! { & }),
BitOr => Ok(parse_quote! { | }),
BitXor => Ok(parse_quote! { ^ }),
LShift => Ok(parse_quote! { << }),
RShift => Ok(parse_quote! { >> }),
In | NotIn => bail!("in/not in operators should be handled by convert_binary"),
}
}
fn str_type_to_syn(lifetime: &Option<String>) -> syn::Type {
if let Some(lt) = lifetime {
let lt_ident = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
parse_quote! { &#lt_ident str }
} else {
parse_quote! { &str }
}
}
fn reference_type_to_syn(
lifetime: &Option<String>,
mutable: bool,
inner: &crate::type_mapper::RustType,
) -> Result<syn::Type> {
let inner_ty = rust_type_to_syn(inner)?;
Ok(if mutable {
if let Some(lt) = lifetime {
let lt_ident = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
parse_quote! { &#lt_ident mut #inner_ty }
} else {
parse_quote! { &mut #inner_ty }
}
} else if let Some(lt) = lifetime {
let lt_ident = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
parse_quote! { &#lt_ident #inner_ty }
} else {
parse_quote! { &#inner_ty }
})
}
fn array_type_to_syn(
element_type: &crate::type_mapper::RustType,
size: &crate::type_mapper::RustConstGeneric,
) -> Result<syn::Type> {
let element = rust_type_to_syn(element_type)?;
Ok(match size {
crate::type_mapper::RustConstGeneric::Literal(n) => {
let size_lit = syn::LitInt::new(&n.to_string(), proc_macro2::Span::call_site());
parse_quote! { [#element; #size_lit] }
}
crate::type_mapper::RustConstGeneric::Parameter(name) => {
let param_ident = syn::Ident::new(name, proc_macro2::Span::call_site());
parse_quote! { [#element; #param_ident] }
}
crate::type_mapper::RustConstGeneric::Expression(expr) => {
let expr_tokens: proc_macro2::TokenStream = expr.parse().unwrap_or_else(|_| {
quote! { }
});
parse_quote! { [#element; #expr_tokens] }
}
})
}
pub fn rust_type_to_syn(rust_type: &crate::type_mapper::RustType) -> Result<syn::Type> {
use crate::type_mapper::RustType;
Ok(match rust_type {
RustType::Primitive(p) => {
let ident = syn::Ident::new(p.to_rust_string(), proc_macro2::Span::call_site());
parse_quote! { #ident }
}
RustType::String => parse_quote! { String },
RustType::Str { lifetime } => str_type_to_syn(lifetime),
RustType::Cow { lifetime } => {
let lt_ident = syn::Lifetime::new(lifetime, proc_macro2::Span::call_site());
parse_quote! { Cow<#lt_ident, str> }
}
RustType::Vec(inner) => {
let inner_ty = rust_type_to_syn(inner)?;
parse_quote! { Vec<#inner_ty> }
}
RustType::HashMap(k, v) => {
let key_ty = rust_type_to_syn(k)?;
let val_ty = rust_type_to_syn(v)?;
parse_quote! { HashMap<#key_ty, #val_ty> }
}
RustType::Option(inner) => {
let inner_ty = rust_type_to_syn(inner)?;
parse_quote! { Option<#inner_ty> }
}
RustType::Result(ok, err) => {
let ok_ty = rust_type_to_syn(ok)?;
let err_ty = rust_type_to_syn(err)?;
parse_quote! { Result<#ok_ty, #err_ty> }
}
RustType::Reference {
lifetime,
mutable,
inner,
} => reference_type_to_syn(lifetime, *mutable, inner)?,
RustType::Tuple(types) => {
let tys: Vec<_> = types
.iter()
.map(rust_type_to_syn)
.collect::<Result<Vec<_>>>()?;
parse_quote! { (#(#tys),*) }
}
RustType::Unit => parse_quote! { () },
RustType::Custom(name) => {
let ty: syn::Type = syn::parse_str(name)?;
ty
}
RustType::Unsupported(reason) => bail!("Unsupported Rust type: {}", reason),
RustType::TypeParam(name) => {
let ident = syn::Ident::new(name, proc_macro2::Span::call_site());
parse_quote! { #ident }
}
RustType::Generic { base, params } => {
let base_ident = syn::Ident::new(base, proc_macro2::Span::call_site());
let param_types: Vec<_> = params
.iter()
.map(rust_type_to_syn)
.collect::<Result<Vec<_>>>()?;
parse_quote! { #base_ident<#(#param_types),*> }
}
RustType::Enum { name, .. } => {
let ident = syn::Ident::new(name, proc_macro2::Span::call_site());
parse_quote! { #ident }
}
RustType::Array { element_type, size } => array_type_to_syn(element_type, size)?,
RustType::HashSet(inner) => {
let inner_ty = rust_type_to_syn(inner)?;
parse_quote! { HashSet<#inner_ty> }
}
})
}
fn format_rust_code(code: String) -> String {
code.replace(" ; ", ";\n ")
.replace(" { ", " {\n ")
.replace(" } ", "\n}\n")
.replace("} ;", "};")
.replace(
"use std :: collections :: HashMap ;",
"use std::collections::HashMap;",
)
.replace(" . ", ".")
.replace(" (", "(")
.replace(" )", ")")
.replace(".len ()", ".len()")
.replace(".push (", ".push(")
.replace(".insert (", ".insert(")
.replace(".get (", ".get(")
.replace(".contains_key (", ".contains_key(")
.replace(".to_string ()", ".to_string()")
.replace(" ::", "::")
.replace(":: ", "::")
.replace("# [", "#[")
.replace(" : ", ": ")
.replace(" , ", ", ")
.replace("=(", " = (")
.replace("= (", " = (")
.replace(" =", " =") .replace(" =", " =") .replace("Vec < ", "Vec<")
.replace(" < ", "<")
.replace(" > ", ">")
.replace("> ", ">")
.replace("< ", "<")
.replace(" >", ">") .replace("->", " -> ")
.replace(" -> ", " -> ")
.replace(" -> ", " -> ")
.replace(" .. ", "..")
.replace(" ..", "..")
.replace(".. ", "..")
.replace("in(", "in (")
.replace(";\n }", "\n}")
}
fn update_import_needs(ctx: &mut CodeGenContext, rust_type: &crate::type_mapper::RustType) {
match rust_type {
crate::type_mapper::RustType::HashMap(_, _) => ctx.needs_hashmap = true,
crate::type_mapper::RustType::Cow { .. } => ctx.needs_cow = true,
crate::type_mapper::RustType::Custom(name) => {
if name.contains("FnvHashMap") {
ctx.needs_fnv_hashmap = true;
} else if name.contains("AHashMap") {
ctx.needs_ahash_hashmap = true;
} else if name.contains("Arc<") {
ctx.needs_arc = true;
} else if name.contains("Rc<") {
ctx.needs_rc = true;
} else if name.contains("HashMap<")
&& !name.contains("FnvHashMap")
&& !name.contains("AHashMap")
{
ctx.needs_hashmap = true;
}
}
crate::type_mapper::RustType::Reference { inner, .. } => {
update_import_needs(ctx, inner);
}
crate::type_mapper::RustType::Vec(inner) => {
update_import_needs(ctx, inner);
}
crate::type_mapper::RustType::Option(inner) => {
update_import_needs(ctx, inner);
}
crate::type_mapper::RustType::Result(ok, err) => {
update_import_needs(ctx, ok);
update_import_needs(ctx, err);
}
crate::type_mapper::RustType::Tuple(types) => {
for t in types {
update_import_needs(ctx, t);
}
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::type_mapper::TypeMapper;
use depyler_annotations::TranspilationAnnotations;
fn create_test_context() -> CodeGenContext<'static> {
let type_mapper: &'static TypeMapper = Box::leak(Box::new(TypeMapper::default()));
CodeGenContext {
type_mapper,
annotation_aware_mapper: AnnotationAwareTypeMapper::with_base_mapper(
type_mapper.clone(),
),
string_optimizer: StringOptimizer::new(),
union_enum_generator: crate::union_enum_gen::UnionEnumGenerator::new(),
generated_enums: Vec::new(),
needs_hashmap: false,
needs_hashset: false,
needs_fnv_hashmap: false,
needs_ahash_hashmap: false,
needs_arc: false,
needs_rc: false,
needs_cow: false,
declared_vars: vec![HashSet::new()],
current_function_can_fail: false,
current_return_type: None,
module_mapper: crate::module_mapper::ModuleMapper::new(),
imported_modules: std::collections::HashMap::new(),
imported_items: std::collections::HashMap::new(),
mutable_vars: HashSet::new(),
needs_zerodivisionerror: false,
needs_indexerror: false,
is_classmethod: false,
in_generator: false,
generator_state_vars: HashSet::new(),
}
}
#[test]
fn test_simple_function_generation() {
let func = HirFunction {
name: "add".to_string(),
params: vec![
HirParam::new("a".to_string(), Type::Int),
HirParam::new("b".to_string(), Type::Int),
]
.into(),
ret_type: Type::Int,
body: vec![HirStmt::Return(Some(HirExpr::Binary {
op: BinOp::Add,
left: Box::new(HirExpr::Var("a".to_string())),
right: Box::new(HirExpr::Var("b".to_string())),
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let mut ctx = create_test_context();
let tokens = func.to_rust_tokens(&mut ctx).unwrap();
let code = tokens.to_string();
assert!(code.contains("pub fn add"));
assert!(code.contains("i32"));
assert!(code.contains("return"));
}
#[test]
fn test_control_flow_generation() {
let if_stmt = HirStmt::If {
condition: HirExpr::Binary {
op: BinOp::Gt,
left: Box::new(HirExpr::Var("x".to_string())),
right: Box::new(HirExpr::Literal(Literal::Int(0))),
},
then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::String(
"positive".to_string(),
))))],
else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(
Literal::String("negative".to_string()),
)))]),
};
let mut ctx = create_test_context();
let tokens = if_stmt.to_rust_tokens(&mut ctx).unwrap();
let code = tokens.to_string();
assert!(code.contains("if"));
assert!(code.contains("else"));
assert!(code.contains("return"));
}
#[test]
fn test_list_generation() {
let list_expr = HirExpr::List(vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::Int(2)),
HirExpr::Literal(Literal::Int(3)),
]);
let mut ctx = create_test_context();
let expr = list_expr.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #expr }.to_string();
assert!(code.contains("[") && code.contains("]"));
assert!(code.contains("1"));
assert!(code.contains("2"));
assert!(code.contains("3"));
let var_list = HirExpr::List(vec![
HirExpr::Var("x".to_string()),
HirExpr::Var("y".to_string()),
]);
let expr2 = var_list.to_rust_expr(&mut ctx).unwrap();
let code2 = quote! { #expr2 }.to_string();
assert!(code2.contains("vec !"));
}
#[test]
fn test_dict_generation_sets_needs_hashmap() {
let dict_expr = HirExpr::Dict(vec![(
HirExpr::Literal(Literal::String("key".to_string())),
HirExpr::Literal(Literal::Int(42)),
)]);
let mut ctx = create_test_context();
assert!(!ctx.needs_hashmap);
let _ = dict_expr.to_rust_expr(&mut ctx).unwrap();
assert!(ctx.needs_hashmap);
}
#[test]
fn test_binary_operations() {
let ops = vec![
(BinOp::Add, "+"),
(BinOp::Sub, "-"),
(BinOp::Mul, "*"),
(BinOp::Eq, "=="),
(BinOp::Lt, "<"),
];
for (op, expected) in ops {
let result = convert_binop(op).unwrap();
assert_eq!(quote! { #result }.to_string(), expected);
}
}
#[test]
fn test_unsupported_operators() {
assert!(convert_binop(BinOp::Pow).is_err());
assert!(convert_binop(BinOp::In).is_err());
assert!(convert_binop(BinOp::NotIn).is_err());
}
#[test]
fn test_codegen_pass_stmt() {
let result = codegen_pass_stmt().unwrap();
assert!(result.is_empty(), "Pass statement should generate no code");
}
#[test]
fn test_codegen_break_stmt_simple() {
let result = codegen_break_stmt(&None).unwrap();
assert_eq!(result.to_string(), "break ;");
}
#[test]
fn test_codegen_break_stmt_with_label() {
let result = codegen_break_stmt(&Some("outer".to_string())).unwrap();
assert_eq!(result.to_string(), "break 'outer ;");
}
#[test]
fn test_codegen_continue_stmt_simple() {
let result = codegen_continue_stmt(&None).unwrap();
assert_eq!(result.to_string(), "continue ;");
}
#[test]
fn test_codegen_continue_stmt_with_label() {
let result = codegen_continue_stmt(&Some("outer".to_string())).unwrap();
assert_eq!(result.to_string(), "continue 'outer ;");
}
#[test]
fn test_codegen_expr_stmt() {
use crate::hir::Literal;
let mut ctx = create_test_context();
let expr = HirExpr::Literal(Literal::Int(42));
let result = codegen_expr_stmt(&expr, &mut ctx).unwrap();
assert_eq!(result.to_string(), "42 ;");
}
#[test]
fn test_codegen_return_stmt_simple() {
use crate::hir::Literal;
let mut ctx = create_test_context();
let expr = Some(HirExpr::Literal(Literal::Int(42)));
let result = codegen_return_stmt(&expr, &mut ctx).unwrap();
assert_eq!(result.to_string(), "return 42 ;");
}
#[test]
fn test_codegen_return_stmt_none() {
let mut ctx = create_test_context();
let result = codegen_return_stmt(&None, &mut ctx).unwrap();
assert_eq!(result.to_string(), "return ;");
}
#[test]
fn test_codegen_while_stmt() {
use crate::hir::Literal;
let mut ctx = create_test_context();
let condition = HirExpr::Literal(Literal::Bool(true));
let body = vec![HirStmt::Pass];
let result = codegen_while_stmt(&condition, &body, &mut ctx).unwrap();
assert!(result.to_string().contains("while true"));
}
#[test]
fn test_codegen_raise_stmt_with_exception() {
use crate::hir::Literal;
let mut ctx = create_test_context();
let exc = Some(HirExpr::Literal(Literal::String("Error".to_string())));
let result = codegen_raise_stmt(&exc, &mut ctx).unwrap();
assert_eq!(result.to_string(), "return Err (\"Error\" . to_string ()) ;");
}
#[test]
fn test_codegen_raise_stmt_bare() {
let mut ctx = create_test_context();
let result = codegen_raise_stmt(&None, &mut ctx).unwrap();
assert_eq!(result.to_string(), "return Err (\"Exception raised\" . into ()) ;");
}
#[test]
fn test_codegen_with_stmt_with_target() {
use crate::hir::Literal;
let mut ctx = create_test_context();
let context = HirExpr::Literal(Literal::Int(42));
let target = Some("file".to_string());
let body = vec![HirStmt::Pass];
let result = codegen_with_stmt(&context, &target, &body, &mut ctx).unwrap();
assert!(result.to_string().contains("let mut file"));
}
#[test]
fn test_codegen_with_stmt_no_target() {
use crate::hir::Literal;
let mut ctx = create_test_context();
let context = HirExpr::Literal(Literal::Int(42));
let body = vec![HirStmt::Pass];
let result = codegen_with_stmt(&context, &None, &body, &mut ctx).unwrap();
assert!(result.to_string().contains("let _context"));
}
#[test]
fn test_codegen_assign_symbol_new_var() {
let mut ctx = create_test_context();
let value_expr = syn::parse_quote! { 42 };
let result = codegen_assign_symbol("x", value_expr, None, &mut ctx).unwrap();
assert!(result.to_string().contains("let x = 42"));
}
#[test]
fn test_codegen_assign_symbol_with_type() {
let mut ctx = create_test_context();
let value_expr = syn::parse_quote! { 42 };
let type_ann = Some(quote! { : i32 });
let result = codegen_assign_symbol("x", value_expr, type_ann, &mut ctx).unwrap();
assert!(result.to_string().contains("let x : i32 = 42"));
}
#[test]
fn test_codegen_assign_symbol_existing_var() {
let mut ctx = create_test_context();
ctx.declare_var("x");
let value_expr = syn::parse_quote! { 100 };
let result = codegen_assign_symbol("x", value_expr, None, &mut ctx).unwrap();
assert_eq!(result.to_string(), "x = 100 ;");
}
#[test]
fn test_codegen_assign_index() {
use crate::hir::Literal;
let mut ctx = create_test_context();
let base = HirExpr::Var("dict".to_string());
let index = HirExpr::Literal(Literal::String("key".to_string()));
let value_expr = syn::parse_quote! { 42 };
let result = codegen_assign_index(&base, &index, value_expr, &mut ctx).unwrap();
assert!(result.to_string().contains("dict . insert"));
}
#[test]
fn test_codegen_assign_attribute() {
let mut ctx = create_test_context();
let base = HirExpr::Var("obj".to_string());
let value_expr = syn::parse_quote! { 42 };
let result = codegen_assign_attribute(&base, "field", value_expr, &mut ctx).unwrap();
assert_eq!(result.to_string(), "obj . field = 42 ;");
}
#[test]
fn test_codegen_assign_tuple_new_vars() {
use crate::hir::AssignTarget;
let mut ctx = create_test_context();
let targets = vec![
AssignTarget::Symbol("a".to_string()),
AssignTarget::Symbol("b".to_string()),
];
let value_expr = syn::parse_quote! { (1, 2) };
let result = codegen_assign_tuple(&targets, value_expr, None, &mut ctx).unwrap();
assert!(result.to_string().contains("let (a , b) = (1 , 2)"));
}
#[test]
fn test_codegen_try_stmt_simple() {
use crate::hir::ExceptHandler;
let mut ctx = create_test_context();
let body = vec![HirStmt::Pass];
let handlers = vec![ExceptHandler {
exception_type: None,
name: None,
body: vec![HirStmt::Pass],
}];
let result = codegen_try_stmt(&body, &handlers, &None, &mut ctx).unwrap();
let result_str = result.to_string();
assert!(result_str.contains("let _result"));
assert!(result_str.contains("if let Err (_e) = _result"));
}
#[test]
fn test_codegen_try_stmt_with_finally() {
let mut ctx = create_test_context();
let body = vec![HirStmt::Pass];
let handlers = vec![];
let finally = Some(vec![HirStmt::Pass]);
let result = codegen_try_stmt(&body, &handlers, &finally, &mut ctx).unwrap();
assert!(!result.to_string().is_empty());
}
#[test]
fn test_codegen_try_stmt_except_and_finally() {
use crate::hir::ExceptHandler;
let mut ctx = create_test_context();
let body = vec![HirStmt::Pass];
let handlers = vec![ExceptHandler {
exception_type: None,
name: Some("e".to_string()),
body: vec![HirStmt::Pass],
}];
let finally = Some(vec![HirStmt::Pass]);
let result = codegen_try_stmt(&body, &handlers, &finally, &mut ctx).unwrap();
let result_str = result.to_string();
assert!(result_str.contains("let _result"));
assert!(result_str.contains("if let Err (_e) = _result"));
}
#[test]
fn test_int_cast_conversion() {
let call_expr = HirExpr::Call {
func: "int".to_string(),
args: vec![HirExpr::Var("x".to_string())],
};
let mut ctx = create_test_context();
let result = call_expr.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #result }.to_string();
assert!(code.contains("x"), "Expected 'x', got: {}", code);
assert!(!code.contains("as"), "Should not contain cast, got: {}", code);
}
#[test]
fn test_float_cast_conversion() {
let call_expr = HirExpr::Call {
func: "float".to_string(),
args: vec![HirExpr::Var("y".to_string())],
};
let mut ctx = create_test_context();
let result = call_expr.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #result }.to_string();
assert!(code.contains("as f64"), "Expected '(y) as f64', got: {}", code);
}
#[test]
fn test_str_conversion() {
let call_expr = HirExpr::Call {
func: "str".to_string(),
args: vec![HirExpr::Var("value".to_string())],
};
let mut ctx = create_test_context();
let result = call_expr.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #result }.to_string();
assert!(code.contains("to_string"), "Expected 'value.to_string()', got: {}", code);
}
#[test]
fn test_bool_cast_conversion() {
let call_expr = HirExpr::Call {
func: "bool".to_string(),
args: vec![HirExpr::Var("flag".to_string())],
};
let mut ctx = create_test_context();
let result = call_expr.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #result }.to_string();
assert!(code.contains("as bool"), "Expected '(flag) as bool', got: {}", code);
}
#[test]
fn test_int_cast_with_expression() {
let division = HirExpr::Binary {
op: BinOp::Div,
left: Box::new(HirExpr::Binary {
op: BinOp::Add,
left: Box::new(HirExpr::Var("low".to_string())),
right: Box::new(HirExpr::Var("high".to_string())),
}),
right: Box::new(HirExpr::Literal(Literal::Int(2))),
};
let call_expr = HirExpr::Call {
func: "int".to_string(),
args: vec![division],
};
let mut ctx = create_test_context();
let result = call_expr.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #result }.to_string();
assert!(code.contains("low"), "Expected 'low' variable, got: {}", code);
assert!(code.contains("high"), "Expected 'high' variable, got: {}", code);
assert!(!code.contains("as"), "Should not contain cast, got: {}", code);
}
#[test]
fn test_float_literal_decimal_point() {
let mut ctx = create_test_context();
let zero_float = HirExpr::Literal(Literal::Float(0.0));
let result = zero_float.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #result }.to_string();
assert!(code.contains("0.0") || code.contains("0 ."),
"Expected '0.0' for float zero, got: {}", code);
let forty_two = HirExpr::Literal(Literal::Float(42.0));
let result = forty_two.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #result }.to_string();
assert!(code.contains("42.0") || code.contains("42 ."),
"Expected '42.0' for float, got: {}", code);
let one_half = HirExpr::Literal(Literal::Float(1.5));
let result = one_half.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #result }.to_string();
assert!(code.contains("1.5"), "Expected '1.5', got: {}", code);
let scientific = HirExpr::Literal(Literal::Float(1e10));
let result = scientific.to_rust_expr(&mut ctx).unwrap();
let code = quote! { #result }.to_string();
assert!(code.contains("e") || code.contains("E") || code.contains("."),
"Expected scientific notation or decimal, got: {}", code);
}
#[test]
fn test_string_method_return_types() {
let upper_func = HirFunction {
name: "to_upper".to_string(),
params: vec![HirParam::new("text".to_string(), Type::String)].into(),
ret_type: Type::String,
body: vec![HirStmt::Return(Some(HirExpr::MethodCall {
object: Box::new(HirExpr::Var("text".to_string())),
method: "upper".to_string(),
args: vec![],
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let mut ctx = create_test_context();
let result = upper_func.to_rust_tokens(&mut ctx).unwrap();
let code = result.to_string();
assert!(code.contains("-> String"),
"Expected '-> String' for .upper() method, got: {}", code);
assert!(!code.contains("-> & ") && !code.contains("-> &'"),
"Should not generate borrowed return for .upper(), got: {}", code);
let lower_func = HirFunction {
name: "to_lower".to_string(),
params: vec![HirParam::new("text".to_string(), Type::String)].into(),
ret_type: Type::String,
body: vec![HirStmt::Return(Some(HirExpr::MethodCall {
object: Box::new(HirExpr::Var("text".to_string())),
method: "lower".to_string(),
args: vec![],
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let mut ctx = create_test_context();
let result = lower_func.to_rust_tokens(&mut ctx).unwrap();
let code = result.to_string();
assert!(code.contains("-> String"),
"Expected '-> String' for .lower() method, got: {}", code);
let strip_func = HirFunction {
name: "trim_text".to_string(),
params: vec![HirParam::new("text".to_string(), Type::String)].into(),
ret_type: Type::String,
body: vec![HirStmt::Return(Some(HirExpr::MethodCall {
object: Box::new(HirExpr::Var("text".to_string())),
method: "strip".to_string(),
args: vec![],
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let mut ctx = create_test_context();
let result = strip_func.to_rust_tokens(&mut ctx).unwrap();
let code = result.to_string();
assert!(code.contains("-> String"),
"Expected '-> String' for .strip() method, got: {}", code);
}
#[test]
fn test_int_float_division_semantics() {
let divide_func = HirFunction {
name: "safe_divide".to_string(),
params: vec![
HirParam::new("a".to_string(), Type::Int),
HirParam::new("b".to_string(), Type::Int),
].into(),
ret_type: Type::Float, body: vec![HirStmt::Return(Some(HirExpr::Binary {
op: BinOp::Div,
left: Box::new(HirExpr::Var("a".to_string())),
right: Box::new(HirExpr::Var("b".to_string())),
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let mut ctx = create_test_context();
let result = divide_func.to_rust_tokens(&mut ctx).unwrap();
let code = result.to_string();
assert!(code.contains("as f64") || code.contains("as f32"),
"Expected float cast for int/int division with float return, got: {}", code);
assert!(code.contains("-> f64") || code.contains("-> f32"),
"Expected float return type, got: {}", code);
let floor_div_func = HirFunction {
name: "floor_divide".to_string(),
params: vec![
HirParam::new("a".to_string(), Type::Int),
HirParam::new("b".to_string(), Type::Int),
].into(),
ret_type: Type::Int, body: vec![HirStmt::Return(Some(HirExpr::Binary {
op: BinOp::FloorDiv,
left: Box::new(HirExpr::Var("a".to_string())),
right: Box::new(HirExpr::Var("b".to_string())),
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let mut ctx = create_test_context();
let result = floor_div_func.to_rust_tokens(&mut ctx).unwrap();
let code = result.to_string();
assert!(code.contains("-> i32") || code.contains("-> i64"),
"Expected int return type for floor division, got: {}", code);
let float_div_func = HirFunction {
name: "divide_floats".to_string(),
params: vec![
HirParam::new("a".to_string(), Type::Float),
HirParam::new("b".to_string(), Type::Float),
].into(),
ret_type: Type::Float,
body: vec![HirStmt::Return(Some(HirExpr::Binary {
op: BinOp::Div,
left: Box::new(HirExpr::Var("a".to_string())),
right: Box::new(HirExpr::Var("b".to_string())),
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let mut ctx = create_test_context();
let result = float_div_func.to_rust_tokens(&mut ctx).unwrap();
let code = result.to_string();
assert!(code.contains("-> f64") || code.contains("-> f32"),
"Expected float return type, got: {}", code);
}
}