mod body_convert;
mod expr_advanced;
mod expr_builtins;
mod expr_collections;
mod expr_index_slice;
mod expr_methods;
mod expr_methods_os;
mod expr_operators;
mod method_stmt_convert;
mod operators;
mod stdlib_calls;
mod stmt_convert;
pub(crate) use body_convert::*;
pub(crate) use method_stmt_convert::*;
#[cfg(test)]
pub(crate) use operators::*;
pub(crate) use stmt_convert::*;
use crate::direct_rules::make_ident;
use crate::hir::*;
use crate::type_mapper::TypeMapper;
use anyhow::{bail, Result};
use quote::quote;
use syn::{self, parse_quote};
#[allow(dead_code)]
pub(crate) fn convert_expr(expr: &HirExpr, type_mapper: &TypeMapper) -> Result<syn::Expr> {
static EMPTY: std::sync::OnceLock<std::collections::HashSet<String>> =
std::sync::OnceLock::new();
convert_expr_with_context(
expr,
type_mapper,
false,
EMPTY.get_or_init(std::collections::HashSet::new),
)
}
pub(crate) fn convert_expr_with_context(
expr: &HirExpr,
type_mapper: &TypeMapper,
is_classmethod: bool,
vararg_functions: &std::collections::HashSet<String>,
) -> Result<syn::Expr> {
let converter = ExprConverter::with_varargs(type_mapper, is_classmethod, vararg_functions);
converter.convert(expr)
}
pub(crate) fn convert_expr_with_param_types(
expr: &HirExpr,
type_mapper: &TypeMapper,
is_classmethod: bool,
vararg_functions: &std::collections::HashSet<String>,
param_types: &std::collections::HashMap<String, Type>,
) -> Result<syn::Expr> {
let converter = ExprConverter::with_param_types(
type_mapper,
is_classmethod,
vararg_functions,
param_types.clone(),
);
converter.convert(expr)
}
pub(crate) fn convert_expr_with_class_fields(
expr: &HirExpr,
type_mapper: &TypeMapper,
is_classmethod: bool,
vararg_functions: &std::collections::HashSet<String>,
param_types: &std::collections::HashMap<String, Type>,
class_field_types: &std::collections::HashMap<String, Type>,
) -> Result<syn::Expr> {
let converter = ExprConverter::with_class_fields(
type_mapper,
is_classmethod,
vararg_functions,
param_types.clone(),
class_field_types.clone(),
);
converter.convert(expr)
}
pub(crate) fn convert_condition_expr(
expr: &HirExpr,
type_mapper: &TypeMapper,
is_classmethod: bool,
vararg_functions: &std::collections::HashSet<String>,
param_types: &std::collections::HashMap<String, Type>,
) -> Result<syn::Expr> {
let converter = ExprConverter::with_param_types(
type_mapper,
is_classmethod,
vararg_functions,
param_types.clone(),
);
let rust_expr = converter.convert(expr)?;
Ok(converter.apply_truthiness_coercion(expr, rust_expr))
}
pub(crate) fn convert_condition_expr_with_class_fields(
expr: &HirExpr,
type_mapper: &TypeMapper,
is_classmethod: bool,
vararg_functions: &std::collections::HashSet<String>,
param_types: &std::collections::HashMap<String, Type>,
class_field_types: &std::collections::HashMap<String, Type>,
) -> Result<syn::Expr> {
let converter = ExprConverter::with_class_fields(
type_mapper,
is_classmethod,
vararg_functions,
param_types.clone(),
class_field_types.clone(),
);
let rust_expr = converter.convert(expr)?;
Ok(converter.apply_truthiness_coercion(expr, rust_expr))
}
pub(crate) struct ExprConverter<'a> {
#[allow(dead_code)]
type_mapper: &'a TypeMapper,
is_classmethod: bool,
vararg_functions: &'a std::collections::HashSet<String>,
param_types: std::collections::HashMap<String, Type>,
class_field_types: std::collections::HashMap<String, Type>,
}
impl<'a> ExprConverter<'a> {
#[allow(dead_code)]
pub(crate) fn new(type_mapper: &'a TypeMapper) -> Self {
static EMPTY: std::sync::OnceLock<std::collections::HashSet<String>> =
std::sync::OnceLock::new();
Self {
type_mapper,
is_classmethod: false,
vararg_functions: EMPTY.get_or_init(std::collections::HashSet::new),
param_types: std::collections::HashMap::new(),
class_field_types: std::collections::HashMap::new(),
}
}
#[allow(dead_code)]
fn with_classmethod(type_mapper: &'a TypeMapper, is_classmethod: bool) -> Self {
static EMPTY: std::sync::OnceLock<std::collections::HashSet<String>> =
std::sync::OnceLock::new();
Self {
type_mapper,
is_classmethod,
vararg_functions: EMPTY.get_or_init(std::collections::HashSet::new),
param_types: std::collections::HashMap::new(),
class_field_types: std::collections::HashMap::new(),
}
}
fn with_varargs(
type_mapper: &'a TypeMapper,
is_classmethod: bool,
vararg_functions: &'a std::collections::HashSet<String>,
) -> Self {
Self {
type_mapper,
is_classmethod,
vararg_functions,
param_types: std::collections::HashMap::new(),
class_field_types: std::collections::HashMap::new(),
}
}
fn with_param_types(
type_mapper: &'a TypeMapper,
is_classmethod: bool,
vararg_functions: &'a std::collections::HashSet<String>,
param_types: std::collections::HashMap<String, Type>,
) -> Self {
Self {
type_mapper,
is_classmethod,
vararg_functions,
param_types,
class_field_types: std::collections::HashMap::new(),
}
}
fn with_class_fields(
type_mapper: &'a TypeMapper,
is_classmethod: bool,
vararg_functions: &'a std::collections::HashSet<String>,
param_types: std::collections::HashMap<String, Type>,
class_field_types: std::collections::HashMap<String, Type>,
) -> Self {
Self {
type_mapper,
is_classmethod,
vararg_functions,
param_types,
class_field_types,
}
}
fn expr_returns_float_direct(&self, expr: &HirExpr) -> bool {
match expr {
HirExpr::Literal(Literal::Float(_)) => true,
HirExpr::Var(name) => {
matches!(self.param_types.get(name), Some(Type::Float))
}
HirExpr::Attribute { value, attr } => {
if matches!(value.as_ref(), HirExpr::Var(name) if name == "self")
&& matches!(self.class_field_types.get(attr), Some(Type::Float))
{
return true;
}
false
}
HirExpr::Binary { left, right, .. } => {
self.expr_returns_float_direct(left) || self.expr_returns_float_direct(right)
}
HirExpr::MethodCall { method, .. } => {
matches!(
method.as_str(),
"mean" | "sum" | "std" | "norm" | "variance"
)
}
_ => false,
}
}
fn is_int_expr(&self, expr: &HirExpr) -> bool {
match expr {
HirExpr::Literal(Literal::Int(_)) => true,
HirExpr::Var(name) => {
matches!(self.param_types.get(name), Some(Type::Int))
}
_ => false,
}
}
fn infer_iterable_element_type(&self, iter_expr: &HirExpr) -> Option<Type> {
match iter_expr {
HirExpr::Var(name) => {
if let Some(Type::List(elem_type) | Type::Set(elem_type)) =
self.param_types.get(name)
{
return Some((**elem_type).clone());
}
if let Some(Type::List(elem_type) | Type::Set(elem_type)) =
self.class_field_types.get(name)
{
return Some((**elem_type).clone());
}
None
}
HirExpr::MethodCall { object, method, .. } => {
match method.as_str() {
"values" | "items" | "keys" => {
if let HirExpr::Var(name) = object.as_ref() {
if let Some(Type::Dict(_, val_type)) = self.param_types.get(name) {
if method == "values" {
return Some((**val_type).clone());
}
}
}
}
_ => {}
}
None
}
HirExpr::Attribute { value, attr } => {
if matches!(value.as_ref(), HirExpr::Var(name) if name == "self") {
if let Some(Type::List(elem_type) | Type::Set(elem_type)) =
self.class_field_types.get(attr)
{
return Some((**elem_type).clone());
}
}
None
}
_ => None,
}
}
fn with_additional_param(&self, var_name: String, var_type: Type) -> Self {
let mut new_params = self.param_types.clone();
new_params.insert(var_name, var_type);
Self {
type_mapper: self.type_mapper,
is_classmethod: self.is_classmethod,
vararg_functions: self.vararg_functions,
param_types: new_params,
class_field_types: self.class_field_types.clone(),
}
}
pub(crate) 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::Slice {
base,
start,
stop,
step,
} => self.convert_slice(base, start, stop, step),
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::DynamicCall { callee, args, .. } => self.convert_dynamic_call(callee, args),
HirExpr::ListComp {
element,
generators,
} => {
if generators.len() != 1 {
bail!("Multiple generators not supported in direct rules path");
}
let gen = &generators[0];
let condition = if gen.conditions.is_empty() {
None
} else if gen.conditions.len() == 1 {
Some(Box::new(gen.conditions[0].clone()))
} else {
bail!("Multiple conditions in generator not supported in direct rules path");
};
self.convert_list_comp(element, &gen.target, &gen.iter, &condition)
}
HirExpr::SetComp {
element,
generators,
} => {
if generators.len() != 1 {
bail!("Multiple generators not supported in direct rules path");
}
let gen = &generators[0];
let condition = if gen.conditions.is_empty() {
None
} else if gen.conditions.len() == 1 {
Some(Box::new(gen.conditions[0].clone()))
} else {
bail!("Multiple conditions in generator not supported in direct rules path");
};
self.convert_set_comp(element, &gen.target, &gen.iter, &condition)
}
HirExpr::DictComp {
key,
value,
generators,
} => {
if generators.len() != 1 {
bail!("Multiple generators not supported in direct rules path");
}
let gen = &generators[0];
let condition = if gen.conditions.is_empty() {
None
} else if gen.conditions.len() == 1 {
Some(Box::new(gen.conditions[0].clone()))
} else {
bail!("Multiple conditions in generator not supported in direct rules path");
};
self.convert_dict_comp(key, value, &gen.target, &gen.iter, &condition)
}
HirExpr::Attribute { value, attr } => self.convert_attribute(value, attr),
HirExpr::Await { value } => self.convert_await(value),
HirExpr::FString { parts } => self.convert_fstring(parts),
HirExpr::IfExpr { test, body, orelse } => {
let test_expr = self.convert(test)?;
let body_expr = self.convert(body)?;
let orelse_expr = self.convert(orelse)?;
Ok(parse_quote! { if #test_expr { #body_expr } else { #orelse_expr } })
}
HirExpr::GeneratorExp {
element,
generators,
} => {
if generators.len() != 1 {
bail!("Multiple generators not supported in direct rules path");
}
let gen = &generators[0];
let iter_expr = self.convert(&gen.iter)?;
let target_ident = make_ident(&gen.target);
let element_type = self.infer_iterable_element_type(&gen.iter);
let inner_converter = if let Some(elem_type) = element_type {
self.with_additional_param(gen.target.clone(), elem_type)
} else {
Self {
type_mapper: self.type_mapper,
is_classmethod: self.is_classmethod,
vararg_functions: self.vararg_functions,
param_types: self.param_types.clone(),
class_field_types: self.class_field_types.clone(),
}
};
let element_expr = inner_converter.convert(element)?;
if gen.conditions.is_empty() {
Ok(parse_quote! { #iter_expr.iter().map(|#target_ident| #element_expr) })
} else if gen.conditions.len() == 1 {
let cond_expr = inner_converter.convert(&gen.conditions[0])?;
Ok(parse_quote! {
#iter_expr.iter()
.filter(|#target_ident| #cond_expr)
.map(|#target_ident| #element_expr)
})
} else {
bail!("Multiple conditions in generator not supported in direct rules path");
}
}
HirExpr::SortByKey {
iterable,
key_params,
key_body,
reverse_expr,
} => {
let iter_expr = self.convert(iterable)?;
let key_body_expr = self.convert(key_body)?;
let key_param = if key_params.len() == 1 {
let p = make_ident(&key_params[0]);
quote! { #p }
} else {
let params: Vec<_> = key_params.iter().map(|p| make_ident(p)).collect();
quote! { (#(#params),*) }
};
let is_reversed = match reverse_expr {
Some(boxed) => matches!(boxed.as_ref(), HirExpr::Literal(Literal::Bool(true))),
_ => false,
};
if is_reversed {
Ok(parse_quote! {
{
let mut v: Vec<_> = #iter_expr.into_iter().collect();
v.sort_by_key(|#key_param| std::cmp::Reverse(#key_body_expr));
v
}
})
} else {
Ok(parse_quote! {
{
let mut v: Vec<_> = #iter_expr.into_iter().collect();
v.sort_by_key(|#key_param| #key_body_expr);
v
}
})
}
}
_ => bail!("Expression type not yet supported: {:?}", expr),
}
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod tests;