#![allow(clippy::missing_errors_doc)]
#![allow(clippy::wildcard_imports)]
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::only_used_in_recursion)]
use super::*;
use crate::frontend::ast::{
ClassMethod, Constructor, EnumVariant, ImplMethod, StructField, TraitMethod, Type, TypeKind,
};
use anyhow::{bail, Result};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Lifetime;
impl Transpiler {
pub fn transpile_type(&self, ty: &Type) -> Result<TokenStream> {
use crate::frontend::ast::TypeKind;
match &ty.kind {
TypeKind::Named(name) => self.transpile_named_type(name),
TypeKind::Generic { base, params } => self.transpile_generic_type(base, params),
TypeKind::Optional(inner) => self.transpile_optional_type(inner),
TypeKind::List(elem_type) => self.transpile_list_type(elem_type),
TypeKind::Array { elem_type, size } => self.transpile_array_type(elem_type, *size),
TypeKind::Tuple(types) => self.transpile_tuple_type(types),
TypeKind::Function { params, ret } => self.transpile_function_type(params, ret),
TypeKind::DataFrame { .. } => Ok(quote! { polars::prelude::DataFrame }),
TypeKind::Series { .. } => Ok(quote! { polars::prelude::Series }),
TypeKind::Reference {
is_mut,
lifetime,
inner,
} => self.transpile_reference_type(*is_mut, lifetime.as_deref(), inner),
TypeKind::Refined { base, .. } => self.transpile_type(base),
}
}
pub(crate) fn transpile_named_type(&self, name: &str) -> Result<TokenStream> {
let rust_type = match name {
"int" => quote! { i64 },
"float" => quote! { f64 },
"bool" => quote! { bool },
"str" => quote! { &str }, "string" | "String" => quote! { String },
"char" => quote! { char },
"()" => quote! { () }, "_" | "Any" => quote! { _ }, "Object" => quote! { std::collections::BTreeMap<String, String> }, _ => {
if name.contains("::") {
let segments: Vec<_> = name
.split("::")
.map(|seg| format_ident!("{}", seg))
.collect();
quote! { #(#segments)::* }
} else {
let type_ident = format_ident!("{}", name);
quote! { #type_ident }
}
}
};
Ok(rust_type)
}
pub(crate) fn transpile_generic_type(
&self,
base: &str,
params: &[Type],
) -> Result<TokenStream> {
use crate::frontend::ast::TypeKind;
if base == "Option" && params.len() == 1 {
if let TypeKind::Named(inner_name) = ¶ms[0].kind {
let current_struct = self.current_struct_name.borrow();
let is_recursive = current_struct.as_ref().is_some_and(|c| c == inner_name);
if is_recursive {
if let Some(struct_name) = current_struct.as_ref() {
self.auto_boxed_fields.borrow_mut().insert(
(struct_name.clone(), inner_name.clone()),
inner_name.clone(),
);
}
drop(current_struct); let inner_ident = format_ident!("{}", inner_name);
return Ok(quote! { Option<Box<#inner_ident>> });
}
}
}
let base_ident = format_ident!("{}", base);
let param_tokens: Result<Vec<_>> = params.iter().map(|p| self.transpile_type(p)).collect();
let param_tokens = param_tokens?;
Ok(quote! { #base_ident<#(#param_tokens),*> })
}
pub(crate) fn transpile_optional_type(&self, inner: &Type) -> Result<TokenStream> {
use crate::frontend::ast::TypeKind;
let is_recursive = if let TypeKind::Named(name) = &inner.kind {
self.current_struct_name
.borrow()
.as_ref()
.is_some_and(|current| current == name)
} else {
false
};
let inner_tokens = self.transpile_type(inner)?;
if is_recursive {
Ok(quote! { Option<Box<#inner_tokens>> })
} else {
Ok(quote! { Option<#inner_tokens> })
}
}
pub(crate) fn transpile_list_type(&self, elem_type: &Type) -> Result<TokenStream> {
let elem_tokens = self.transpile_type(elem_type)?;
Ok(quote! { Vec<#elem_tokens> })
}
pub(crate) fn transpile_array_type(
&self,
elem_type: &Type,
size: usize,
) -> Result<TokenStream> {
let elem_tokens = self.transpile_type(elem_type)?;
let size_lit = proc_macro2::Literal::usize_unsuffixed(size);
Ok(quote! { [#elem_tokens; #size_lit] })
}
pub(crate) fn transpile_tuple_type(&self, types: &[Type]) -> Result<TokenStream> {
let type_tokens: Result<Vec<_>> = types.iter().map(|t| self.transpile_type(t)).collect();
let type_tokens = type_tokens?;
Ok(quote! { (#(#type_tokens),*) })
}
pub(crate) fn transpile_function_type(
&self,
params: &[Type],
ret: &Type,
) -> Result<TokenStream> {
let param_tokens: Result<Vec<_>> = params.iter().map(|p| self.transpile_type(p)).collect();
let param_tokens = param_tokens?;
let ret_tokens = self.transpile_type(ret)?;
Ok(quote! { fn(#(#param_tokens),*) -> #ret_tokens })
}
pub(crate) fn transpile_reference_type(
&self,
is_mut: bool,
lifetime: Option<&str>,
inner: &Type,
) -> Result<TokenStream> {
use crate::frontend::ast::TypeKind;
let lifetime_token = if let Some(lt) = lifetime {
let lifetime = Lifetime::new(lt, proc_macro2::Span::call_site());
quote! { #lifetime }
} else {
quote! {}
};
if let TypeKind::Named(name) = &inner.kind {
if name == "str" {
return if is_mut {
Ok(quote! { &#lifetime_token mut str })
} else {
Ok(quote! { &#lifetime_token str })
};
}
}
let inner_tokens = self.transpile_type(inner)?;
if is_mut {
Ok(quote! { &#lifetime_token mut #inner_tokens })
} else {
Ok(quote! { &#lifetime_token #inner_tokens })
}
}
pub fn transpile_tuple_struct(
&self,
name: &str,
type_params: &[String],
fields: &[Type],
derives: &[String],
is_pub: bool,
) -> Result<TokenStream> {
let struct_name = format_ident!("{}", name);
let type_param_tokens: Vec<TokenStream> = type_params
.iter()
.map(|p| Self::parse_type_param_to_tokens(p))
.collect();
let field_tokens: Vec<TokenStream> = fields
.iter()
.map(|ty| self.transpile_type(ty).unwrap_or_else(|_| quote! { _ }))
.collect();
let visibility = if is_pub {
quote! { pub }
} else {
quote! {}
};
let mut extended_derives = derives.to_vec();
if !extended_derives.contains(&"Clone".to_string()) {
extended_derives.push("Clone".to_string());
}
let derive_attrs = self.generate_derive_attributes(&extended_derives);
let struct_def = if type_params.is_empty() {
quote! {
#derive_attrs
#visibility struct #struct_name(#(pub #field_tokens),*);
}
} else {
quote! {
#derive_attrs
#visibility struct #struct_name<#(#type_param_tokens),*>(#(pub #field_tokens),*);
}
};
Ok(struct_def)
}
pub(crate) fn has_reference_fields(&self, fields: &[StructField]) -> bool {
use crate::frontend::ast::TypeKind;
fields
.iter()
.any(|field| matches!(field.ty.kind, TypeKind::Reference { .. }))
}
pub(crate) fn has_lifetime_params(&self, type_params: &[String]) -> bool {
type_params.iter().any(|p| p.starts_with('\''))
}
pub(crate) fn parse_type_param_to_tokens(p: &str) -> TokenStream {
if p.starts_with('\'') {
let lifetime = Lifetime::new(p, proc_macro2::Span::call_site());
quote! { #lifetime }
} else if p.contains(':') {
syn::parse_str::<syn::TypeParam>(p).map_or_else(
|_| {
let name = p.split(':').next().unwrap_or(p).trim();
let ident = format_ident!("{}", name);
quote! { #ident }
},
|tp| quote! { #tp },
)
} else {
let ident = format_ident!("{}", p);
quote! { #ident }
}
}
pub(crate) fn transpile_struct_field_type_with_lifetime(
&self,
ty: &Type,
lifetime: &str,
) -> Result<TokenStream> {
use crate::frontend::ast::TypeKind;
match &ty.kind {
TypeKind::Reference { is_mut, inner, .. } => {
self.transpile_reference_type(*is_mut, Some(lifetime), inner)
}
_ => {
self.transpile_type(ty)
}
}
}
pub fn transpile_struct(
&self,
name: &str,
type_params: &[String],
fields: &[StructField],
derives: &[String],
is_pub: bool,
) -> Result<TokenStream> {
self.transpile_struct_with_methods(name, type_params, fields, &[], derives, is_pub)
}
pub fn transpile_struct_with_methods(
&self,
name: &str,
type_params: &[String],
fields: &[StructField],
methods: &[ClassMethod],
derives: &[String],
is_pub: bool,
) -> Result<TokenStream> {
*self.current_struct_name.borrow_mut() = Some(name.to_string());
let struct_name = format_ident!("{}", name);
let needs_lifetime =
self.has_reference_fields(fields) && !self.has_lifetime_params(type_params);
let effective_type_params: Vec<String> = if needs_lifetime {
let mut params = vec!["'a".to_string()];
params.extend_from_slice(type_params);
params
} else {
type_params.to_vec()
};
let type_param_tokens: Vec<TokenStream> = effective_type_params
.iter()
.map(|p| Self::parse_type_param_to_tokens(p))
.collect();
let field_tokens: Vec<TokenStream> = fields
.iter()
.map(|field| {
let field_name = format_ident!("{}", field.name);
let field_type = if needs_lifetime {
self.transpile_struct_field_type_with_lifetime(&field.ty, "'a")
.unwrap_or_else(|_| quote! { _ })
} else {
self.transpile_type(&field.ty)
.unwrap_or_else(|_| quote! { _ })
};
use crate::frontend::ast::Visibility;
match &field.visibility {
Visibility::Public => quote! { pub #field_name: #field_type },
Visibility::PubCrate => quote! { pub(crate) #field_name: #field_type },
Visibility::PubSuper => quote! { pub(super) #field_name: #field_type },
Visibility::Private | Visibility::Protected => {
quote! { #field_name: #field_type }
}
}
})
.collect();
let visibility = if is_pub {
quote! { pub }
} else {
quote! {}
};
let mut extended_derives = derives.to_vec();
if !extended_derives.contains(&"Clone".to_string()) {
extended_derives.push("Clone".to_string());
}
let derive_attrs = self.generate_derive_attributes(&extended_derives);
{
use crate::frontend::ast::TypeKind;
let mut field_types = self.struct_field_types.borrow_mut();
for field in fields {
let type_name = match &field.ty.kind {
TypeKind::Named(n) => n.clone(),
_ => format!("{:?}", field.ty.kind), };
field_types.insert((name.to_string(), field.name.clone()), type_name);
}
}
let struct_def = if effective_type_params.is_empty() {
quote! {
#derive_attrs
#visibility struct #struct_name {
#(#field_tokens,)*
}
}
} else {
quote! {
#derive_attrs
#visibility struct #struct_name<#(#type_param_tokens),*> {
#(#field_tokens,)*
}
}
};
let has_defaults = fields.iter().any(|f| f.default_value.is_some());
if has_defaults {
use crate::frontend::ast::{ExprKind, Literal};
let default_field_tokens: Result<Vec<_>> = fields
.iter()
.map(|field| -> Result<TokenStream> {
let field_name = format_ident!("{}", field.name);
if let Some(ref default_expr) = field.default_value {
let default_value = self.transpile_expr(default_expr)?;
let is_string_field =
matches!(&field.ty.kind, TypeKind::Named(n) if n == "String");
let is_string_literal =
matches!(&default_expr.kind, ExprKind::Literal(Literal::String(_)));
if is_string_field && is_string_literal {
Ok(quote! { #field_name: #default_value.to_string() })
} else {
Ok(quote! { #field_name: #default_value })
}
} else {
Ok(quote! { #field_name: Default::default() })
}
})
.collect();
let default_field_tokens = default_field_tokens?;
let default_impl = if type_params.is_empty() {
quote! {
impl Default for #struct_name {
fn default() -> Self {
Self {
#(#default_field_tokens,)*
}
}
}
}
} else {
let where_clause_tokens: Vec<_> = type_params
.iter()
.map(|p| {
let param_ident = format_ident!("{}", p);
quote! { #param_ident: Default }
})
.collect();
quote! {
impl<#(#type_param_tokens),*> Default for #struct_name<#(#type_param_tokens),*>
where
#(#where_clause_tokens),*
{
fn default() -> Self {
Self {
#(#default_field_tokens,)*
}
}
}
}
};
if methods.is_empty() {
Ok(quote! {
#struct_def
#default_impl
})
} else {
let method_tokens = self.transpile_class_methods(methods)?;
let type_param_tokens = self.generate_class_type_param_tokens(type_params);
let impl_block = if type_param_tokens.is_empty() {
quote! {
impl #struct_name {
#(#method_tokens)*
}
}
} else {
quote! {
impl<#(#type_param_tokens),*> #struct_name<#(#type_param_tokens),*> {
#(#method_tokens)*
}
}
};
Ok(quote! {
#struct_def
#default_impl
#impl_block
})
}
} else {
if methods.is_empty() {
Ok(struct_def)
} else {
let method_tokens = self.transpile_class_methods(methods)?;
let type_param_tokens = self.generate_class_type_param_tokens(type_params);
let impl_block = if type_param_tokens.is_empty() {
quote! {
impl #struct_name {
#(#method_tokens)*
}
}
} else {
quote! {
impl<#(#type_param_tokens),*> #struct_name<#(#type_param_tokens),*> {
#(#method_tokens)*
}
}
};
Ok(quote! {
#struct_def
#impl_block
})
}
}
}
pub fn transpile_class(
&self,
name: &str,
type_params: &[String],
_traits: &[String],
fields: &[StructField],
constructors: &[Constructor],
methods: &[ClassMethod],
constants: &[crate::frontend::ast::ClassConstant],
derives: &[String],
is_pub: bool,
) -> Result<TokenStream> {
let struct_tokens = self.transpile_struct(name, type_params, fields, derives, is_pub)?;
let type_param_tokens = self.generate_class_type_param_tokens(type_params);
let struct_name = format_ident!("{}", name);
let constructor_tokens = self.transpile_constructors(constructors)?;
let method_tokens = self.transpile_class_methods(methods)?;
let constant_tokens = self.transpile_class_constants(constants)?;
let impl_tokens = self.generate_impl_block(
&struct_name,
&type_param_tokens,
&constant_tokens,
&constructor_tokens,
&method_tokens,
);
let default_impl = self.generate_default_impl(fields, &struct_name, &type_param_tokens)?;
Ok(quote! {
#struct_tokens
#impl_tokens
#default_impl
})
}
pub(crate) fn generate_derive_attributes(&self, derives: &[String]) -> TokenStream {
if derives.is_empty() {
quote! {}
} else {
let derive_idents: Vec<_> = derives.iter().map(|d| format_ident!("{}", d)).collect();
quote! { #[derive(#(#derive_idents),*)] }
}
}
pub(crate) fn generate_class_type_param_tokens(
&self,
type_params: &[String],
) -> Vec<TokenStream> {
type_params
.iter()
.map(|p| {
if p.starts_with('\'') {
let lifetime = Lifetime::new(p, proc_macro2::Span::call_site());
quote! { #lifetime }
} else {
let ident = format_ident!("{}", p);
quote! { #ident }
}
})
.collect()
}
fn transpile_constructor_body(&self, body: &Expr) -> Result<TokenStream> {
use crate::frontend::ast::ExprKind;
if let ExprKind::Block(exprs) = &body.kind {
let mut field_inits: Vec<(String, TokenStream)> = Vec::new();
for expr in exprs {
if let ExprKind::Assign { target, value } = &expr.kind {
if let ExprKind::FieldAccess { object, field } = &target.kind {
if let ExprKind::Identifier(name) = &object.kind {
if name == "self" {
let value_tokens = self.transpile_expr(value)?;
field_inits.push((field.clone(), value_tokens));
continue;
}
}
}
}
return self.transpile_expr(body);
}
if !field_inits.is_empty() {
let fields: Vec<TokenStream> = field_inits
.into_iter()
.map(|(name, value)| {
let field_ident = format_ident!("{}", name);
quote! { #field_ident: #value }
})
.collect();
return Ok(quote! { Self { #(#fields),* } });
}
}
if let ExprKind::Assign { target, value } = &body.kind {
if let ExprKind::FieldAccess { object, field } = &target.kind {
if let ExprKind::Identifier(name) = &object.kind {
if name == "self" {
let field_ident = format_ident!("{}", field);
let value_tokens = self.transpile_expr(value)?;
return Ok(quote! { Self { #field_ident: #value_tokens } });
}
}
}
}
self.transpile_expr(body)
}
pub(crate) fn transpile_constructors(
&self,
constructors: &[Constructor],
) -> Result<Vec<TokenStream>> {
constructors
.iter()
.map(|ctor| {
let params = self.transpile_params(&ctor.params)?;
let body = self.transpile_constructor_body(&ctor.body)?;
let visibility = if ctor.is_pub {
quote! { pub }
} else {
quote! {}
};
let method_name = ctor
.name
.as_ref()
.map_or_else(|| format_ident!("new"), |n| format_ident!("{}", n));
let return_type = if let Some(ref ret_ty) = ctor.return_type {
let ret_tokens = self.transpile_type(ret_ty)?;
quote! { -> #ret_tokens }
} else {
quote! { -> Self }
};
Ok(quote! {
#visibility fn #method_name(#(#params),*) #return_type {
#body
}
})
})
.collect()
}
pub(crate) fn transpile_class_methods(
&self,
methods: &[ClassMethod],
) -> Result<Vec<TokenStream>> {
methods
.iter()
.map(|method| {
let method_name = format_ident!("{}", method.name);
let params = self.transpile_params(&method.params)?;
let return_type = if let Some(ref ret_ty) = method.return_type {
let ret_tokens = self.transpile_type(ret_ty)?;
quote! { -> #ret_tokens }
} else {
quote! {}
};
let body = self.transpile_expr(&method.body)?;
let visibility = if method.is_pub {
quote! { pub }
} else {
quote! {}
};
Ok(quote! {
#visibility fn #method_name(#(#params),*) #return_type {
#body
}
})
})
.collect()
}
pub(crate) fn transpile_class_constants(
&self,
constants: &[crate::frontend::ast::ClassConstant],
) -> Result<Vec<TokenStream>> {
constants
.iter()
.map(|constant| {
let const_name = format_ident!("{}", constant.name);
let const_type = self.transpile_type(&constant.ty)?;
let const_value = self.transpile_expr(&constant.value)?;
let visibility = if constant.is_pub {
quote! { pub }
} else {
quote! {}
};
Ok(quote! {
#visibility const #const_name: #const_type = #const_value;
})
})
.collect()
}
pub(crate) fn generate_impl_block(
&self,
struct_name: &proc_macro2::Ident,
type_param_tokens: &[TokenStream],
constant_tokens: &[TokenStream],
constructor_tokens: &[TokenStream],
method_tokens: &[TokenStream],
) -> TokenStream {
if type_param_tokens.is_empty() {
quote! {
impl #struct_name {
#(#constant_tokens)*
#(#constructor_tokens)*
#(#method_tokens)*
}
}
} else {
quote! {
impl<#(#type_param_tokens),*> #struct_name<#(#type_param_tokens),*> {
#(#constant_tokens)*
#(#constructor_tokens)*
#(#method_tokens)*
}
}
}
}
pub(crate) fn generate_default_impl(
&self,
fields: &[StructField],
struct_name: &proc_macro2::Ident,
type_param_tokens: &[TokenStream],
) -> Result<TokenStream> {
let has_defaults = fields.iter().any(|f| f.default_value.is_some());
if !has_defaults {
return Ok(quote! {});
}
let default_field_tokens: Result<Vec<_>> = fields
.iter()
.map(|field| {
let field_name = format_ident!("{}", field.name);
if let Some(ref default_expr) = field.default_value {
let default_value = self.transpile_expr(default_expr)?;
Ok(quote! { #field_name: #default_value })
} else {
Ok(quote! { #field_name: Default::default() })
}
})
.collect();
let default_field_tokens = default_field_tokens?;
Ok(if type_param_tokens.is_empty() {
quote! {
impl Default for #struct_name {
fn default() -> Self {
Self {
#(#default_field_tokens,)*
}
}
}
}
} else {
quote! {
impl<#(#type_param_tokens),*> Default for #struct_name<#(#type_param_tokens),*> {
fn default() -> Self {
Self {
#(#default_field_tokens,)*
}
}
}
}
})
}
pub(crate) fn transpile_params(
&self,
params: &[crate::frontend::ast::Param],
) -> Result<Vec<TokenStream>> {
params
.iter()
.map(|param| -> Result<TokenStream> {
let param_name = param.name();
if param_name == "self" {
use crate::frontend::ast::TypeKind;
match ¶m.ty.kind {
TypeKind::Reference { is_mut: true, .. } => Ok(quote! { &mut self }),
TypeKind::Reference { is_mut: false, .. } => Ok(quote! { &self }),
_ => {
if param.is_mutable {
Ok(quote! { mut self })
} else {
Ok(quote! { self })
}
}
}
} else {
let param_ident = format_ident!("{}", param_name);
let type_tokens = self.transpile_type(¶m.ty)?;
if param.is_mutable {
Ok(quote! { mut #param_ident: #type_tokens })
} else {
Ok(quote! { #param_ident: #type_tokens })
}
}
})
.collect()
}
pub fn transpile_enum(
&self,
name: &str,
type_params: &[String],
variants: &[EnumVariant],
is_pub: bool,
) -> Result<TokenStream> {
let enum_name = format_ident!("{}", name);
let type_param_tokens: Vec<_> = type_params
.iter()
.map(|p| Self::parse_type_param_to_tokens(p))
.collect();
let has_discriminants = variants.iter().any(|v| v.discriminant.is_some());
let variant_tokens: Vec<TokenStream> = variants
.iter()
.map(|variant| {
use crate::frontend::ast::EnumVariantKind;
let variant_name = format_ident!("{}", variant.name);
match &variant.kind {
EnumVariantKind::Tuple(fields) => {
let field_types: Vec<TokenStream> = fields
.iter()
.map(|ty| self.transpile_type(ty).unwrap_or_else(|_| quote! { _ }))
.collect();
quote! { #variant_name(#(#field_types),*) }
}
EnumVariantKind::Struct(fields) => {
let field_defs: Vec<TokenStream> = fields
.iter()
.map(|field| {
let field_name = format_ident!("{}", field.name);
let field_type = self
.transpile_type(&field.ty)
.unwrap_or_else(|_| quote! { _ });
quote! { #field_name: #field_type }
})
.collect();
quote! { #variant_name { #(#field_defs),* } }
}
EnumVariantKind::Unit => {
if let Some(disc_value) = variant.discriminant {
let disc_literal =
proc_macro2::Literal::i32_unsuffixed(disc_value as i32);
quote! { #variant_name = #disc_literal }
} else {
quote! { #variant_name }
}
}
}
})
.collect();
let visibility = if is_pub {
quote! { pub }
} else {
quote! {}
};
let derive_attr = quote! { #[derive(Debug, Clone, PartialEq)] };
let repr_attr = if has_discriminants {
quote! { #[repr(i32)] }
} else {
quote! {}
};
if type_params.is_empty() {
Ok(quote! {
#derive_attr
#repr_attr
#visibility enum #enum_name {
#(#variant_tokens,)*
}
})
} else {
Ok(quote! {
#derive_attr
#repr_attr
#visibility enum #enum_name<#(#type_param_tokens),*> {
#(#variant_tokens,)*
}
})
}
}
pub fn transpile_trait(
&self,
name: &str,
type_params: &[String],
associated_types: &[String],
methods: &[TraitMethod],
is_pub: bool,
) -> Result<TokenStream> {
let trait_name = format_ident!("{}", name);
let associated_type_tokens: Vec<TokenStream> = associated_types
.iter()
.map(|type_name| {
let type_ident = format_ident!("{}", type_name);
quote! { type #type_ident; }
})
.collect();
let method_tokens: Result<Vec<_>> = methods
.iter()
.map(|method| {
let method_name = format_ident!("{}", method.name);
let self_is_mutated = method.body.as_ref().is_some_and(|body| {
crate::backend::transpiler::mutation_detection::is_variable_mutated(
"self", body,
)
});
let param_tokens: Vec<TokenStream> = method
.params
.iter()
.enumerate()
.map(|(i, param)| {
if i == 0 && (param.name() == "self" || param.name() == "&self") {
if let TypeKind::Reference { is_mut, .. } = ¶m.ty.kind {
if *is_mut {
quote! { &mut self }
} else {
quote! { &self }
}
} else if param.name().starts_with('&') {
quote! { &self }
} else {
if self_is_mutated {
quote! { &mut self }
} else {
quote! { &self }
}
}
} else {
let param_name = format_ident!("{}", param.name());
let type_tokens = self
.transpile_type(¶m.ty)
.unwrap_or_else(|_| quote! { _ });
quote! { #param_name: #type_tokens }
}
})
.collect();
let return_type_tokens = if let Some(ref ty) = method.return_type {
let ty_tokens = self.transpile_type(ty)?;
quote! { -> #ty_tokens }
} else {
quote! {}
};
if let Some(ref body) = method.body {
let body_tokens = self.transpile_expr(body)?;
Ok(quote! {
fn #method_name(#(#param_tokens),*) #return_type_tokens {
#body_tokens
}
})
} else {
Ok(quote! {
fn #method_name(#(#param_tokens),*) #return_type_tokens;
})
}
})
.collect();
let method_tokens = method_tokens?;
let type_param_tokens: Vec<_> = type_params
.iter()
.map(|p| Self::parse_type_param_to_tokens(p))
.collect();
let visibility = if is_pub {
quote! { pub }
} else {
quote! {}
};
if type_params.is_empty() {
Ok(quote! {
#visibility trait #trait_name {
#(#associated_type_tokens)*
#(#method_tokens)*
}
})
} else {
Ok(quote! {
#visibility trait #trait_name<#(#type_param_tokens),*> {
#(#associated_type_tokens)*
#(#method_tokens)*
}
})
}
}
pub fn transpile_impl(
&self,
for_type: &str,
type_params: &[String],
trait_name: Option<&str>,
methods: &[ImplMethod],
_is_pub: bool,
) -> Result<TokenStream> {
let base_type = for_type.split('<').next().unwrap_or(for_type).trim();
let type_ident = format_ident!("{}", base_type);
let method_tokens: Result<Vec<_>> = methods
.iter()
.map(|method| {
let method_name = format_ident!("{}", method.name);
let self_is_mutated =
crate::backend::transpiler::mutation_detection::is_variable_mutated(
"self",
&method.body,
);
let param_tokens: Vec<TokenStream> = method
.params
.iter()
.map(|param| {
let name = param.name();
if name == "self" {
if let TypeKind::Reference { is_mut, .. } = ¶m.ty.kind {
if *is_mut {
quote! { &mut self }
} else {
quote! { &self }
}
} else {
if self_is_mutated {
quote! { &mut self }
} else {
quote! { &self }
}
}
} else {
let param_name = format_ident!("{}", param.name());
let type_tokens = self
.transpile_type(¶m.ty)
.unwrap_or_else(|_| quote! { _ });
quote! { #param_name: #type_tokens }
}
})
.collect();
let return_type_tokens = if let Some(ref ty) = method.return_type {
let ty_tokens = self.transpile_type(ty)?;
quote! { -> #ty_tokens }
} else {
quote! {}
};
let needs_auto_clone = !self_is_mutated
&& method
.return_type
.as_ref()
.is_some_and(Self::is_owned_type)
&& Self::body_returns_self_field(&method.body);
let body_tokens = if needs_auto_clone {
self.transpile_body_with_auto_clone(&method.body)?
} else {
self.transpile_expr(&method.body)?
};
let visibility = if method.is_pub {
quote! { pub }
} else {
quote! {}
};
Ok(quote! {
#visibility fn #method_name(#(#param_tokens),*) #return_type_tokens {
#body_tokens
}
})
})
.collect();
let method_tokens = method_tokens?;
let type_param_tokens: Vec<_> = type_params
.iter()
.map(|p| Self::parse_type_param_to_tokens(p))
.collect();
if let Some(trait_name) = trait_name {
let trait_ident = format_ident!("{}", trait_name);
if type_params.is_empty() {
Ok(quote! {
impl #trait_ident for #type_ident {
#(#method_tokens)*
}
})
} else {
Ok(quote! {
impl<#(#type_param_tokens),*> #trait_ident for #type_ident<#(#type_param_tokens),*> {
#(#method_tokens)*
}
})
}
} else {
if type_params.is_empty() {
Ok(quote! {
impl #type_ident {
#(#method_tokens)*
}
})
} else {
Ok(quote! {
impl<#(#type_param_tokens),*> #type_ident<#(#type_param_tokens),*> {
#(#method_tokens)*
}
})
}
}
}
pub fn transpile_property_test(&self, expr: &Expr, _attr: &Attribute) -> Result<TokenStream> {
if let ExprKind::Function {
name, params, body, ..
} = &expr.kind
{
let fn_name = format_ident!("{}", name);
let param_tokens: Vec<TokenStream> = params
.iter()
.map(|p| {
let param_name = format_ident!("{}", p.name());
let type_tokens = self
.transpile_type(&p.ty)
.unwrap_or_else(|_| quote! { i32 });
quote! { #param_name: #type_tokens }
})
.collect();
let body_tokens = self.transpile_expr(body)?;
Ok(quote! {
#[cfg(test)]
mod #fn_name {
use super::*;
proptest! {
#[test]
fn #fn_name(#(#param_tokens),*) {
#body_tokens
}
}
}
})
} else {
bail!("Property test attribute can only be applied to functions");
}
}
pub fn transpile_extend(
&self,
target_type: &str,
methods: &[ImplMethod],
) -> Result<TokenStream> {
let target_ident = format_ident!("{}", target_type);
let trait_name = format_ident!("{}Ext", target_type); let trait_method_tokens: Result<Vec<_>> = methods
.iter()
.map(|method| {
let method_name = format_ident!("{}", method.name);
let param_tokens: Vec<TokenStream> = method
.params
.iter()
.map(|param| {
let name = param.name();
if name == "self" {
if let TypeKind::Reference { is_mut, .. } = ¶m.ty.kind {
if *is_mut {
quote! { &mut self }
} else {
quote! { &self }
}
} else {
quote! { self }
}
} else {
let param_name = format_ident!("{}", param.name());
let type_tokens = self
.transpile_type(¶m.ty)
.unwrap_or_else(|_| quote! { _ });
quote! { #param_name: #type_tokens }
}
})
.collect();
let return_type_tokens = if let Some(ref ty) = method.return_type {
let ty_tokens = self.transpile_type(ty)?;
quote! { -> #ty_tokens }
} else {
quote! {}
};
Ok(quote! {
fn #method_name(#(#param_tokens),*) #return_type_tokens;
})
})
.collect();
let trait_method_tokens = trait_method_tokens?;
let impl_method_tokens: Result<Vec<_>> = methods
.iter()
.map(|method| {
let method_name = format_ident!("{}", method.name);
let param_tokens: Vec<TokenStream> = method
.params
.iter()
.enumerate()
.map(|(i, param)| {
if i == 0 && (param.name() == "self" || param.name() == "&self") {
if param.name().starts_with('&') {
quote! { &self }
} else {
quote! { self }
}
} else {
let param_name = format_ident!("{}", param.name());
let type_tokens = self
.transpile_type(¶m.ty)
.unwrap_or_else(|_| quote! { _ });
quote! { #param_name: #type_tokens }
}
})
.collect();
let return_type_tokens = if let Some(ref ty) = method.return_type {
let ty_tokens = self.transpile_type(ty)?;
quote! { -> #ty_tokens }
} else {
quote! {}
};
let body_tokens = self.transpile_expr(&method.body)?;
Ok(quote! {
fn #method_name(#(#param_tokens),*) #return_type_tokens {
#body_tokens
}
})
})
.collect();
let impl_method_tokens = impl_method_tokens?;
Ok(quote! {
trait #trait_name {
#(#trait_method_tokens)*
}
impl #trait_name for #target_ident {
#(#impl_method_tokens)*
}
})
}
fn is_owned_type(ty: &Type) -> bool {
matches!(&ty.kind, TypeKind::Named(name) if name == "String" || name == "Vec" || name == "HashMap")
}
fn body_returns_self_field(body: &Expr) -> bool {
match &body.kind {
ExprKind::FieldAccess { object, .. } => {
matches!(&object.kind, ExprKind::Identifier(name) if name == "self")
}
ExprKind::Block(exprs) => {
if let Some(last) = exprs.last() {
Self::body_returns_self_field(last)
} else {
false
}
}
_ => false,
}
}
fn transpile_body_with_auto_clone(&self, body: &Expr) -> Result<TokenStream> {
match &body.kind {
ExprKind::FieldAccess { object, field } => {
if matches!(&object.kind, ExprKind::Identifier(name) if name == "self") {
let field_ident = format_ident!("{}", field);
Ok(quote! { self.#field_ident.clone() })
} else {
self.transpile_expr(body)
}
}
ExprKind::Block(exprs) if !exprs.is_empty() => {
let mut tokens = Vec::new();
for expr in exprs.iter().take(exprs.len() - 1) {
tokens.push(self.transpile_expr(expr)?);
}
if let Some(last) = exprs.last() {
let last_tokens = self.transpile_body_with_auto_clone(last)?;
tokens.push(last_tokens);
}
Ok(quote! { { #(#tokens)* } })
}
_ => self.transpile_expr(body),
}
}
}
#[cfg(test)]
mod extreme_tdd_tests {
use super::*;
use crate::frontend::ast::{
EnumVariant, EnumVariantKind, ImplMethod, Span, StructField, TraitMethod, Type, TypeKind,
Visibility,
};
fn make_type(kind: TypeKind) -> Type {
Type {
kind,
span: Span::new(0, 0),
}
}
fn make_expr(kind: crate::frontend::ast::ExprKind) -> Expr {
Expr {
kind,
span: Span::new(0, 0),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
fn make_param(name: &str, ty: Type) -> crate::frontend::ast::Param {
crate::frontend::ast::Param {
pattern: crate::frontend::ast::Pattern::Identifier(name.to_string()),
ty,
span: Span::new(0, 0),
is_mutable: false,
default_value: None,
}
}
fn make_struct_field(name: &str, ty: Type, visibility: Visibility) -> StructField {
StructField {
name: name.to_string(),
ty,
visibility,
default_value: None,
decorators: vec![],
is_mut: false,
}
}
#[test]
fn test_transpile_named_type_int() {
let t = Transpiler::new();
let result = t.transpile_named_type("int").unwrap();
assert!(result.to_string().contains("i64"));
}
#[test]
fn test_transpile_named_type_float() {
let t = Transpiler::new();
let result = t.transpile_named_type("float").unwrap();
assert!(result.to_string().contains("f64"));
}
#[test]
fn test_transpile_named_type_bool() {
let t = Transpiler::new();
let result = t.transpile_named_type("bool").unwrap();
assert!(result.to_string().contains("bool"));
}
#[test]
fn test_transpile_named_type_str() {
let t = Transpiler::new();
let result = t.transpile_named_type("str").unwrap();
assert!(result.to_string().contains("& str"));
}
#[test]
fn test_transpile_named_type_string() {
let t = Transpiler::new();
let result = t.transpile_named_type("string").unwrap();
assert!(result.to_string().contains("String"));
}
#[test]
fn test_transpile_named_type_char() {
let t = Transpiler::new();
let result = t.transpile_named_type("char").unwrap();
assert!(result.to_string().contains("char"));
}
#[test]
fn test_transpile_named_type_unit() {
let t = Transpiler::new();
let result = t.transpile_named_type("()").unwrap();
assert!(result.to_string().contains("()"));
}
#[test]
fn test_transpile_named_type_any() {
let t = Transpiler::new();
let result = t.transpile_named_type("Any").unwrap();
assert!(result.to_string().contains("_"));
}
#[test]
fn test_transpile_named_type_object() {
let t = Transpiler::new();
let result = t.transpile_named_type("Object").unwrap();
assert!(result.to_string().contains("BTreeMap"));
}
#[test]
fn test_transpile_named_type_namespaced() {
let t = Transpiler::new();
let result = t.transpile_named_type("std::io::Error").unwrap();
let s = result.to_string();
assert!(s.contains("std"));
assert!(s.contains("io"));
assert!(s.contains("Error"));
}
#[test]
fn test_transpile_named_type_custom() {
let t = Transpiler::new();
let result = t.transpile_named_type("MyCustomType").unwrap();
assert!(result.to_string().contains("MyCustomType"));
}
#[test]
fn test_transpile_generic_type_single_param() {
let t = Transpiler::new();
let inner = make_type(TypeKind::Named("i32".to_string()));
let result = t.transpile_generic_type("Vec", &[inner]).unwrap();
assert!(result.to_string().contains("Vec"));
assert!(result.to_string().contains("i32"));
}
#[test]
fn test_transpile_generic_type_multiple_params() {
let t = Transpiler::new();
let k = make_type(TypeKind::Named("String".to_string()));
let v = make_type(TypeKind::Named("i32".to_string()));
let result = t.transpile_generic_type("HashMap", &[k, v]).unwrap();
let s = result.to_string();
assert!(s.contains("HashMap"));
assert!(s.contains("String"));
assert!(s.contains("i32"));
}
#[test]
fn test_transpile_optional_type() {
let t = Transpiler::new();
let inner = make_type(TypeKind::Named("i32".to_string()));
let result = t.transpile_optional_type(&inner).unwrap();
let s = result.to_string();
assert!(s.contains("Option"));
assert!(s.contains("i32"));
}
#[test]
fn test_transpile_list_type() {
let t = Transpiler::new();
let elem = make_type(TypeKind::Named("String".to_string()));
let result = t.transpile_list_type(&elem).unwrap();
let s = result.to_string();
assert!(s.contains("Vec"));
assert!(s.contains("String"));
}
#[test]
fn test_transpile_array_type() {
let t = Transpiler::new();
let elem = make_type(TypeKind::Named("f64".to_string()));
let result = t.transpile_array_type(&elem, 10).unwrap();
let s = result.to_string();
assert!(s.contains("f64"));
assert!(s.contains("10"));
}
#[test]
fn test_transpile_tuple_type_empty() {
let t = Transpiler::new();
let result = t.transpile_tuple_type(&[]).unwrap();
assert!(result.to_string().contains("()"));
}
#[test]
fn test_transpile_tuple_type_single() {
let t = Transpiler::new();
let elem = make_type(TypeKind::Named("i32".to_string()));
let result = t.transpile_tuple_type(&[elem]).unwrap();
assert!(result.to_string().contains("i32"));
}
#[test]
fn test_transpile_tuple_type_multiple() {
let t = Transpiler::new();
let e1 = make_type(TypeKind::Named("i32".to_string()));
let e2 = make_type(TypeKind::Named("String".to_string()));
let e3 = make_type(TypeKind::Named("bool".to_string()));
let result = t.transpile_tuple_type(&[e1, e2, e3]).unwrap();
let s = result.to_string();
assert!(s.contains("i32"));
assert!(s.contains("String"));
assert!(s.contains("bool"));
}
#[test]
fn test_transpile_function_type() {
let t = Transpiler::new();
let param = make_type(TypeKind::Named("i32".to_string()));
let ret = make_type(TypeKind::Named("bool".to_string()));
let result = t.transpile_function_type(&[param], &ret).unwrap();
let s = result.to_string();
assert!(s.contains("fn"));
assert!(s.contains("i32"));
assert!(s.contains("bool"));
}
#[test]
fn test_transpile_reference_type_immutable() {
let t = Transpiler::new();
let inner = make_type(TypeKind::Named("i32".to_string()));
let result = t.transpile_reference_type(false, None, &inner).unwrap();
let s = result.to_string();
assert!(s.contains("&"));
assert!(s.contains("i32"));
assert!(!s.contains("mut"));
}
#[test]
fn test_transpile_reference_type_mutable() {
let t = Transpiler::new();
let inner = make_type(TypeKind::Named("i32".to_string()));
let result = t.transpile_reference_type(true, None, &inner).unwrap();
let s = result.to_string();
assert!(s.contains("mut"));
assert!(s.contains("i32"));
}
#[test]
fn test_transpile_reference_type_with_lifetime() {
let t = Transpiler::new();
let inner = make_type(TypeKind::Named("i32".to_string()));
let result = t
.transpile_reference_type(false, Some("'a"), &inner)
.unwrap();
let s = result.to_string();
assert!(s.contains("'a"));
assert!(s.contains("i32"));
}
#[test]
fn test_transpile_reference_type_str_special() {
let t = Transpiler::new();
let inner = make_type(TypeKind::Named("str".to_string()));
let result = t.transpile_reference_type(false, None, &inner).unwrap();
assert_eq!(result.to_string().matches("&").count(), 1);
}
#[test]
fn test_parse_type_param_simple() {
let result = Transpiler::parse_type_param_to_tokens("T");
assert!(result.to_string().contains("T"));
}
#[test]
fn test_parse_type_param_lifetime() {
let result = Transpiler::parse_type_param_to_tokens("'a");
assert!(result.to_string().contains("'a"));
}
#[test]
fn test_parse_type_param_with_bound() {
let result = Transpiler::parse_type_param_to_tokens("T: Clone");
let s = result.to_string();
assert!(s.contains("T"));
assert!(s.contains("Clone"));
}
#[test]
fn test_transpile_struct_empty() {
let t = Transpiler::new();
let result = t.transpile_struct("Empty", &[], &[], &[], false).unwrap();
let s = result.to_string();
assert!(s.contains("struct"));
assert!(s.contains("Empty"));
}
#[test]
fn test_transpile_struct_with_fields() {
let t = Transpiler::new();
let fields = vec![
make_struct_field(
"x",
make_type(TypeKind::Named("i32".to_string())),
Visibility::Public,
),
make_struct_field(
"y",
make_type(TypeKind::Named("i32".to_string())),
Visibility::Private,
),
];
let result = t
.transpile_struct("Point", &[], &fields, &[], true)
.unwrap();
let s = result.to_string();
assert!(s.contains("pub struct"));
assert!(s.contains("Point"));
assert!(s.contains("x"));
assert!(s.contains("y"));
}
#[test]
fn test_transpile_struct_with_type_params() {
let t = Transpiler::new();
let fields = vec![make_struct_field(
"value",
make_type(TypeKind::Named("T".to_string())),
Visibility::Public,
)];
let result = t
.transpile_struct("Container", &["T".to_string()], &fields, &[], true)
.unwrap();
let s = result.to_string();
assert!(s.contains("Container"));
assert!(s.contains("<"));
assert!(s.contains("T"));
}
#[test]
fn test_transpile_struct_with_derives() {
let t = Transpiler::new();
let result = t
.transpile_struct(
"MyStruct",
&[],
&[],
&["Debug".to_string(), "Clone".to_string()],
false,
)
.unwrap();
let s = result.to_string();
assert!(s.contains("derive"));
assert!(s.contains("Debug"));
assert!(s.contains("Clone"));
}
#[test]
fn test_transpile_tuple_struct() {
let t = Transpiler::new();
let fields = vec![
make_type(TypeKind::Named("i32".to_string())),
make_type(TypeKind::Named("String".to_string())),
];
let result = t
.transpile_tuple_struct("Pair", &[], &fields, &[], true)
.unwrap();
let s = result.to_string();
assert!(s.contains("pub struct"));
assert!(s.contains("Pair"));
assert!(s.contains("i32"));
assert!(s.contains("String"));
}
#[test]
fn test_transpile_enum_unit_variants() {
let t = Transpiler::new();
let variants = vec![
EnumVariant {
name: "Red".to_string(),
kind: EnumVariantKind::Unit,
discriminant: None,
},
EnumVariant {
name: "Green".to_string(),
kind: EnumVariantKind::Unit,
discriminant: None,
},
EnumVariant {
name: "Blue".to_string(),
kind: EnumVariantKind::Unit,
discriminant: None,
},
];
let result = t.transpile_enum("Color", &[], &variants, true).unwrap();
let s = result.to_string();
assert!(s.contains("pub enum"));
assert!(s.contains("Color"));
assert!(s.contains("Red"));
assert!(s.contains("Green"));
assert!(s.contains("Blue"));
}
#[test]
fn test_transpile_enum_with_discriminants() {
let t = Transpiler::new();
let variants = vec![
EnumVariant {
name: "A".to_string(),
kind: EnumVariantKind::Unit,
discriminant: Some(1),
},
EnumVariant {
name: "B".to_string(),
kind: EnumVariantKind::Unit,
discriminant: Some(2),
},
];
let result = t.transpile_enum("MyEnum", &[], &variants, false).unwrap();
let s = result.to_string();
assert!(s.contains("repr"));
assert!(s.contains("= 1"));
assert!(s.contains("= 2"));
}
#[test]
fn test_transpile_enum_tuple_variant() {
let t = Transpiler::new();
let variants = vec![
EnumVariant {
name: "Some".to_string(),
kind: EnumVariantKind::Tuple(vec![make_type(TypeKind::Named("T".to_string()))]),
discriminant: None,
},
EnumVariant {
name: "None".to_string(),
kind: EnumVariantKind::Unit,
discriminant: None,
},
];
let result = t
.transpile_enum("MyOption", &["T".to_string()], &variants, true)
.unwrap();
let s = result.to_string();
assert!(s.contains("MyOption"));
assert!(s.contains("Some"));
assert!(s.contains("None"));
}
#[test]
fn test_transpile_enum_struct_variant() {
let t = Transpiler::new();
let variants = vec![EnumVariant {
name: "Move".to_string(),
kind: EnumVariantKind::Struct(vec![
make_struct_field(
"x",
make_type(TypeKind::Named("i32".to_string())),
Visibility::Public,
),
make_struct_field(
"y",
make_type(TypeKind::Named("i32".to_string())),
Visibility::Public,
),
]),
discriminant: None,
}];
let result = t.transpile_enum("Message", &[], &variants, true).unwrap();
let s = result.to_string();
assert!(s.contains("Move"));
assert!(s.contains("x"));
assert!(s.contains("y"));
}
#[test]
fn test_transpile_trait_empty() {
let t = Transpiler::new();
let result = t.transpile_trait("Empty", &[], &[], &[], true).unwrap();
let s = result.to_string();
assert!(s.contains("pub trait"));
assert!(s.contains("Empty"));
}
#[test]
fn test_transpile_trait_with_method() {
let t = Transpiler::new();
let methods = vec![TraitMethod {
name: "do_something".to_string(),
params: vec![make_param(
"self",
make_type(TypeKind::Reference {
is_mut: false,
lifetime: None,
inner: Box::new(make_type(TypeKind::Named("Self".to_string()))),
}),
)],
return_type: Some(make_type(TypeKind::Named("i32".to_string()))),
body: None,
is_pub: false,
}];
let result = t
.transpile_trait("MyTrait", &[], &[], &methods, true)
.unwrap();
let s = result.to_string();
assert!(s.contains("MyTrait"));
assert!(s.contains("do_something"));
assert!(s.contains("i32"));
}
#[test]
fn test_transpile_trait_with_associated_type() {
let t = Transpiler::new();
let result = t
.transpile_trait("Iterator", &[], &["Item".to_string()], &[], true)
.unwrap();
let s = result.to_string();
assert!(s.contains("type Item"));
}
#[test]
fn test_transpile_impl_inherent() {
let t = Transpiler::new();
let body = make_expr(crate::frontend::ast::ExprKind::Literal(
crate::frontend::ast::Literal::Integer(42, None),
));
let methods = vec![ImplMethod {
name: "answer".to_string(),
params: vec![],
return_type: Some(make_type(TypeKind::Named("i32".to_string()))),
body: Box::new(body),
is_pub: true,
}];
let result = t
.transpile_impl("MyStruct", &[], None, &methods, true)
.unwrap();
let s = result.to_string();
assert!(s.contains("impl MyStruct"));
assert!(s.contains("answer"));
}
#[test]
fn test_transpile_impl_trait() {
let t = Transpiler::new();
let body = make_expr(crate::frontend::ast::ExprKind::Literal(
crate::frontend::ast::Literal::Integer(0, None),
));
let methods = vec![ImplMethod {
name: "default".to_string(),
params: vec![],
return_type: Some(make_type(TypeKind::Named("Self".to_string()))),
body: Box::new(body),
is_pub: false,
}];
let result = t
.transpile_impl("MyStruct", &[], Some("Default"), &methods, false)
.unwrap();
let s = result.to_string();
assert!(s.contains("impl Default for MyStruct"));
}
#[test]
fn test_transpile_extend() {
let t = Transpiler::new();
let body = make_expr(crate::frontend::ast::ExprKind::Literal(
crate::frontend::ast::Literal::Bool(true),
));
let methods = vec![ImplMethod {
name: "is_empty".to_string(),
params: vec![make_param(
"self",
make_type(TypeKind::Reference {
is_mut: false,
lifetime: None,
inner: Box::new(make_type(TypeKind::Named("Self".to_string()))),
}),
)],
return_type: Some(make_type(TypeKind::Named("bool".to_string()))),
body: Box::new(body),
is_pub: true,
}];
let result = t.transpile_extend("String", &methods).unwrap();
let s = result.to_string();
assert!(s.contains("trait StringExt"));
assert!(s.contains("impl StringExt for String"));
}
#[test]
fn test_has_reference_fields_true() {
let t = Transpiler::new();
let fields = vec![StructField {
name: "data".to_string(),
ty: make_type(TypeKind::Reference {
is_mut: false,
lifetime: None,
inner: Box::new(make_type(TypeKind::Named("str".to_string()))),
}),
visibility: Visibility::Public,
default_value: None,
decorators: vec![],
is_mut: false,
}];
assert!(t.has_reference_fields(&fields));
}
#[test]
fn test_has_reference_fields_false() {
let t = Transpiler::new();
let fields = vec![StructField {
name: "data".to_string(),
ty: make_type(TypeKind::Named("String".to_string())),
visibility: Visibility::Public,
default_value: None,
decorators: vec![],
is_mut: false,
}];
assert!(!t.has_reference_fields(&fields));
}
#[test]
fn test_has_lifetime_params_true() {
let t = Transpiler::new();
assert!(t.has_lifetime_params(&["'a".to_string(), "T".to_string()]));
}
#[test]
fn test_has_lifetime_params_false() {
let t = Transpiler::new();
assert!(!t.has_lifetime_params(&["T".to_string(), "U".to_string()]));
}
#[test]
fn test_generate_derive_attributes_empty() {
let t = Transpiler::new();
let result = t.generate_derive_attributes(&[]);
assert!(result.is_empty());
}
#[test]
fn test_generate_derive_attributes_multiple() {
let t = Transpiler::new();
let result = t.generate_derive_attributes(&["Debug".to_string(), "Clone".to_string()]);
let s = result.to_string();
assert!(s.contains("derive"));
assert!(s.contains("Debug"));
assert!(s.contains("Clone"));
}
#[test]
fn test_generate_class_type_param_tokens() {
let t = Transpiler::new();
let result = t.generate_class_type_param_tokens(&["T".to_string(), "'a".to_string()]);
assert_eq!(result.len(), 2);
}
#[test]
fn test_transpile_type_named() {
let t = Transpiler::new();
let ty = make_type(TypeKind::Named("i32".to_string()));
let result = t.transpile_type(&ty).unwrap();
assert!(result.to_string().contains("i32"));
}
#[test]
fn test_transpile_type_optional() {
let t = Transpiler::new();
let ty = make_type(TypeKind::Optional(Box::new(make_type(TypeKind::Named(
"i32".to_string(),
)))));
let result = t.transpile_type(&ty).unwrap();
assert!(result.to_string().contains("Option"));
}
#[test]
fn test_transpile_type_list() {
let t = Transpiler::new();
let ty = make_type(TypeKind::List(Box::new(make_type(TypeKind::Named(
"String".to_string(),
)))));
let result = t.transpile_type(&ty).unwrap();
assert!(result.to_string().contains("Vec"));
}
#[test]
fn test_transpile_type_dataframe() {
let t = Transpiler::new();
let ty = make_type(TypeKind::DataFrame { columns: vec![] });
let result = t.transpile_type(&ty).unwrap();
assert!(result.to_string().contains("DataFrame"));
}
#[test]
fn test_transpile_type_series() {
let t = Transpiler::new();
let ty = make_type(TypeKind::Series {
dtype: Box::new(make_type(TypeKind::Named("f64".to_string()))),
});
let result = t.transpile_type(&ty).unwrap();
assert!(result.to_string().contains("Series"));
}
#[test]
fn test_transpile_type_refined() {
let t = Transpiler::new();
let ty = make_type(TypeKind::Refined {
base: Box::new(make_type(TypeKind::Named("i32".to_string()))),
constraint: Box::new(make_expr(crate::frontend::ast::ExprKind::Literal(
crate::frontend::ast::Literal::Bool(true),
))),
});
let result = t.transpile_type(&ty).unwrap();
assert!(result.to_string().contains("i32"));
}
}