use super::Transpiler;
use crate::frontend::ast::Expr;
use anyhow::Result;
use proc_macro2::TokenStream;
use quote::quote;
impl Transpiler {
pub fn try_transpile_result_call(
&self,
base_name: &str,
args: &[Expr],
) -> Result<Option<TokenStream>> {
use crate::frontend::ast::{ExprKind, Literal};
if base_name != "Ok" && base_name != "Err" && base_name != "Some" {
return Ok(None);
}
let arg_tokens: Result<Vec<_>> = args
.iter()
.map(|arg| {
let base_tokens = self.transpile_expr(arg)?;
match &arg.kind {
ExprKind::Literal(Literal::String(_)) => {
Ok(quote! { #base_tokens.to_string() })
}
_ => Ok(base_tokens),
}
})
.collect();
let arg_tokens = arg_tokens?;
let func_ident = proc_macro2::Ident::new(base_name, proc_macro2::Span::call_site());
Ok(Some(quote! { #func_ident(#(#arg_tokens),*) }))
}
pub fn transpile_regular_function_call(
&self,
func_tokens: &TokenStream,
args: &[Expr],
) -> Result<TokenStream> {
let func_name = func_tokens.to_string().trim().to_string();
let arg_tokens: Result<Vec<_>> = if let Some(signature) =
self.function_signatures.get(&func_name)
{
args.iter()
.enumerate()
.map(|(i, arg)| {
let mut base_tokens = self.transpile_expr(arg)?;
if let Some(expected_type) = signature.param_types.get(i) {
if self.in_loop_context.get()
&& matches!(&arg.kind, crate::frontend::ast::ExprKind::Identifier(_))
{
if expected_type == "String" {
return Ok(quote! { #base_tokens.to_string() });
}
base_tokens = quote! { #base_tokens.clone() };
}
self.apply_string_coercion(arg, &base_tokens, expected_type)
} else {
if self.in_loop_context.get()
&& matches!(&arg.kind, crate::frontend::ast::ExprKind::Identifier(_))
{
base_tokens = quote! { #base_tokens.clone() };
}
Ok(base_tokens)
}
})
.collect()
} else {
args.iter()
.map(|arg| {
let mut base_tokens = self.transpile_expr(arg)?;
if matches!(&arg.kind, crate::frontend::ast::ExprKind::Literal(
crate::frontend::ast::Literal::String(_)
)) {
return Ok(quote! { #base_tokens.to_string() });
}
if let crate::frontend::ast::ExprKind::List(elements) = &arg.kind {
if !elements.is_empty() {
return Ok(quote! { #base_tokens.to_vec() });
}
}
if self.in_loop_context.get()
&& matches!(&arg.kind, crate::frontend::ast::ExprKind::Identifier(_))
{
base_tokens = quote! { #base_tokens.clone() };
}
Ok(base_tokens)
})
.collect()
};
let arg_tokens = arg_tokens?;
Ok(quote! { #func_tokens(#(#arg_tokens),*) })
}
pub fn apply_string_coercion(
&self,
arg: &Expr,
tokens: &TokenStream,
expected_type: &str,
) -> Result<TokenStream> {
use crate::frontend::ast::{ExprKind, Literal};
match (&arg.kind, expected_type) {
(ExprKind::Literal(Literal::String(_)), "String") => Ok(quote! { #tokens.to_string() }),
(ExprKind::Literal(Literal::String(_)), "Any" | "Unknown") => {
Ok(quote! { #tokens.to_string() })
}
(ExprKind::List(elements), "Any" | "Unknown") if !elements.is_empty() => {
Ok(quote! { #tokens.to_vec() })
}
(ExprKind::List(_), expected) if expected.starts_with('[') && expected.contains(';') => {
Ok(tokens.clone())
}
(ExprKind::Literal(Literal::String(_)), expected) if expected.starts_with('&') => {
Ok(tokens.clone())
}
(ExprKind::Identifier(_), "String") => {
Ok(quote! { #tokens.to_string() })
}
_ => Ok(tokens.clone()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::ast::{ExprKind, Literal, Span};
fn make_expr(kind: ExprKind) -> Expr {
Expr {
kind,
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
fn string_expr(s: &str) -> Expr {
make_expr(ExprKind::Literal(Literal::String(s.to_string())))
}
fn int_expr(n: i64) -> Expr {
make_expr(ExprKind::Literal(Literal::Integer(n, None)))
}
fn ident_expr(name: &str) -> Expr {
make_expr(ExprKind::Identifier(name.to_string()))
}
#[test]
fn test_result_call_ok() {
let transpiler = Transpiler::new();
let args = vec![int_expr(42)];
let result = transpiler.try_transpile_result_call("Ok", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("Ok"));
assert!(tokens_str.contains("42"));
}
#[test]
fn test_result_call_ok_with_string() {
let transpiler = Transpiler::new();
let args = vec![string_expr("success")];
let result = transpiler.try_transpile_result_call("Ok", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("Ok"));
assert!(tokens_str.contains("to_string"));
}
#[test]
fn test_result_call_err() {
let transpiler = Transpiler::new();
let args = vec![string_expr("error message")];
let result = transpiler.try_transpile_result_call("Err", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("Err"));
assert!(tokens_str.contains("to_string"));
}
#[test]
fn test_result_call_some() {
let transpiler = Transpiler::new();
let args = vec![int_expr(100)];
let result = transpiler.try_transpile_result_call("Some", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("Some"));
}
#[test]
fn test_result_call_some_with_string() {
let transpiler = Transpiler::new();
let args = vec![string_expr("value")];
let result = transpiler.try_transpile_result_call("Some", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("Some"));
assert!(tokens_str.contains("to_string"));
}
#[test]
fn test_result_call_unknown() {
let transpiler = Transpiler::new();
let args = vec![int_expr(1)];
let result = transpiler.try_transpile_result_call("Unknown", &args);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
}
#[test]
fn test_result_call_ok_empty() {
let transpiler = Transpiler::new();
let args: Vec<Expr> = vec![];
let result = transpiler.try_transpile_result_call("Ok", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
let tokens_str = tokens.unwrap().to_string();
assert!(tokens_str.contains("Ok"));
}
#[test]
fn test_result_call_ok_multiple_args() {
let transpiler = Transpiler::new();
let args = vec![int_expr(1), int_expr(2)];
let result = transpiler.try_transpile_result_call("Ok", &args);
assert!(result.is_ok());
let tokens = result.unwrap();
assert!(tokens.is_some());
}
#[test]
fn test_regular_function_call_simple() {
let transpiler = Transpiler::new();
let func_tokens = quote! { my_func };
let args = vec![int_expr(42)];
let result = transpiler.transpile_regular_function_call(&func_tokens, &args);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("my_func"));
assert!(tokens_str.contains("42"));
}
#[test]
fn test_regular_function_call_with_string() {
let transpiler = Transpiler::new();
let func_tokens = quote! { greet };
let args = vec![string_expr("hello")];
let result = transpiler.transpile_regular_function_call(&func_tokens, &args);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("greet"));
assert!(tokens_str.contains("hello"));
}
#[test]
fn test_regular_function_call_no_args() {
let transpiler = Transpiler::new();
let func_tokens = quote! { get_value };
let args: Vec<Expr> = vec![];
let result = transpiler.transpile_regular_function_call(&func_tokens, &args);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("get_value"));
}
#[test]
fn test_regular_function_call_multiple_args() {
let transpiler = Transpiler::new();
let func_tokens = quote! { add };
let args = vec![int_expr(1), int_expr(2), int_expr(3)];
let result = transpiler.transpile_regular_function_call(&func_tokens, &args);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("add"));
}
#[test]
fn test_regular_function_call_with_identifier() {
let transpiler = Transpiler::new();
let func_tokens = quote! { process };
let args = vec![ident_expr("data")];
let result = transpiler.transpile_regular_function_call(&func_tokens, &args);
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("process"));
assert!(tokens_str.contains("data"));
}
#[test]
fn test_apply_string_coercion_string_literal_to_string() {
let transpiler = Transpiler::new();
let arg = string_expr("hello");
let tokens = quote! { "hello" };
let result = transpiler.apply_string_coercion(&arg, &tokens, "String");
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("to_string"));
}
#[test]
fn test_apply_string_coercion_string_literal_to_str_ref() {
let transpiler = Transpiler::new();
let arg = string_expr("hello");
let tokens = quote! { "hello" };
let result = transpiler.apply_string_coercion(&arg, &tokens, "&str");
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(!tokens_str.contains("to_string"));
}
#[test]
fn test_apply_string_coercion_identifier_to_string() {
let transpiler = Transpiler::new();
let arg = ident_expr("name");
let tokens = quote! { name };
let result = transpiler.apply_string_coercion(&arg, &tokens, "String");
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(tokens_str.contains("to_string"));
}
#[test]
fn test_apply_string_coercion_int_no_change() {
let transpiler = Transpiler::new();
let arg = int_expr(42);
let tokens = quote! { 42 };
let result = transpiler.apply_string_coercion(&arg, &tokens, "i32");
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(!tokens_str.contains("to_string"));
}
#[test]
fn test_apply_string_coercion_identifier_to_int() {
let transpiler = Transpiler::new();
let arg = ident_expr("x");
let tokens = quote! { x };
let result = transpiler.apply_string_coercion(&arg, &tokens, "i32");
assert!(result.is_ok());
let tokens_str = result.unwrap().to_string();
assert!(!tokens_str.contains("to_string"));
}
}