#![allow(clippy::missing_errors_doc)]
#![allow(clippy::wildcard_imports)]
#![allow(clippy::doc_markdown)]
use super::*;
use crate::frontend::ast::{AggregateOp, DataFrameColumn, DataFrameOp, JoinType};
use anyhow::Result;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
impl Transpiler {
pub fn transpile_dataframe(&self, columns: &[DataFrameColumn]) -> Result<TokenStream> {
if columns.is_empty() {
return Ok(quote! {
{
let df: std::collections::HashMap<String, Vec<String>> = std::collections::HashMap::new();
df
}
});
}
let mut col_inserts = Vec::new();
for column in columns {
let col_name = &column.name;
let values_tokens = if column.values.is_empty() {
quote! { vec![] }
} else {
let value_tokens: Result<Vec<_>> = column
.values
.iter()
.map(|v| self.transpile_expr(v))
.collect();
let value_tokens = value_tokens?;
quote! { vec![#(format!("{:?}", #value_tokens)),*] }
};
col_inserts.push(quote! {
df.insert(#col_name.to_string(), #values_tokens);
});
}
Ok(quote! {
{
let mut df: std::collections::HashMap<String, Vec<String>> = std::collections::HashMap::new();
#(#col_inserts)*
df
}
})
}
pub fn transpile_dataframe_operation(
&self,
df: &Expr,
op: &DataFrameOp,
) -> Result<TokenStream> {
let df_tokens = self.transpile_expr(df)?;
match op {
DataFrameOp::Select(columns) => {
let col_tokens: Vec<TokenStream> =
columns.iter().map(|col| quote! { #col }).collect();
Ok(quote! {
#df_tokens.select(&[#(#col_tokens),*]).expect("Failed to select DataFrame columns")
})
}
DataFrameOp::Filter(condition) => {
let cond_tokens = self.transpile_expr(condition)?;
Ok(quote! {
#df_tokens.filter(&#cond_tokens).expect("Failed to filter DataFrame")
})
}
DataFrameOp::GroupBy(columns) => {
let col_tokens: Vec<TokenStream> =
columns.iter().map(|col| quote! { #col }).collect();
Ok(quote! {
#df_tokens.groupby(&[#(#col_tokens),*]).expect("Failed to group DataFrame")
})
}
DataFrameOp::Sort(columns) => {
let col_tokens: Vec<TokenStream> =
columns.iter().map(|col| quote! { #col }).collect();
Ok(quote! {
#df_tokens.sort(&[#(#col_tokens),*], false)
.expect("DataFrame sort operation should not fail with valid columns")
})
}
DataFrameOp::Join { other, on, how } => {
let other_tokens = self.transpile_expr(other)?;
let on_tokens: Vec<TokenStream> = on.iter().map(|col| quote! { #col }).collect();
let join_type = match how {
JoinType::Left => quote! { polars::prelude::JoinType::Left },
JoinType::Right => quote! { polars::prelude::JoinType::Right },
JoinType::Inner => quote! { polars::prelude::JoinType::Inner },
JoinType::Outer => quote! { polars::prelude::JoinType::Outer },
};
Ok(quote! {
#df_tokens.join(
&#other_tokens,
&[#(#on_tokens),*],
&[#(#on_tokens),*],
#join_type
).expect("DataFrame join operation should not fail with valid parameters")
})
}
DataFrameOp::Aggregate(agg_ops) => {
let agg_exprs: Vec<TokenStream> = agg_ops
.iter()
.map(|op| match op {
AggregateOp::Sum(col) => quote! { col(#col).sum() },
AggregateOp::Mean(col) => quote! { col(#col).mean() },
AggregateOp::Min(col) => quote! { col(#col).min() },
AggregateOp::Max(col) => quote! { col(#col).max() },
AggregateOp::Count(col) => quote! { col(#col).count() },
AggregateOp::Std(col) => quote! { col(#col).std() },
AggregateOp::Var(col) => quote! { col(#col).var() },
})
.collect();
Ok(quote! {
#df_tokens.agg(&[#(#agg_exprs),*])
.expect("DataFrame aggregation should not fail with valid expressions")
})
}
DataFrameOp::Limit(n) => Ok(quote! {
#df_tokens.limit(#n)
}),
DataFrameOp::Head(n) => Ok(quote! {
#df_tokens.head(Some(#n))
}),
DataFrameOp::Tail(n) => Ok(quote! {
#df_tokens.tail(Some(#n))
}),
}
}
pub fn transpile_dataframe_method(
&self,
df_expr: &Expr,
method: &str,
args: &[Expr],
) -> Result<TokenStream> {
let df_tokens = self.transpile_expr(df_expr)?;
let arg_tokens: Result<Vec<_>> = args.iter().map(|a| self.transpile_expr(a)).collect();
let arg_tokens = arg_tokens?;
match method {
"column" | "build" => self.transpile_builder_method(&df_tokens, method, &arg_tokens),
"rows" | "columns" | "get" => {
self.transpile_inspection_method(&df_tokens, method, &arg_tokens)
}
"select" | "filter" | "sort" => {
self.transpile_lazy_operation(&df_tokens, method, &arg_tokens)
}
"groupby" | "group_by" => self.transpile_groupby(&df_tokens, &arg_tokens),
"agg" | "join" => self.transpile_simple_operation(&df_tokens, method, &arg_tokens),
"mean" | "std" | "min" | "max" | "sum" | "count" => {
self.transpile_statistical_method(&df_tokens, method)
}
"head" | "tail" => self.transpile_head_tail(&df_tokens, method, &arg_tokens),
_ => self.transpile_default_method(&df_tokens, method, &arg_tokens),
}
}
fn transpile_builder_method(
&self,
df_tokens: &TokenStream,
method: &str,
arg_tokens: &[TokenStream],
) -> Result<TokenStream> {
match method {
"column" => {
if arg_tokens.len() == 2 {
let name = &arg_tokens[0];
let data = &arg_tokens[1];
Ok(quote! { #df_tokens.column(#name, #data) })
} else {
Ok(quote! { #df_tokens.column(#(#arg_tokens),*) })
}
}
"build" => Ok(quote! { #df_tokens }),
_ => unreachable!("Invalid builder method: {}", method),
}
}
fn transpile_inspection_method(
&self,
df_tokens: &TokenStream,
method: &str,
arg_tokens: &[TokenStream],
) -> Result<TokenStream> {
match method {
"rows" => Ok(quote! { #df_tokens.height() }),
"columns" => Ok(quote! { #df_tokens.width() }),
"get" => {
if arg_tokens.len() == 1 {
let col_name = &arg_tokens[0];
Ok(quote! { #df_tokens.column(#col_name) })
} else {
Ok(quote! { #df_tokens.get(#(#arg_tokens),*) })
}
}
_ => unreachable!("Invalid inspection method: {}", method),
}
}
fn transpile_lazy_operation(
&self,
df_tokens: &TokenStream,
method: &str,
arg_tokens: &[TokenStream],
) -> Result<TokenStream> {
let method_ident = format_ident!("{}", method);
Ok(quote! {
#df_tokens.lazy().#method_ident(#(#arg_tokens),*).collect()
.expect("DataFrame lazy operation collection should not fail")
})
}
fn transpile_groupby(
&self,
df_tokens: &TokenStream,
arg_tokens: &[TokenStream],
) -> Result<TokenStream> {
Ok(quote! {
#df_tokens.group_by(#(#arg_tokens),*)
.expect("DataFrame group_by operation should not fail with valid columns")
})
}
fn transpile_simple_operation(
&self,
df_tokens: &TokenStream,
method: &str,
arg_tokens: &[TokenStream],
) -> Result<TokenStream> {
let method_ident = format_ident!("{}", method);
Ok(quote! {
#df_tokens.#method_ident(#(#arg_tokens),*)
.expect("DataFrame operation should not fail with valid parameters")
})
}
fn transpile_statistical_method(
&self,
df_tokens: &TokenStream,
method: &str,
) -> Result<TokenStream> {
let method_ident = format_ident!("{}", method);
Ok(quote! {
#df_tokens.#method_ident()
})
}
fn transpile_head_tail(
&self,
df_tokens: &TokenStream,
method: &str,
arg_tokens: &[TokenStream],
) -> Result<TokenStream> {
let method_ident = format_ident!("{}", method);
if arg_tokens.is_empty() {
Ok(quote! { #df_tokens.#method_ident(Some(5)) })
} else {
Ok(quote! { #df_tokens.#method_ident(Some(#(#arg_tokens),*)) })
}
}
fn transpile_default_method(
&self,
df_tokens: &TokenStream,
method: &str,
arg_tokens: &[TokenStream],
) -> Result<TokenStream> {
let method_ident = format_ident!("{}", method);
Ok(quote! {
#df_tokens.#method_ident(#(#arg_tokens),*)
})
}
pub fn is_dataframe_expr(expr: &Expr) -> bool {
use crate::frontend::ast::ExprKind;
match &expr.kind {
ExprKind::Identifier(name) if name == "df" => true,
ExprKind::Call { func, .. } => {
if let ExprKind::QualifiedName { module, name } = &func.kind {
module == "DataFrame"
&& (name == "new"
|| name == "from_csv"
|| name == "from_json"
|| name == "from_csv_string")
} else {
false
}
}
ExprKind::MethodCall {
receiver, method, ..
} => {
let is_df_method = matches!(
method.as_str(),
"column"
| "build"
| "select"
| "filter"
| "sort"
| "head"
| "tail"
| "drop_nulls"
| "fill_null"
);
is_df_method && Self::is_dataframe_expr(receiver)
}
ExprKind::DataFrame { .. } => true,
_ => false,
}
}
pub fn is_option_or_result_expr(expr: &Expr) -> bool {
use crate::frontend::ast::ExprKind;
match &expr.kind {
ExprKind::Call { func, .. } => {
if let ExprKind::Identifier(name) = &func.kind {
name == "Some"
} else {
false
}
}
ExprKind::Identifier(name) => name == "None",
ExprKind::MethodCall {
receiver, method, ..
} => {
let option_result_methods = [
"unwrap",
"unwrap_or",
"unwrap_or_else",
"unwrap_or_default",
"expect",
"ok",
"err",
"ok_or",
"ok_or_else",
"and_then",
"or_else",
"map_or",
"map_or_else",
"map_err",
"is_some",
"is_none",
"is_ok",
"is_err",
"as_ref",
"as_mut",
"take",
"replace",
"transpose",
"flatten",
];
if option_result_methods.contains(&method.as_str()) {
true
} else {
Self::is_option_or_result_expr(receiver)
}
}
_ => false,
}
}
pub fn is_option_or_result_with_context(&self, expr: &Expr) -> bool {
use crate::frontend::ast::ExprKind;
match &expr.kind {
ExprKind::Identifier(name) => {
if name == "None" {
return true;
}
if let Some(type_str) = self.variable_types.borrow().get(name) {
type_str.starts_with("Option") || type_str.starts_with("Result")
} else {
false
}
}
_ => Self::is_option_or_result_expr(expr),
}
}
pub fn register_variable_type(&self, name: &str, type_str: &str) {
self.variable_types
.borrow_mut()
.insert(name.to_string(), type_str.to_string());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
fn make_test_transpiler() -> Transpiler {
Transpiler::new()
}
fn make_literal_expr(val: i64) -> Expr {
Expr {
kind: ExprKind::Literal(Literal::Integer(val, None)),
span: Span::new(0, 10),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
#[test]
fn test_empty_dataframe() {
let transpiler = make_test_transpiler();
let result = transpiler
.transpile_dataframe(&[])
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("HashMap"));
}
#[test]
fn test_dataframe_with_columns() {
let transpiler = make_test_transpiler();
let columns = vec![
DataFrameColumn {
name: "col1".to_string(),
values: vec![make_literal_expr(1), make_literal_expr(2)],
},
DataFrameColumn {
name: "col2".to_string(),
values: vec![make_literal_expr(3), make_literal_expr(4)],
},
];
let result = transpiler
.transpile_dataframe(&columns)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("HashMap"));
assert!(output.contains("insert"));
assert!(output.contains("col1"));
assert!(output.contains("col2"));
}
#[test]
fn test_dataframe_select_operation() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0); let op = DataFrameOp::Select(vec!["col1".to_string(), "col2".to_string()]);
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("select"));
assert!(output.contains("col1"));
assert!(output.contains("col2"));
}
#[test]
fn test_dataframe_filter_operation() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let condition = make_literal_expr(1);
let op = DataFrameOp::Filter(Box::new(condition));
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("filter"));
}
#[test]
fn test_dataframe_groupby_operation() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let op = DataFrameOp::GroupBy(vec!["group_col".to_string()]);
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("groupby"));
assert!(output.contains("group_col"));
}
#[test]
fn test_dataframe_sort_operation() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let op = DataFrameOp::Sort(vec!["sort_col".to_string()]);
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("sort"));
assert!(output.contains("sort_col"));
}
#[test]
fn test_dataframe_join_operations() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let other_expr = make_literal_expr(1);
let join_types = vec![
(JoinType::Inner, "Inner"),
(JoinType::Left, "Left"),
(JoinType::Right, "Right"),
];
for (join_type, expected) in join_types {
let op = DataFrameOp::Join {
other: Box::new(other_expr.clone()),
on: vec!["id".to_string()],
how: join_type,
};
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("join"));
assert!(output.contains(expected));
}
}
#[test]
fn test_dataframe_aggregate_operations() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let agg_ops = vec![
AggregateOp::Mean("col1".to_string()),
AggregateOp::Sum("col2".to_string()),
AggregateOp::Min("col3".to_string()),
AggregateOp::Max("col4".to_string()),
AggregateOp::Count("col5".to_string()),
AggregateOp::Std("col6".to_string()),
];
let op = DataFrameOp::Aggregate(agg_ops);
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(!output.is_empty());
}
#[test]
fn test_dataframe_limit_operations() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let op = DataFrameOp::Limit(10);
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("limit"));
let op = DataFrameOp::Head(5);
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("head"));
let op = DataFrameOp::Tail(5);
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("tail"));
}
#[test]
fn test_dataframe_with_empty_column_values() {
let transpiler = make_test_transpiler();
let columns = vec![DataFrameColumn {
name: "empty_col".to_string(),
values: vec![],
}];
let result = transpiler
.transpile_dataframe(&columns)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("HashMap"));
assert!(output.contains("empty_col"));
assert!(output.contains("vec"));
}
#[test]
fn test_transpile_dataframe_method_select() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![Expr {
kind: ExprKind::Literal(Literal::String("col1".to_string())),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}];
let result = transpiler.transpile_dataframe_method(&df_expr, "select", &args);
assert!(result.is_ok());
assert!(result
.expect("operation should succeed in test")
.to_string()
.contains("lazy"));
}
#[test]
fn test_transpile_dataframe_method_rows() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "rows", &[]);
assert!(result.is_ok());
assert!(result
.expect("operation should succeed in test")
.to_string()
.contains("height"));
}
#[test]
fn test_transpile_dataframe_method_columns() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "columns", &[]);
assert!(result.is_ok());
assert!(result
.expect("operation should succeed in test")
.to_string()
.contains("width"));
}
#[test]
fn test_transpile_dataframe_method_mean() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "mean", &[]);
assert!(result.is_ok());
assert!(result
.expect("operation should succeed in test")
.to_string()
.contains("mean"));
}
#[test]
fn test_transpile_dataframe_method_head_no_args() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "head", &[]);
assert!(result.is_ok());
let output = result
.expect("operation should succeed in test")
.to_string();
assert!(output.contains("head"));
assert!(output.contains('5'));
}
#[test]
fn test_transpile_dataframe_method_tail_with_arg() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![make_literal_expr(10)];
let result = transpiler.transpile_dataframe_method(&df_expr, "tail", &args);
assert!(result.is_ok());
assert!(result
.expect("operation should succeed in test")
.to_string()
.contains("tail"));
}
#[test]
fn test_transpile_dataframe_method_groupby() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![Expr {
kind: ExprKind::Literal(Literal::String("group_col".to_string())),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}];
let result = transpiler.transpile_dataframe_method(&df_expr, "groupby", &args);
assert!(result.is_ok());
assert!(result
.expect("operation should succeed in test")
.to_string()
.contains("group_by"));
}
#[test]
fn test_transpile_dataframe_method_default() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "unknown_method", &[]);
assert!(result.is_ok());
assert!(result
.expect("operation should succeed in test")
.to_string()
.contains("unknown_method"));
}
#[test]
fn test_is_dataframe_expr_literal() {
let expr = Expr {
kind: ExprKind::DataFrame { columns: vec![] },
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_dataframe_expr(&expr));
}
#[test]
fn test_is_dataframe_expr_identifier_df() {
let expr = Expr {
kind: ExprKind::Identifier("df".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_dataframe_expr(&expr));
}
#[test]
fn test_is_dataframe_expr_method_call() {
let receiver = Box::new(Expr {
kind: ExprKind::Identifier("df".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
});
let expr = Expr {
kind: ExprKind::MethodCall {
receiver,
method: "select".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_dataframe_expr(&expr));
}
#[test]
fn test_is_dataframe_expr_qualified_name() {
let func = Box::new(Expr {
kind: ExprKind::QualifiedName {
module: "DataFrame".to_string(),
name: "new".to_string(),
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
});
let expr = Expr {
kind: ExprKind::Call { func, args: vec![] },
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_dataframe_expr(&expr));
}
#[test]
fn test_dataframe_join_outer() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let other_expr = make_literal_expr(1);
let op = DataFrameOp::Join {
other: Box::new(other_expr),
on: vec!["id".to_string()],
how: JoinType::Outer,
};
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("join"));
assert!(output.contains("Outer"));
}
#[test]
fn test_dataframe_aggregate_var() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let agg_ops = vec![AggregateOp::Var("variance_col".to_string())];
let op = DataFrameOp::Aggregate(agg_ops);
let result = transpiler
.transpile_dataframe_operation(&df_expr, &op)
.expect("operation should succeed in test");
let output = result.to_string();
assert!(output.contains("var"));
assert!(output.contains("variance_col"));
}
#[test]
fn test_is_dataframe_expr_false() {
let expr = Expr {
kind: ExprKind::Identifier("not_df".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(!Transpiler::is_dataframe_expr(&expr));
}
#[test]
fn test_transpile_builder_method_column_two_args() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![
Expr {
kind: ExprKind::Literal(Literal::String("col".to_string())),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
},
make_literal_expr(42),
];
let result = transpiler.transpile_dataframe_method(&df_expr, "column", &args);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("column"));
}
#[test]
fn test_transpile_builder_method_build() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "build", &[]);
assert!(result.is_ok());
}
#[test]
fn test_transpile_inspection_method_get_with_arg() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![Expr {
kind: ExprKind::Literal(Literal::String("col_name".to_string())),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}];
let result = transpiler.transpile_dataframe_method(&df_expr, "get", &args);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("column"));
}
#[test]
fn test_transpile_inspection_method_get_multiple_args() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![make_literal_expr(0), make_literal_expr(1)];
let result = transpiler.transpile_dataframe_method(&df_expr, "get", &args);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("get"));
}
#[test]
fn test_transpile_simple_operation_agg() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![make_literal_expr(0)];
let result = transpiler.transpile_dataframe_method(&df_expr, "agg", &args);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("agg"));
}
#[test]
fn test_transpile_simple_operation_join() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![make_literal_expr(0)];
let result = transpiler.transpile_dataframe_method(&df_expr, "join", &args);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("join"));
}
#[test]
fn test_transpile_statistical_method_std() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "std", &[]);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("std"));
}
#[test]
fn test_transpile_statistical_method_min() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "min", &[]);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("min"));
}
#[test]
fn test_transpile_statistical_method_max() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "max", &[]);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("max"));
}
#[test]
fn test_transpile_statistical_method_sum() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "sum", &[]);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("sum"));
}
#[test]
fn test_transpile_statistical_method_count() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let result = transpiler.transpile_dataframe_method(&df_expr, "count", &[]);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("count"));
}
#[test]
fn test_transpile_lazy_operation_filter() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![make_literal_expr(1)];
let result = transpiler.transpile_dataframe_method(&df_expr, "filter", &args);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("lazy"));
}
#[test]
fn test_transpile_lazy_operation_sort() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![Expr {
kind: ExprKind::Literal(Literal::String("col".to_string())),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}];
let result = transpiler.transpile_dataframe_method(&df_expr, "sort", &args);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("lazy"));
}
#[test]
fn test_transpile_groupby_alias() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let args = vec![Expr {
kind: ExprKind::Literal(Literal::String("group_col".to_string())),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}];
let result = transpiler.transpile_dataframe_method(&df_expr, "group_by", &args);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("group_by"));
}
#[test]
fn test_is_option_or_result_expr_some() {
let func = Box::new(Expr {
kind: ExprKind::Identifier("Some".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
});
let expr = Expr {
kind: ExprKind::Call {
func,
args: vec![make_literal_expr(42)],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_option_or_result_expr(&expr));
}
#[test]
fn test_is_option_or_result_expr_none() {
let expr = Expr {
kind: ExprKind::Identifier("None".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_option_or_result_expr(&expr));
}
#[test]
fn test_is_option_or_result_expr_unwrap_method() {
let receiver = Box::new(Expr {
kind: ExprKind::Identifier("opt".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
});
let expr = Expr {
kind: ExprKind::MethodCall {
receiver,
method: "unwrap".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_option_or_result_expr(&expr));
}
#[test]
fn test_is_option_or_result_expr_is_some_method() {
let receiver = Box::new(Expr {
kind: ExprKind::Identifier("opt".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
});
let expr = Expr {
kind: ExprKind::MethodCall {
receiver,
method: "is_some".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_option_or_result_expr(&expr));
}
#[test]
fn test_is_option_or_result_expr_non_option_method() {
let receiver = Box::new(Expr {
kind: ExprKind::Identifier("vec".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
});
let expr = Expr {
kind: ExprKind::MethodCall {
receiver,
method: "len".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(!Transpiler::is_option_or_result_expr(&expr));
}
#[test]
fn test_is_option_or_result_with_context_none() {
let transpiler = make_test_transpiler();
let expr = Expr {
kind: ExprKind::Identifier("None".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(transpiler.is_option_or_result_with_context(&expr));
}
#[test]
fn test_is_option_or_result_with_context_registered_option() {
let transpiler = make_test_transpiler();
transpiler.register_variable_type("my_opt", "Option<i32>");
let expr = Expr {
kind: ExprKind::Identifier("my_opt".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(transpiler.is_option_or_result_with_context(&expr));
}
#[test]
fn test_is_option_or_result_with_context_registered_result() {
let transpiler = make_test_transpiler();
transpiler.register_variable_type("my_result", "Result<i32, Error>");
let expr = Expr {
kind: ExprKind::Identifier("my_result".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(transpiler.is_option_or_result_with_context(&expr));
}
#[test]
fn test_is_option_or_result_with_context_not_registered() {
let transpiler = make_test_transpiler();
let expr = Expr {
kind: ExprKind::Identifier("unknown_var".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(!transpiler.is_option_or_result_with_context(&expr));
}
#[test]
fn test_register_variable_type() {
let transpiler = make_test_transpiler();
transpiler.register_variable_type("x", "i32");
transpiler.register_variable_type("y", "String");
let expr_x = Expr {
kind: ExprKind::Identifier("x".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(!transpiler.is_option_or_result_with_context(&expr_x));
}
#[test]
fn test_dataframe_single_column() {
let transpiler = make_test_transpiler();
let columns = vec![DataFrameColumn {
name: "single".to_string(),
values: vec![make_literal_expr(1)],
}];
let result = transpiler.transpile_dataframe(&columns);
assert!(result.is_ok());
assert!(result.unwrap().to_string().contains("single"));
}
#[test]
fn test_dataframe_many_columns() {
let transpiler = make_test_transpiler();
let columns: Vec<DataFrameColumn> = (0..5)
.map(|i| DataFrameColumn {
name: format!("col{}", i),
values: vec![make_literal_expr(i)],
})
.collect();
let result = transpiler.transpile_dataframe(&columns);
assert!(result.is_ok());
}
#[test]
fn test_dataframe_operation_multiple_select_columns() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let op = DataFrameOp::Select(vec![
"a".to_string(),
"b".to_string(),
"c".to_string(),
"d".to_string(),
]);
let result = transpiler.transpile_dataframe_operation(&df_expr, &op);
assert!(result.is_ok());
}
#[test]
fn test_dataframe_operation_multiple_groupby_columns() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let op = DataFrameOp::GroupBy(vec!["g1".to_string(), "g2".to_string()]);
let result = transpiler.transpile_dataframe_operation(&df_expr, &op);
assert!(result.is_ok());
}
#[test]
fn test_dataframe_operation_multiple_sort_columns() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let op = DataFrameOp::Sort(vec!["s1".to_string(), "s2".to_string(), "s3".to_string()]);
let result = transpiler.transpile_dataframe_operation(&df_expr, &op);
assert!(result.is_ok());
}
#[test]
fn test_dataframe_join_multiple_on_columns() {
let transpiler = make_test_transpiler();
let df_expr = make_literal_expr(0);
let other_expr = make_literal_expr(1);
let op = DataFrameOp::Join {
other: Box::new(other_expr),
on: vec!["k1".to_string(), "k2".to_string()],
how: JoinType::Inner,
};
let result = transpiler.transpile_dataframe_operation(&df_expr, &op);
assert!(result.is_ok());
}
#[test]
fn test_is_dataframe_expr_method_filter() {
let receiver = Box::new(Expr {
kind: ExprKind::Identifier("df".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
});
let expr = Expr {
kind: ExprKind::MethodCall {
receiver,
method: "filter".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_dataframe_expr(&expr));
}
#[test]
fn test_is_dataframe_expr_method_head() {
let receiver = Box::new(Expr {
kind: ExprKind::Identifier("df".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
});
let expr = Expr {
kind: ExprKind::MethodCall {
receiver,
method: "head".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_dataframe_expr(&expr));
}
#[test]
fn test_is_dataframe_expr_method_tail() {
let receiver = Box::new(Expr {
kind: ExprKind::Identifier("df".to_string()),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
});
let expr = Expr {
kind: ExprKind::MethodCall {
receiver,
method: "tail".to_string(),
args: vec![],
},
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
};
assert!(Transpiler::is_dataframe_expr(&expr));
}
}
#[cfg(test)]
mod property_tests_dataframe {
use super::*;
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
#[test]
fn test_transpile_dataframe_never_panics() {
let transpiler = super::Transpiler::new();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = transpiler.transpile_dataframe(&[]);
}));
assert!(
result.is_ok(),
"transpile_dataframe should not panic on empty input"
);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let bad_columns = vec![DataFrameColumn {
name: String::new(), values: vec![], }];
let _ = transpiler.transpile_dataframe(&bad_columns);
}));
assert!(
result.is_ok(),
"transpile_dataframe should handle malformed data gracefully"
);
}
#[test]
fn test_coverage_boost_dataframe() {
let transpiler = Transpiler::new();
let columns = vec![
DataFrameColumn {
name: "id".to_string(),
values: vec![
Expr {
kind: ExprKind::Literal(Literal::Integer(1, None)),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
},
Expr {
kind: ExprKind::Literal(Literal::Integer(2, None)),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
},
],
},
DataFrameColumn {
name: "name".to_string(),
values: vec![
Expr {
kind: ExprKind::Literal(Literal::String("Alice".to_string())),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
},
Expr {
kind: ExprKind::Literal(Literal::String("Bob".to_string())),
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
},
],
},
];
let result = transpiler.transpile_dataframe(&columns);
assert!(result.is_ok());
let empty_columns = vec![];
let result = transpiler.transpile_dataframe(&empty_columns);
assert!(result.is_ok());
}
}