use crate::hir::*;
use crate::type_mapper::{RustType, TypeMapper};
use anyhow::{bail, Result};
use quote::quote;
use syn::{self, parse_quote};
fn extract_nested_indices(
expr: &HirExpr,
type_mapper: &TypeMapper,
) -> Result<(syn::Expr, Vec<syn::Expr>)> {
let mut indices = Vec::new();
let mut current = expr;
loop {
match current {
HirExpr::Index { base, index } => {
indices.push(convert_expr(index, type_mapper)?);
current = base;
}
_ => {
let base_expr = convert_expr(current, type_mapper)?;
indices.reverse(); return Ok((base_expr, indices));
}
}
}
}
pub fn apply_rules(module: &HirModule, type_mapper: &TypeMapper) -> Result<syn::File> {
let mut items = Vec::new();
items.push(parse_quote! {
use std::collections::HashMap;
});
for type_alias in &module.type_aliases {
let alias_item = convert_type_alias(type_alias, type_mapper)?;
items.push(alias_item);
}
for protocol in &module.protocols {
let trait_item = convert_protocol_to_trait(protocol, type_mapper)?;
items.push(trait_item);
}
for class in &module.classes {
let struct_items = convert_class_to_struct(class, type_mapper)?;
items.extend(struct_items);
}
for func in &module.functions {
let rust_func = convert_function(func, type_mapper)?;
items.push(syn::Item::Fn(rust_func));
}
Ok(syn::File {
shebang: None,
attrs: vec![],
items,
})
}
fn convert_type_alias(type_alias: &TypeAlias, type_mapper: &TypeMapper) -> Result<syn::Item> {
let alias_name = syn::Ident::new(&type_alias.name, proc_macro2::Span::call_site());
let rust_type = type_mapper.map_type(&type_alias.target_type);
let target_type = rust_type_to_syn_type(&rust_type)?;
if type_alias.is_newtype {
Ok(parse_quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct #alias_name(pub #target_type);
})
} else {
Ok(parse_quote! {
pub type #alias_name = #target_type;
})
}
}
fn convert_protocol_to_trait(protocol: &Protocol, type_mapper: &TypeMapper) -> Result<syn::Item> {
let trait_name = syn::Ident::new(&protocol.name, proc_macro2::Span::call_site());
let generics = if protocol.type_params.is_empty() {
syn::Generics::default()
} else {
let params: Vec<syn::GenericParam> = protocol
.type_params
.iter()
.map(|param| {
let ident = syn::Ident::new(param, proc_macro2::Span::call_site());
syn::GenericParam::Type(syn::TypeParam {
attrs: vec![],
ident,
colon_token: None,
bounds: syn::punctuated::Punctuated::new(),
eq_token: None,
default: None,
})
})
.collect();
syn::Generics {
lt_token: Some(syn::Token)),
params: params.into_iter().collect(),
gt_token: Some(syn::Token)),
where_clause: None,
}
};
let mut trait_items = Vec::new();
for method in &protocol.methods {
let method_item = convert_protocol_method_to_trait_method(method, type_mapper)?;
trait_items.push(method_item);
}
let mut attrs = vec![];
if protocol.is_runtime_checkable {
attrs.push(parse_quote! { #[cfg(feature = "runtime_checkable")] });
}
Ok(syn::Item::Trait(syn::ItemTrait {
attrs,
vis: parse_quote! { pub },
unsafety: None,
auto_token: None,
restriction: None,
trait_token: syn::Token),
ident: trait_name,
generics,
colon_token: None,
supertraits: syn::punctuated::Punctuated::new(),
brace_token: syn::token::Brace::default(),
items: trait_items,
}))
}
pub fn convert_class_to_struct(
class: &HirClass,
type_mapper: &TypeMapper,
) -> Result<Vec<syn::Item>> {
let mut items = Vec::new();
let struct_name = syn::Ident::new(&class.name, proc_macro2::Span::call_site());
let mut fields = Vec::new();
for field in &class.fields {
let field_name = syn::Ident::new(&field.name, proc_macro2::Span::call_site());
let rust_type = type_mapper.map_type(&field.field_type);
let field_type = rust_type_to_syn_type(&rust_type)?;
fields.push(syn::Field {
attrs: vec![],
vis: syn::Visibility::Public(syn::Token)),
mutability: syn::FieldMutability::None,
ident: Some(field_name),
colon_token: Some(syn::Token)),
ty: field_type,
});
}
let struct_item = syn::Item::Struct(syn::ItemStruct {
attrs: if class.is_dataclass {
vec![parse_quote! { #[derive(Debug, Clone, PartialEq)] }]
} else {
vec![parse_quote! { #[derive(Debug, Clone)] }]
},
vis: syn::Visibility::Public(syn::Token)),
struct_token: syn::Token),
ident: struct_name.clone(),
generics: syn::Generics::default(),
fields: syn::Fields::Named(syn::FieldsNamed {
brace_token: syn::token::Brace::default(),
named: fields.into_iter().collect(),
}),
semi_token: None,
});
items.push(struct_item);
let mut impl_items = Vec::new();
let has_init = class.methods.iter().any(|m| m.name == "__init__");
if has_init {
for method in &class.methods {
if method.name == "__init__" {
let new_method = convert_init_to_new(method, class, &struct_name, type_mapper)?;
impl_items.push(syn::ImplItem::Fn(new_method));
} else {
let rust_method = convert_method_to_impl_item(method, type_mapper)?;
impl_items.push(syn::ImplItem::Fn(rust_method));
}
}
} else {
if class.is_dataclass
|| class
.fields
.iter()
.all(|f| f.default_value.is_some() || f.field_type == Type::Int)
{
let new_method = generate_dataclass_new(class, &struct_name, type_mapper)?;
impl_items.push(syn::ImplItem::Fn(new_method));
}
for method in &class.methods {
let rust_method = convert_method_to_impl_item(method, type_mapper)?;
impl_items.push(syn::ImplItem::Fn(rust_method));
}
}
if !impl_items.is_empty() {
let impl_block = syn::Item::Impl(syn::ItemImpl {
attrs: vec![],
defaultness: None,
unsafety: None,
impl_token: syn::Token),
generics: syn::Generics::default(),
trait_: None,
self_ty: Box::new(parse_quote! { #struct_name }),
brace_token: syn::token::Brace::default(),
items: impl_items,
});
items.push(impl_block);
}
Ok(items)
}
fn generate_dataclass_new(
class: &HirClass,
_struct_name: &syn::Ident,
type_mapper: &TypeMapper,
) -> Result<syn::ImplItemFn> {
let mut inputs = syn::punctuated::Punctuated::new();
let fields_without_defaults: Vec<_> = class
.fields
.iter()
.filter(|f| f.default_value.is_none())
.collect();
for field in &fields_without_defaults {
let param_ident = syn::Ident::new(&field.name, proc_macro2::Span::call_site());
let rust_type = type_mapper.map_type(&field.field_type);
let param_syn_type = rust_type_to_syn_type(&rust_type)?;
inputs.push(syn::FnArg::Typed(syn::PatType {
attrs: vec![],
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: param_ident.clone(),
subpat: None,
})),
colon_token: syn::Token),
ty: Box::new(param_syn_type),
}));
}
let field_inits = class
.fields
.iter()
.map(|field| {
let field_ident = syn::Ident::new(&field.name, proc_macro2::Span::call_site());
if field.default_value.is_some() {
if field.field_type == Type::Int {
quote! { #field_ident: 0 }
} else {
quote! { #field_ident: Default::default() }
}
} else {
quote! { #field_ident }
}
})
.collect::<Vec<_>>();
let body = parse_quote! {
{
Self {
#(#field_inits),*
}
}
};
Ok(syn::ImplItemFn {
attrs: vec![],
vis: syn::Visibility::Public(syn::Token)),
defaultness: None,
sig: syn::Signature {
constness: None,
asyncness: None,
unsafety: None,
abi: None,
fn_token: syn::Token),
ident: syn::Ident::new("new", proc_macro2::Span::call_site()),
generics: syn::Generics::default(),
paren_token: syn::token::Paren::default(),
inputs,
variadic: None,
output: syn::ReturnType::Type(
syn::Token),
Box::new(parse_quote! { Self }),
),
},
block: body,
})
}
fn convert_init_to_new(
init_method: &HirMethod,
class: &HirClass,
_struct_name: &syn::Ident,
type_mapper: &TypeMapper,
) -> Result<syn::ImplItemFn> {
let mut inputs = syn::punctuated::Punctuated::new();
for (param_name, param_type) in &init_method.params {
let param_ident = syn::Ident::new(param_name, proc_macro2::Span::call_site());
let rust_type = type_mapper.map_type(param_type);
let param_syn_type = rust_type_to_syn_type(&rust_type)?;
inputs.push(syn::FnArg::Typed(syn::PatType {
attrs: vec![],
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: param_ident,
subpat: None,
})),
colon_token: syn::Token),
ty: Box::new(param_syn_type),
}));
}
let mut field_inits = Vec::new();
for field in &class.fields {
let field_ident = syn::Ident::new(&field.name, proc_macro2::Span::call_site());
if init_method
.params
.iter()
.any(|(param_name, _)| param_name == &field.name)
{
field_inits.push(quote! { #field_ident });
} else {
let default_value = match &field.field_type {
Type::Int => quote! { 0 },
Type::Float => quote! { 0.0 },
Type::String => quote! { String::new() },
Type::Bool => quote! { false },
Type::List(_) => quote! { Vec::new() },
Type::Dict(_, _) => quote! { std::collections::HashMap::new() },
Type::Set(_) => quote! { std::collections::HashSet::new() },
_ => quote! { Default::default() },
};
field_inits.push(quote! { #field_ident: #default_value });
}
}
let body = parse_quote! {
{
Self {
#(#field_inits),*
}
}
};
Ok(syn::ImplItemFn {
attrs: vec![],
vis: syn::Visibility::Public(syn::Token)),
defaultness: None,
sig: syn::Signature {
constness: None,
asyncness: None,
unsafety: None,
abi: None,
fn_token: syn::Token),
ident: syn::Ident::new("new", proc_macro2::Span::call_site()),
generics: syn::Generics::default(),
paren_token: syn::token::Paren::default(),
inputs,
variadic: None,
output: syn::ReturnType::Type(
syn::Token),
Box::new(parse_quote! { Self }),
),
},
block: body,
})
}
fn convert_method_to_impl_item(
method: &HirMethod,
type_mapper: &TypeMapper,
) -> Result<syn::ImplItemFn> {
let method_name = syn::Ident::new(&method.name, proc_macro2::Span::call_site());
let mut inputs = syn::punctuated::Punctuated::new();
if method.is_static {
} else if method.is_classmethod {
} else if method.is_property {
inputs.push(parse_quote! { &self });
} else {
inputs.push(parse_quote! { &mut self });
}
for (param_name, param_type) in &method.params {
let param_ident = syn::Ident::new(param_name, proc_macro2::Span::call_site());
let rust_type = type_mapper.map_type(param_type);
let param_syn_type = rust_type_to_syn_type(&rust_type)?;
inputs.push(syn::FnArg::Typed(syn::PatType {
attrs: vec![],
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: param_ident,
subpat: None,
})),
colon_token: syn::Token),
ty: Box::new(param_syn_type),
}));
}
let rust_ret_type = type_mapper.map_type(&method.ret_type);
let ret_type = rust_type_to_syn_type(&rust_ret_type)?;
let body = if method.body.is_empty() {
parse_quote! { {} }
} else {
convert_block(&method.body, type_mapper)?
};
Ok(syn::ImplItemFn {
attrs: vec![],
vis: syn::Visibility::Public(syn::Token)),
defaultness: None,
sig: syn::Signature {
constness: None,
asyncness: None,
unsafety: None,
abi: None,
fn_token: syn::Token),
ident: method_name,
generics: syn::Generics::default(),
paren_token: syn::token::Paren::default(),
inputs,
variadic: None,
output: if matches!(method.ret_type, Type::None) {
syn::ReturnType::Default
} else {
syn::ReturnType::Type(
syn::Token),
Box::new(ret_type),
)
},
},
block: body,
})
}
fn convert_protocol_method_to_trait_method(
method: &ProtocolMethod,
type_mapper: &TypeMapper,
) -> Result<syn::TraitItem> {
let method_name = syn::Ident::new(&method.name, proc_macro2::Span::call_site());
let mut inputs = syn::punctuated::Punctuated::new();
let method_params = if !method.params.is_empty() && method.params[0].0 == "self" {
inputs.push(syn::FnArg::Receiver(syn::Receiver {
attrs: vec![],
reference: Some((syn::Token), None)),
mutability: None,
self_token: syn::Token),
colon_token: None,
ty: Box::new(parse_quote! { Self }),
}));
&method.params[1..] } else {
&method.params[..]
};
for (param_name, param_type) in method_params {
let param_ident = syn::Ident::new(param_name, proc_macro2::Span::call_site());
let rust_type = type_mapper.map_type(param_type);
let param_syn_type = rust_type_to_syn_type(&rust_type)?;
inputs.push(syn::FnArg::Typed(syn::PatType {
attrs: vec![],
pat: Box::new(syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: param_ident,
subpat: None,
})),
colon_token: syn::Token),
ty: Box::new(param_syn_type),
}));
}
let rust_return_type = type_mapper.map_type(&method.ret_type);
let return_type = rust_type_to_syn_type(&rust_return_type)?;
let sig = syn::Signature {
constness: None,
asyncness: None,
unsafety: None,
abi: None,
fn_token: syn::Token),
ident: method_name,
generics: syn::Generics::default(),
paren_token: syn::token::Paren::default(),
inputs,
variadic: None,
output: syn::ReturnType::Type(
syn::Token),
Box::new(return_type),
),
};
if method.has_default {
Ok(syn::TraitItem::Fn(syn::TraitItemFn {
attrs: vec![],
sig,
default: None,
semi_token: Some(syn::Token)),
}))
} else {
Ok(syn::TraitItem::Fn(syn::TraitItemFn {
attrs: vec![],
sig,
default: None,
semi_token: Some(syn::Token)),
}))
}
}
fn rust_type_to_syn_type(rust_type: &RustType) -> Result<syn::Type> {
use RustType::*;
Ok(match rust_type {
Unit => parse_quote! { () },
Primitive(prim_type) => {
use crate::type_mapper::PrimitiveType;
match prim_type {
PrimitiveType::Bool => parse_quote! { bool },
PrimitiveType::I8 => parse_quote! { i8 },
PrimitiveType::I16 => parse_quote! { i16 },
PrimitiveType::I32 => parse_quote! { i32 },
PrimitiveType::I64 => parse_quote! { i64 },
PrimitiveType::I128 => parse_quote! { i128 },
PrimitiveType::ISize => parse_quote! { isize },
PrimitiveType::U8 => parse_quote! { u8 },
PrimitiveType::U16 => parse_quote! { u16 },
PrimitiveType::U32 => parse_quote! { u32 },
PrimitiveType::U64 => parse_quote! { u64 },
PrimitiveType::U128 => parse_quote! { u128 },
PrimitiveType::USize => parse_quote! { usize },
PrimitiveType::F32 => parse_quote! { f32 },
PrimitiveType::F64 => parse_quote! { f64 },
}
}
String => parse_quote! { String },
Str { lifetime } => {
if let Some(lt) = lifetime {
let lifetime_token =
syn::Lifetime::new(&format!("'{}", lt), proc_macro2::Span::call_site());
parse_quote! { &#lifetime_token str }
} else {
parse_quote! { &str }
}
}
Cow { lifetime } => {
let lifetime_token =
syn::Lifetime::new(&format!("'{}", lifetime), proc_macro2::Span::call_site());
parse_quote! { std::borrow::Cow<#lifetime_token, str> }
}
Vec(inner) => {
let inner_type = rust_type_to_syn_type(inner)?;
parse_quote! { Vec<#inner_type> }
}
HashMap(key, value) => {
let key_type = rust_type_to_syn_type(key)?;
let value_type = rust_type_to_syn_type(value)?;
parse_quote! { HashMap<#key_type, #value_type> }
}
Option(inner) => {
let inner_type = rust_type_to_syn_type(inner)?;
parse_quote! { Option<#inner_type> }
}
Result(ok, err) => {
let ok_type = rust_type_to_syn_type(ok)?;
let err_type = rust_type_to_syn_type(err)?;
parse_quote! { Result<#ok_type, #err_type> }
}
Tuple(types) => {
let type_tokens: anyhow::Result<std::vec::Vec<_>> =
types.iter().map(rust_type_to_syn_type).collect();
let type_tokens = type_tokens?;
parse_quote! { (#(#type_tokens),*) }
}
Custom(name) => {
let ident = syn::Ident::new(name, proc_macro2::Span::call_site());
parse_quote! { #ident }
}
Unsupported(name) => {
let ident = syn::Ident::new(
&format!("UnsupportedType_{}", name.replace(" ", "_")),
proc_macro2::Span::call_site(),
);
parse_quote! { #ident }
}
TypeParam(name) => {
let ident = syn::Ident::new(name, proc_macro2::Span::call_site());
parse_quote! { #ident }
}
Generic { base, params } => {
let base_ident = syn::Ident::new(base, proc_macro2::Span::call_site());
let param_types: anyhow::Result<std::vec::Vec<_>> =
params.iter().map(rust_type_to_syn_type).collect();
let param_types = param_types?;
parse_quote! { #base_ident<#(#param_types),*> }
}
Enum { name, variants: _ } => {
let ident = syn::Ident::new(name, proc_macro2::Span::call_site());
parse_quote! { #ident }
}
Reference { inner, mutable, .. } => {
let inner_type = rust_type_to_syn_type(inner)?;
if *mutable {
parse_quote! { &mut #inner_type }
} else {
parse_quote! { &#inner_type }
}
}
Array { element_type, size } => {
let element = rust_type_to_syn_type(element_type)?;
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(|_| "/* invalid const expression */".parse().unwrap());
parse_quote! { [#element; #expr_tokens] }
}
}
}
HashSet(inner) => {
let inner_type = rust_type_to_syn_type(inner)?;
parse_quote! { HashSet<#inner_type> }
}
})
}
fn convert_function(func: &HirFunction, type_mapper: &TypeMapper) -> Result<syn::ItemFn> {
let name = syn::Ident::new(&func.name, proc_macro2::Span::call_site());
let mut inputs = Vec::new();
for (param_name, param_type) in &func.params {
let rust_type = type_mapper.map_type(param_type);
let ty = rust_type_to_syn(&rust_type)?;
let pat = syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: None,
ident: syn::Ident::new(param_name, proc_macro2::Span::call_site()),
subpat: None,
});
let ty = if type_mapper.needs_reference(&rust_type) {
parse_quote! { &#ty }
} else {
ty
};
inputs.push(syn::FnArg::Typed(syn::PatType {
attrs: vec![],
pat: Box::new(pat),
colon_token: Default::default(),
ty: Box::new(ty),
}));
}
let rust_ret_type = type_mapper.map_return_type(&func.ret_type);
let output = if matches!(rust_ret_type, RustType::Unit) {
syn::ReturnType::Default
} else {
let ty = rust_type_to_syn(&rust_ret_type)?;
syn::ReturnType::Type(Default::default(), Box::new(ty))
};
let body_stmts = convert_body(&func.body, type_mapper)?;
let block = syn::Block {
brace_token: Default::default(),
stmts: body_stmts,
};
let mut attrs = vec![];
if let Some(docstring) = &func.docstring {
attrs.push(parse_quote! {
#[doc = #docstring]
});
}
if func.properties.panic_free {
attrs.push(parse_quote! {
#[doc = " Depyler: verified panic-free"]
});
}
if func.properties.always_terminates {
attrs.push(parse_quote! {
#[doc = " Depyler: proven to terminate"]
});
}
Ok(syn::ItemFn {
attrs,
vis: syn::Visibility::Public(Default::default()),
sig: syn::Signature {
constness: None,
asyncness: None,
unsafety: None,
abi: None,
fn_token: Default::default(),
ident: name,
generics: Default::default(),
paren_token: Default::default(),
inputs: inputs.into_iter().collect(),
variadic: None,
output,
},
block: Box::new(block),
})
}
fn rust_type_to_syn(rust_type: &RustType) -> Result<syn::Type> {
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::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::Unit => parse_quote! { () },
RustType::Array { element_type, size } => {
let element = rust_type_to_syn(element_type)?;
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(|_| "/* invalid const expression */".parse().unwrap());
parse_quote! { [#element; #expr_tokens] }
}
}
}
_ => bail!("Unsupported Rust type: {:?}", rust_type),
})
}
fn convert_body(stmts: &[HirStmt], type_mapper: &TypeMapper) -> Result<Vec<syn::Stmt>> {
stmts
.iter()
.map(|stmt| convert_stmt(stmt, type_mapper))
.collect()
}
fn convert_stmt(stmt: &HirStmt, type_mapper: &TypeMapper) -> Result<syn::Stmt> {
match stmt {
HirStmt::Assign { target, value } => {
let value_expr = convert_expr(value, type_mapper)?;
match target {
AssignTarget::Symbol(symbol) => {
let target_ident = syn::Ident::new(symbol, proc_macro2::Span::call_site());
let stmt = syn::Stmt::Local(syn::Local {
attrs: vec![],
let_token: Default::default(),
pat: syn::Pat::Ident(syn::PatIdent {
attrs: vec![],
by_ref: None,
mutability: Some(Default::default()),
ident: target_ident,
subpat: None,
}),
init: Some(syn::LocalInit {
eq_token: Default::default(),
expr: Box::new(value_expr),
diverge: None,
}),
semi_token: Default::default(),
});
Ok(stmt)
}
AssignTarget::Index { base, index } => {
let final_index = convert_expr(index, type_mapper)?;
let (base_expr, indices) = extract_nested_indices(base, type_mapper)?;
if indices.is_empty() {
let assign_expr = parse_quote! {
#base_expr.insert(#final_index, #value_expr)
};
Ok(syn::Stmt::Expr(assign_expr, Some(Default::default())))
} else {
let mut chain = base_expr;
for idx in &indices {
chain = parse_quote! {
#chain.get_mut(&#idx).unwrap()
};
}
let assign_expr = parse_quote! {
#chain.insert(#final_index, #value_expr)
};
Ok(syn::Stmt::Expr(assign_expr, Some(Default::default())))
}
}
AssignTarget::Attribute { value, attr } => {
let base_expr = convert_expr(value, type_mapper)?;
let attr_ident = syn::Ident::new(attr, proc_macro2::Span::call_site());
let assign_expr = parse_quote! {
#base_expr.#attr_ident = #value_expr
};
Ok(syn::Stmt::Expr(assign_expr, Some(Default::default())))
}
}
}
HirStmt::Return(expr) => {
let ret_expr = if let Some(e) = expr {
convert_expr(e, type_mapper)?
} else {
parse_quote! { () }
};
Ok(syn::Stmt::Expr(
parse_quote! { return #ret_expr },
Some(Default::default()),
))
}
HirStmt::If {
condition,
then_body,
else_body,
} => {
let cond = convert_expr(condition, type_mapper)?;
let then_block = convert_block(then_body, type_mapper)?;
let if_expr = if let Some(else_stmts) = else_body {
let else_block = convert_block(else_stmts, type_mapper)?;
parse_quote! {
if #cond #then_block else #else_block
}
} else {
parse_quote! {
if #cond #then_block
}
};
Ok(syn::Stmt::Expr(if_expr, Some(Default::default())))
}
HirStmt::While { condition, body } => {
let cond = convert_expr(condition, type_mapper)?;
let body_block = convert_block(body, type_mapper)?;
let while_expr = parse_quote! {
while #cond #body_block
};
Ok(syn::Stmt::Expr(while_expr, Some(Default::default())))
}
HirStmt::For { target, iter, body } => {
let target_ident = syn::Ident::new(target, proc_macro2::Span::call_site());
let iter_expr = convert_expr(iter, type_mapper)?;
let body_block = convert_block(body, type_mapper)?;
let for_expr = parse_quote! {
for #target_ident in #iter_expr #body_block
};
Ok(syn::Stmt::Expr(for_expr, Some(Default::default())))
}
HirStmt::Expr(expr) => {
let rust_expr = convert_expr(expr, type_mapper)?;
Ok(syn::Stmt::Expr(rust_expr, Some(Default::default())))
}
HirStmt::Raise {
exception,
cause: _,
} => {
let panic_expr = if let Some(exc) = exception {
let exc_expr = convert_expr(exc, type_mapper)?;
parse_quote! { panic!("Exception: {}", #exc_expr) }
} else {
parse_quote! { panic!("Exception raised") }
};
Ok(syn::Stmt::Expr(panic_expr, Some(Default::default())))
}
HirStmt::Break { label } => {
let break_expr = if let Some(label_name) = label {
let label_ident =
syn::Lifetime::new(&format!("'{}", label_name), proc_macro2::Span::call_site());
parse_quote! { break #label_ident }
} else {
parse_quote! { break }
};
Ok(syn::Stmt::Expr(break_expr, Some(Default::default())))
}
HirStmt::Continue { label } => {
let continue_expr = if let Some(label_name) = label {
let label_ident =
syn::Lifetime::new(&format!("'{}", label_name), proc_macro2::Span::call_site());
parse_quote! { continue #label_ident }
} else {
parse_quote! { continue }
};
Ok(syn::Stmt::Expr(continue_expr, Some(Default::default())))
}
HirStmt::With {
context,
target,
body,
} => {
let context_expr = convert_expr(context, type_mapper)?;
let body_block = convert_block(body, type_mapper)?;
let block_expr = if let Some(var_name) = target {
let var_ident = syn::Ident::new(var_name, proc_macro2::Span::call_site());
parse_quote! {
{
let mut #var_ident = #context_expr;
#body_block
}
}
} else {
parse_quote! {
{
let _context = #context_expr;
#body_block
}
}
};
Ok(syn::Stmt::Expr(block_expr, None))
}
}
}
fn convert_block(stmts: &[HirStmt], type_mapper: &TypeMapper) -> Result<syn::Block> {
let rust_stmts = convert_body(stmts, type_mapper)?;
Ok(syn::Block {
brace_token: Default::default(),
stmts: rust_stmts,
})
}
fn convert_expr(expr: &HirExpr, type_mapper: &TypeMapper) -> Result<syn::Expr> {
let converter = ExprConverter::new(type_mapper);
converter.convert(expr)
}
struct ExprConverter<'a> {
#[allow(dead_code)]
type_mapper: &'a TypeMapper,
}
impl<'a> ExprConverter<'a> {
fn new(type_mapper: &'a TypeMapper) -> Self {
Self { type_mapper }
}
fn convert(&self, expr: &HirExpr) -> Result<syn::Expr> {
match expr {
HirExpr::Literal(lit) => self.convert_literal(lit),
HirExpr::Var(name) => self.convert_variable(name),
HirExpr::Binary { op, left, right } => self.convert_binary(*op, left, right),
HirExpr::Unary { op, operand } => self.convert_unary(*op, operand),
HirExpr::Call { func, args } => self.convert_call(func, args),
HirExpr::Index { base, index } => self.convert_index(base, index),
HirExpr::List(elts) => self.convert_list(elts),
HirExpr::Dict(items) => self.convert_dict(items),
HirExpr::Tuple(elts) => self.convert_tuple(elts),
HirExpr::Set(elts) => self.convert_set(elts),
HirExpr::FrozenSet(elts) => self.convert_frozenset(elts),
HirExpr::Lambda { params, body } => self.convert_lambda(params, body),
HirExpr::MethodCall {
object,
method,
args,
} => self.convert_method_call(object, method, args),
HirExpr::ListComp {
element,
target,
iter,
condition,
} => self.convert_list_comp(element, target, iter, condition),
HirExpr::SetComp {
element,
target,
iter,
condition,
} => self.convert_set_comp(element, target, iter, condition),
HirExpr::Attribute { value, attr } => self.convert_attribute(value, attr),
HirExpr::Await { value } => self.convert_await(value),
_ => bail!("Expression type not yet supported: {:?}", expr),
}
}
fn convert_literal(&self, lit: &Literal) -> Result<syn::Expr> {
Ok(convert_literal(lit))
}
fn convert_variable(&self, name: &str) -> Result<syn::Expr> {
let ident = syn::Ident::new(name, proc_macro2::Span::call_site());
Ok(parse_quote! { #ident })
}
fn convert_binary(&self, op: BinOp, left: &HirExpr, right: &HirExpr) -> Result<syn::Expr> {
let left_expr = self.convert(left)?;
let right_expr = self.convert(right)?;
match op {
BinOp::In => {
Ok(parse_quote! { #right_expr.contains_key(&#left_expr) })
}
BinOp::NotIn => {
Ok(parse_quote! { !#right_expr.contains_key(&#left_expr) })
}
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 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::FloorDiv => {
Ok(parse_quote! {
{
let a = #left_expr;
let b = #right_expr;
let q = a / b;
let r = a % b;
if (r != 0) && ((r < 0) != (b < 0)) { q - 1 } else { q }
}
})
}
BinOp::Mul => {
match (left, right) {
(HirExpr::List(elts), HirExpr::Literal(Literal::Int(size)))
if elts.len() == 1 && *size > 0 && *size <= 32 =>
{
let elem = self.convert(&elts[0])?;
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 = self.convert(&elts[0])?;
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::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(&self, op: UnaryOp, operand: &HirExpr) -> Result<syn::Expr> {
let operand_expr = self.convert(operand)?;
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(&self, func: &str, args: &[HirExpr]) -> Result<syn::Expr> {
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| self.convert(arg))
.collect::<Result<Vec<_>>>()?;
match func {
"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 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() })
}
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 => {
bail!("range() with step parameter not yet supported")
}
_ => bail!("Invalid number of arguments for range()"),
}
}
fn convert_array_init_call(
&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 = self.convert(&args[1])?;
Ok(parse_quote! { [#value; #size_lit] })
} else {
bail!("full() requires a value argument");
}
}
_ => unreachable!(),
}
} else {
match func {
"zeros" => {
let size_expr = self.convert(&args[0])?;
Ok(parse_quote! { vec![0; #size_expr as usize] })
}
"ones" => {
let size_expr = self.convert(&args[0])?;
Ok(parse_quote! { vec![1; #size_expr as usize] })
}
"full" => {
if args.len() >= 2 {
let size_expr = self.convert(&args[0])?;
let value = self.convert(&args[1])?;
Ok(parse_quote! { vec![#value; #size_expr as usize] })
} else {
bail!("full() requires a value argument");
}
}
_ => unreachable!(),
}
}
} else {
let size_expr = self.convert(&args[0])?;
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 = self.convert(&args[1])?;
Ok(parse_quote! { vec![#value; #size_expr as usize] })
} else {
bail!("full() requires a value argument");
}
}
_ => unreachable!(),
}
}
}
fn convert_set_constructor(&self, args: &[syn::Expr]) -> Result<syn::Expr> {
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(&self, args: &[syn::Expr]) -> Result<syn::Expr> {
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 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),*) })
}
}
fn convert_index(&self, base: &HirExpr, index: &HirExpr) -> Result<syn::Expr> {
let base_expr = self.convert(base)?;
let index_expr = self.convert(index)?;
Ok(parse_quote! {
#base_expr[#index_expr as usize]
})
}
fn convert_list(&self, elts: &[HirExpr]) -> Result<syn::Expr> {
let elt_exprs: Vec<syn::Expr> = elts
.iter()
.map(|e| self.convert(e))
.collect::<Result<Vec<_>>>()?;
if !elts.is_empty() && elts.len() <= 32 {
let all_literals = elts.iter().all(|e| matches!(e, HirExpr::Literal(_)));
if all_literals {
Ok(parse_quote! { [#(#elt_exprs),*] })
} else {
Ok(parse_quote! { vec![#(#elt_exprs),*] })
}
} else {
Ok(parse_quote! { vec![#(#elt_exprs),*] })
}
}
fn convert_dict(&self, items: &[(HirExpr, HirExpr)]) -> Result<syn::Expr> {
let insert_exprs: Vec<syn::Expr> = items
.iter()
.map(|(k, v)| {
let key = self.convert(k)?;
let val = self.convert(v)?;
Ok(parse_quote! { map.insert(#key, #val) })
})
.collect::<Result<Vec<_>>>()?;
Ok(parse_quote! {
{
let mut map = HashMap::new();
#(#insert_exprs;)*
map
}
})
}
fn convert_tuple(&self, elts: &[HirExpr]) -> Result<syn::Expr> {
let elt_exprs: Vec<syn::Expr> = elts
.iter()
.map(|e| self.convert(e))
.collect::<Result<Vec<_>>>()?;
Ok(parse_quote! { (#(#elt_exprs),*) })
}
fn convert_set(&self, elts: &[HirExpr]) -> Result<syn::Expr> {
let insert_exprs: Vec<syn::Expr> = elts
.iter()
.map(|e| {
let elem = self.convert(e)?;
Ok(parse_quote! { set.insert(#elem) })
})
.collect::<Result<Vec<_>>>()?;
Ok(parse_quote! {
{
let mut set = HashSet::new();
#(#insert_exprs;)*
set
}
})
}
fn convert_frozenset(&self, elts: &[HirExpr]) -> Result<syn::Expr> {
let insert_exprs: Vec<syn::Expr> = elts
.iter()
.map(|e| {
let elem = self.convert(e)?;
Ok(parse_quote! { set.insert(#elem) })
})
.collect::<Result<Vec<_>>>()?;
Ok(parse_quote! {
{
let mut set = HashSet::new();
#(#insert_exprs;)*
std::sync::Arc::new(set)
}
})
}
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_method_call(
&self,
object: &HirExpr,
method: &str,
args: &[HirExpr],
) -> Result<syn::Expr> {
if let HirExpr::Var(class_name) = object {
if class_name
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
{
let class_ident = syn::Ident::new(class_name, proc_macro2::Span::call_site());
let method_ident = syn::Ident::new(method, proc_macro2::Span::call_site());
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| self.convert(arg))
.collect::<Result<Vec<_>>>()?;
return Ok(parse_quote! { #class_ident::#method_ident(#(#arg_exprs),*) });
}
}
let object_expr = self.convert(object)?;
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| self.convert(arg))
.collect::<Result<Vec<_>>>()?;
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) })
}
"remove" => {
if arg_exprs.len() != 1 {
bail!("remove() requires exactly one argument");
}
let arg = &arg_exprs[0];
if self.is_set_expr(object) {
Ok(parse_quote! {
if !#object_expr.remove(&#arg) {
panic!("KeyError: element not in set");
}
})
} else {
Ok(parse_quote! {
if let Some(pos) = #object_expr.iter().position(|x| x == &#arg) {
#object_expr.remove(pos);
} else {
panic!("ValueError: list.remove(x): x not in list");
}
})
}
}
"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() })
}
"pop" => {
if self.is_set_expr(object) {
if !arg_exprs.is_empty() {
bail!("pop() takes no arguments");
}
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 {
let idx = &arg_exprs[0];
Ok(parse_quote! { #object_expr.remove(#idx as usize) })
}
}
}
_ => {
let method_ident = syn::Ident::new(method, proc_macro2::Span::call_site());
Ok(parse_quote! { #object_expr.#method_ident(#(#arg_exprs),*) })
}
}
}
fn convert_list_comp(
&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 = self.convert(iter)?;
let element_expr = self.convert(element)?;
if let Some(cond) = condition {
let cond_expr = self.convert(cond)?;
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 convert_set_comp(
&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 = self.convert(iter)?;
let element_expr = self.convert(element)?;
if let Some(cond) = condition {
let cond_expr = self.convert(cond)?;
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(&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 = self.convert(body)?;
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 convert_await(&self, value: &HirExpr) -> Result<syn::Expr> {
let value_expr = self.convert(value)?;
Ok(parse_quote! { #value_expr.await })
}
fn convert_attribute(&self, value: &HirExpr, attr: &str) -> Result<syn::Expr> {
let value_expr = self.convert(value)?;
let attr_ident = syn::Ident::new(attr, proc_macro2::Span::call_site());
Ok(parse_quote! { #value_expr.#attr_ident })
}
}
fn is_len_call(expr: &HirExpr) -> bool {
matches!(expr, HirExpr::Call { func, args } if func == "len" && args.len() == 1)
}
fn convert_literal(lit: &Literal) -> 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 lit = syn::LitFloat::new(&f.to_string(), proc_macro2::Span::call_site());
parse_quote! { #lit }
}
Literal::String(s) => {
let lit = syn::LitStr::new(s, proc_macro2::Span::call_site());
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! { () },
}
}
fn convert_binop(op: BinOp) -> Result<syn::BinOp> {
match op {
BinOp::Add
| BinOp::Sub
| BinOp::Mul
| BinOp::Div
| BinOp::Mod
| BinOp::FloorDiv
| BinOp::Pow => convert_arithmetic_op(op),
BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::LtEq | BinOp::Gt | BinOp::GtEq => {
convert_comparison_op(op)
}
BinOp::And | BinOp::Or => convert_logical_op(op),
BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor | BinOp::LShift | BinOp::RShift => {
convert_bitwise_op(op)
}
BinOp::In | BinOp::NotIn => {
bail!("in/not in operators should be handled by convert_binary")
}
}
}
fn convert_arithmetic_op(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 in convert_expr, not as simple operator")
}
Pow => bail!("Power operator not directly supported in Rust"),
_ => bail!("Invalid operator {:?} for arithmetic conversion", op),
}
}
fn convert_comparison_op(op: BinOp) -> Result<syn::BinOp> {
use BinOp::*;
match op {
Eq => Ok(parse_quote! { == }),
NotEq => Ok(parse_quote! { != }),
Lt => Ok(parse_quote! { < }),
LtEq => Ok(parse_quote! { <= }),
Gt => Ok(parse_quote! { > }),
GtEq => Ok(parse_quote! { >= }),
_ => bail!("Invalid operator {:?} for comparison conversion", op),
}
}
fn convert_logical_op(op: BinOp) -> Result<syn::BinOp> {
use BinOp::*;
match op {
And => Ok(parse_quote! { && }),
Or => Ok(parse_quote! { || }),
_ => bail!("Invalid operator {:?} for logical conversion", op),
}
}
fn convert_bitwise_op(op: BinOp) -> Result<syn::BinOp> {
use BinOp::*;
match op {
BitAnd => Ok(parse_quote! { & }),
BitOr => Ok(parse_quote! { | }),
BitXor => Ok(parse_quote! { ^ }),
LShift => Ok(parse_quote! { << }),
RShift => Ok(parse_quote! { >> }),
_ => bail!("Invalid operator {:?} for bitwise conversion", op),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::type_mapper::TypeMapper;
use depyler_annotations::TranspilationAnnotations;
fn create_test_type_mapper() -> TypeMapper {
TypeMapper::default()
}
#[test]
fn test_expr_converter_literal() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let lit_expr = HirExpr::Literal(Literal::Int(42));
let result = converter.convert(&lit_expr).unwrap();
assert!(matches!(result, syn::Expr::Lit(_)));
}
#[test]
fn test_expr_converter_variable() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let var_expr = HirExpr::Var("x".to_string());
let result = converter.convert(&var_expr).unwrap();
assert!(matches!(result, syn::Expr::Path(_)));
}
#[test]
fn test_expr_converter_binary() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let binary_expr = HirExpr::Binary {
op: BinOp::Add,
left: Box::new(HirExpr::Literal(Literal::Int(1))),
right: Box::new(HirExpr::Literal(Literal::Int(2))),
};
let result = converter.convert(&binary_expr).unwrap();
assert!(matches!(result, syn::Expr::Binary(_)));
}
#[test]
fn test_expr_converter_len_call() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let call_expr = HirExpr::Call {
func: "len".to_string(),
args: vec![HirExpr::Var("arr".to_string())],
};
let result = converter.convert(&call_expr).unwrap();
assert!(matches!(result, syn::Expr::MethodCall(_)));
}
#[test]
fn test_expr_converter_range_call_single_arg() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let call_expr = HirExpr::Call {
func: "range".to_string(),
args: vec![HirExpr::Literal(Literal::Int(10))],
};
let result = converter.convert(&call_expr).unwrap();
assert!(matches!(result, syn::Expr::Range(_)));
}
#[test]
fn test_array_literal_generation() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let list_expr = HirExpr::List(vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::Int(2)),
HirExpr::Literal(Literal::Int(3)),
]);
let result = converter.convert(&list_expr).unwrap();
assert!(matches!(result, syn::Expr::Array(_)));
}
#[test]
fn test_array_multiplication_pattern() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let mult_expr = HirExpr::Binary {
op: BinOp::Mul,
left: Box::new(HirExpr::List(vec![HirExpr::Literal(Literal::Int(0))])),
right: Box::new(HirExpr::Literal(Literal::Int(10))),
};
let result = converter.convert(&mult_expr).unwrap();
assert!(matches!(result, syn::Expr::Repeat(_)));
}
#[test]
fn test_array_init_functions() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let zeros_call = HirExpr::Call {
func: "zeros".to_string(),
args: vec![HirExpr::Literal(Literal::Int(5))],
};
let result = converter.convert(&zeros_call).unwrap();
assert!(matches!(result, syn::Expr::Repeat(_)));
}
#[test]
fn test_expr_converter_range_call_two_args() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let call_expr = HirExpr::Call {
func: "range".to_string(),
args: vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::Int(10)),
],
};
let result = converter.convert(&call_expr).unwrap();
assert!(matches!(result, syn::Expr::Range(_)));
}
#[test]
fn test_expr_converter_list() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let list_expr = HirExpr::List(vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::Int(2)),
HirExpr::Literal(Literal::Int(3)),
]);
let result = converter.convert(&list_expr).unwrap();
assert!(matches!(result, syn::Expr::Array(_)));
let var_list = HirExpr::List(vec![
HirExpr::Var("x".to_string()),
HirExpr::Var("y".to_string()),
]);
let result2 = converter.convert(&var_list).unwrap();
assert!(matches!(result2, syn::Expr::Macro(_)));
}
#[test]
fn test_expr_converter_dict() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let dict_expr = HirExpr::Dict(vec![(
HirExpr::Literal(Literal::String("key".to_string())),
HirExpr::Literal(Literal::Int(42)),
)]);
let result = converter.convert(&dict_expr).unwrap();
assert!(matches!(result, syn::Expr::Block(_)));
}
#[test]
fn test_expr_converter_tuple() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let tuple_expr = HirExpr::Tuple(vec![
HirExpr::Literal(Literal::Int(1)),
HirExpr::Literal(Literal::String("hello".to_string())),
]);
let result = converter.convert(&tuple_expr).unwrap();
assert!(matches!(result, syn::Expr::Tuple(_)));
}
#[test]
fn test_convert_binop_arithmetic() {
assert!(convert_binop(BinOp::Add).is_ok());
assert!(convert_binop(BinOp::Sub).is_ok());
assert!(convert_binop(BinOp::Mul).is_ok());
assert!(convert_binop(BinOp::Div).is_ok());
assert!(convert_binop(BinOp::Mod).is_ok());
}
#[test]
fn test_convert_binop_comparison() {
assert!(convert_binop(BinOp::Eq).is_ok());
assert!(convert_binop(BinOp::NotEq).is_ok());
assert!(convert_binop(BinOp::Lt).is_ok());
assert!(convert_binop(BinOp::LtEq).is_ok());
assert!(convert_binop(BinOp::Gt).is_ok());
assert!(convert_binop(BinOp::GtEq).is_ok());
}
#[test]
fn test_convert_binop_logical() {
assert!(convert_binop(BinOp::And).is_ok());
assert!(convert_binop(BinOp::Or).is_ok());
}
#[test]
fn test_convert_binop_bitwise() {
assert!(convert_binop(BinOp::BitAnd).is_ok());
assert!(convert_binop(BinOp::BitOr).is_ok());
assert!(convert_binop(BinOp::BitXor).is_ok());
assert!(convert_binop(BinOp::LShift).is_ok());
assert!(convert_binop(BinOp::RShift).is_ok());
}
#[test]
fn test_convert_binop_unsupported() {
assert!(convert_binop(BinOp::Pow).is_err());
assert!(convert_binop(BinOp::In).is_err());
assert!(convert_binop(BinOp::NotIn).is_err());
assert!(convert_binop(BinOp::FloorDiv).is_err()); }
#[test]
fn test_floor_division_handling() {
let type_mapper = create_test_type_mapper();
let converter = ExprConverter::new(&type_mapper);
let int_floor_div = HirExpr::Binary {
op: BinOp::FloorDiv,
left: Box::new(HirExpr::Literal(Literal::Int(7))),
right: Box::new(HirExpr::Literal(Literal::Int(3))),
};
let result = converter.convert(&int_floor_div).unwrap();
assert!(matches!(result, syn::Expr::Block(_)));
let neg_floor_div = HirExpr::Binary {
op: BinOp::FloorDiv,
left: Box::new(HirExpr::Literal(Literal::Int(-7))),
right: Box::new(HirExpr::Literal(Literal::Int(3))),
};
let result = converter.convert(&neg_floor_div).unwrap();
assert!(matches!(result, syn::Expr::Block(_)));
}
#[test]
fn test_convert_literal() {
let int_lit = convert_literal(&Literal::Int(42));
assert!(matches!(int_lit, syn::Expr::Lit(_)));
let float_lit = convert_literal(&Literal::Float(1.234)); assert!(matches!(float_lit, syn::Expr::Lit(_)));
let string_lit = convert_literal(&Literal::String("hello".to_string()));
assert!(matches!(string_lit, syn::Expr::MethodCall(_)));
let bool_lit = convert_literal(&Literal::Bool(true));
assert!(matches!(bool_lit, syn::Expr::Lit(_)));
let none_lit = convert_literal(&Literal::None);
assert!(matches!(none_lit, syn::Expr::Tuple(_)));
}
#[test]
fn test_convert_function_with_documentation() {
let type_mapper = create_test_type_mapper();
let func = HirFunction {
name: "test_func".to_string(),
params: vec![("x".to_string(), Type::Int)].into(),
ret_type: Type::Int,
body: vec![HirStmt::Return(Some(HirExpr::Var("x".to_string())))],
properties: FunctionProperties {
is_pure: true,
always_terminates: true,
panic_free: true,
max_stack_depth: Some(1),
can_fail: false,
error_types: vec![],
is_async: false,
},
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let result = convert_function(&func, &type_mapper).unwrap();
assert!(!result.attrs.is_empty());
assert_eq!(result.sig.ident.to_string(), "test_func");
}
#[test]
fn test_apply_rules() {
let type_mapper = create_test_type_mapper();
let module = HirModule {
functions: vec![HirFunction {
name: "add".to_string(),
params: vec![("a".to_string(), Type::Int), ("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,
}],
imports: vec![],
type_aliases: vec![],
protocols: vec![],
classes: vec![],
};
let result = apply_rules(&module, &type_mapper).unwrap();
assert!(result.items.len() >= 2);
assert!(matches!(result.items[0], syn::Item::Use(_)));
assert!(matches!(result.items[1], syn::Item::Fn(_)));
}
}