#[cfg(feature = "decision-tracing")]
use depyler_hir::decision_trace::DecisionCategory;
use depyler_hir::hir::{ConstGeneric, Type as PythonType};
use depyler_hir::trace_decision;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum IntWidth {
I32,
I64,
ISize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum StringStrategy {
AlwaysOwned, InferBorrowing, CowByDefault, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeMapper {
pub width_preference: IntWidth,
pub string_type: StringStrategy,
#[serde(default = "default_nasa_mode")]
pub nasa_mode: bool,
}
fn default_nasa_mode() -> bool {
true
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RustType {
Primitive(PrimitiveType),
String,
Str {
lifetime: Option<String>,
},
Cow {
lifetime: String,
},
Vec(Box<RustType>),
HashMap(Box<RustType>, Box<RustType>),
HashSet(Box<RustType>),
Option(Box<RustType>),
Result(Box<RustType>, Box<RustType>),
Reference {
lifetime: Option<String>,
mutable: bool,
inner: Box<RustType>,
},
Tuple(Vec<RustType>),
Unit,
Custom(String),
Unsupported(String),
TypeParam(String),
Generic {
base: String,
params: Vec<RustType>,
},
Enum {
name: String,
variants: Vec<(String, RustType)>,
},
Array {
element_type: Box<RustType>,
size: RustConstGeneric,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RustConstGeneric {
Literal(usize),
Parameter(String),
Expression(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PrimitiveType {
Bool,
I8,
I16,
I32,
I64,
I128,
ISize,
U8,
U16,
U32,
U64,
U128,
USize,
F32,
F64,
}
impl Default for TypeMapper {
fn default() -> Self {
Self {
width_preference: IntWidth::I32,
string_type: StringStrategy::AlwaysOwned,
nasa_mode: true, }
}
}
impl TypeMapper {
pub fn new() -> Self {
Self::default()
}
pub fn with_i64(mut self) -> Self {
self.width_preference = IntWidth::I64;
self
}
pub fn with_string_strategy(mut self, strategy: StringStrategy) -> Self {
self.string_type = strategy;
self
}
pub fn with_nasa_mode(mut self, enabled: bool) -> Self {
self.nasa_mode = enabled;
self
}
fn unknown_fallback(&self) -> RustType {
if self.nasa_mode {
RustType::Custom("DepylerValue".to_string())
} else {
RustType::Custom("serde_json::Value".to_string())
}
}
fn unknown_fallback_str(&self) -> &'static str {
if self.nasa_mode {
"DepylerValue"
} else {
"serde_json::Value"
}
}
pub fn map_type(&self, py_type: &PythonType) -> RustType {
trace_decision!(
category = DecisionCategory::TypeMapping,
name = "python_to_rust_type",
chosen = &format!("{:?}", py_type),
alternatives = ["primitive", "owned", "borrowed", "option", "result"],
confidence = 0.90
);
match py_type {
PythonType::Unknown => self.unknown_fallback(),
PythonType::Int => RustType::Primitive(match self.width_preference {
IntWidth::I32 => PrimitiveType::I32,
IntWidth::I64 => PrimitiveType::I64,
IntWidth::ISize => PrimitiveType::ISize,
}),
PythonType::Float => RustType::Primitive(PrimitiveType::F64),
PythonType::String => match self.string_type {
StringStrategy::AlwaysOwned => RustType::String,
StringStrategy::InferBorrowing => RustType::String, StringStrategy::CowByDefault => RustType::String, },
PythonType::Bool => RustType::Primitive(PrimitiveType::Bool),
PythonType::None => RustType::Unit,
PythonType::List(inner) => match inner.as_ref() {
PythonType::Unknown => RustType::Vec(Box::new(self.unknown_fallback())),
_ => RustType::Vec(Box::new(self.map_type(inner))),
},
PythonType::Dict(k, v) => {
let key_type = if matches!(**k, PythonType::Unknown) {
RustType::String } else {
self.map_type(k)
};
let val_type = if matches!(**v, PythonType::Unknown) {
self.unknown_fallback()
} else {
self.map_type(v)
};
RustType::HashMap(Box::new(key_type), Box::new(val_type))
}
PythonType::Tuple(types) => {
let rust_types = types.iter().map(|t| self.map_type(t)).collect();
RustType::Tuple(rust_types)
}
PythonType::Optional(inner) => RustType::Option(Box::new(self.map_type(inner))),
PythonType::Final(inner) => self.map_type(inner), PythonType::Function { params: _, ret: _ } => {
RustType::Unsupported("function".to_string())
}
PythonType::Custom(name) => {
if name.len() == 1 && name.chars().next().unwrap().is_uppercase() {
RustType::TypeParam(name.clone())
} else {
match name.as_str() {
"Dict" => RustType::HashMap(
Box::new(RustType::String), Box::new(self.unknown_fallback()), ),
"List" | "list" => RustType::Vec(Box::new(self.unknown_fallback())),
"Sequence" => RustType::Vec(Box::new(self.unknown_fallback())),
"Set" => RustType::HashSet(Box::new(self.unknown_fallback())),
"tuple" => RustType::Tuple(vec![]),
"File" => RustType::Reference {
lifetime: None,
mutable: true,
inner: Box::new(RustType::Custom("impl std::io::Write".to_string())),
},
"Namespace" | "argparse.Namespace" => RustType::Custom("Args".to_string()),
"bytes" => RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::U8))),
"bytearray" => {
RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::U8)))
}
"Callable" | "typing.Callable" | "callable" => {
RustType::Custom("impl Fn()".to_string())
}
"Any" | "typing.Any" | "any" => self.unknown_fallback(),
"object" | "builtins.object" => self.unknown_fallback(),
"date" | "datetime.date" => {
if self.nasa_mode {
RustType::Custom("DepylerDate".to_string())
} else {
RustType::Custom("chrono::NaiveDate".to_string())
}
}
"datetime" | "datetime.datetime" => {
if self.nasa_mode {
RustType::Custom("DepylerDateTime".to_string())
} else {
RustType::Custom("chrono::NaiveDateTime".to_string())
}
}
"time" | "datetime.time" => {
if self.nasa_mode {
RustType::Custom("(u32, u32, u32)".to_string()) } else {
RustType::Custom("chrono::NaiveTime".to_string())
}
}
"timedelta" | "datetime.timedelta" => {
if self.nasa_mode {
RustType::Custom("DepylerTimeDelta".to_string())
} else {
RustType::Custom("chrono::Duration".to_string())
}
}
"Path" | "pathlib.Path" | "PurePath" | "pathlib.PurePath" => {
RustType::Custom("std::path::PathBuf".to_string())
}
"OSError" | "IOError" | "FileNotFoundError" | "PermissionError" => {
RustType::Custom("std::io::Error".to_string())
}
"Exception"
| "BaseException"
| "ValueError"
| "TypeError"
| "KeyError"
| "IndexError"
| "RuntimeError"
| "AttributeError"
| "NotImplementedError"
| "AssertionError"
| "StopIteration"
| "ZeroDivisionError"
| "OverflowError"
| "ArithmeticError" => {
RustType::Custom("Box<dyn std::error::Error>".to_string())
}
"deque" | "collections.deque" | "Deque" => {
RustType::Custom("std::collections::VecDeque<i32>".to_string())
}
"Counter" | "collections.Counter" => RustType::HashMap(
Box::new(RustType::String),
Box::new(RustType::Primitive(PrimitiveType::I32)),
),
_ => RustType::Custom(name.clone()),
}
}
}
PythonType::TypeVar(name) => RustType::TypeParam(name.clone()),
PythonType::Generic { base, params } => {
match base.as_str() {
"List" if params.len() == 1 => {
RustType::Vec(Box::new(self.map_type(¶ms[0])))
}
"Sequence" if params.len() == 1 => {
RustType::Vec(Box::new(self.map_type(¶ms[0])))
}
"Dict" if params.len() == 2 => RustType::HashMap(
Box::new(self.map_type(¶ms[0])),
Box::new(self.map_type(¶ms[1])),
),
"deque" | "collections.deque" | "Deque" if params.len() == 1 => {
let inner_type = self.map_type(¶ms[0]);
RustType::Custom(format!(
"std::collections::VecDeque<{}>",
inner_type.to_rust_string()
))
}
"Generator" if !params.is_empty() => {
let yield_type = self.map_type(¶ms[0]);
RustType::Custom(format!(
"impl Iterator<Item={}>",
yield_type.to_rust_string()
))
}
"Iterator" if params.len() == 1 => {
let yield_type = self.map_type(¶ms[0]);
RustType::Custom(format!(
"impl Iterator<Item={}>",
yield_type.to_rust_string()
))
}
"Iterable" if params.len() == 1 => {
let item_type = self.map_type(¶ms[0]);
RustType::Custom(format!(
"impl IntoIterator<Item={}>",
item_type.to_rust_string()
))
}
"Callable" if params.len() == 2 => {
let param_types = match ¶ms[0] {
PythonType::Tuple(inner) => inner
.iter()
.map(|t| self.map_type(t).to_rust_string())
.collect::<Vec<_>>(),
PythonType::List(inner) => {
vec![self.map_type(inner).to_rust_string()]
}
PythonType::None => vec![], PythonType::Unknown => vec![], _ => vec![self.map_type(¶ms[0]).to_rust_string()],
};
let return_type = self.map_type(¶ms[1]);
let return_str = return_type.to_rust_string();
let has_nested_fn = param_types.iter().any(|s| s.contains("impl Fn"))
|| return_str.contains("impl Fn");
let (fn_prefix, fixed_params, fixed_return) = if has_nested_fn {
let fixed_params: Vec<String> = param_types
.iter()
.map(|s| s.replace("impl Fn", "&dyn Fn"))
.collect();
let fixed_return = return_str.replace("impl Fn", "&dyn Fn");
("&dyn Fn", fixed_params, fixed_return)
} else {
("impl Fn", param_types.clone(), return_str.clone())
};
let fn_str =
if fixed_return == "()" || matches!(params[1], PythonType::None) {
format!("{}({})", fn_prefix, fixed_params.join(", "))
} else {
format!(
"{}({}) -> {}",
fn_prefix,
fixed_params.join(", "),
fixed_return
)
};
RustType::Custom(fn_str)
}
"Callable" if params.is_empty() => {
RustType::Custom("impl Fn()".to_string())
}
"type" if params.len() == 1 => {
let inner_type = self.map_type(¶ms[0]);
RustType::Custom(format!(
"std::marker::PhantomData<{}>",
inner_type.to_rust_string()
))
}
_ => RustType::Generic {
base: base.clone(),
params: params.iter().map(|t| self.map_type(t)).collect(),
},
}
}
PythonType::Union(types) => {
if types.len() == 2 && types.iter().any(|t| matches!(t, PythonType::None)) {
let non_none = types
.iter()
.find(|t| !matches!(t, PythonType::None))
.unwrap();
RustType::Option(Box::new(self.map_type(non_none)))
} else {
RustType::Enum {
name: "UnionType".to_string(), variants: types
.iter()
.enumerate()
.map(|(i, t)| {
let variant_name = match t {
PythonType::Int => "Integer".to_string(),
PythonType::Float => "Float".to_string(),
PythonType::String => "Text".to_string(),
PythonType::Bool => "Boolean".to_string(),
PythonType::None => "None".to_string(),
_ => format!("Variant{}", i),
};
(variant_name, self.map_type(t))
})
.collect(),
}
}
}
PythonType::Array { element_type, size } => RustType::Array {
element_type: Box::new(self.map_type(element_type)),
size: self.map_const_generic(size),
},
PythonType::Set(inner) => match inner.as_ref() {
PythonType::Unknown => RustType::HashSet(Box::new(RustType::String)),
_ => RustType::HashSet(Box::new(self.map_type(inner))),
},
PythonType::UnificationVar(id) => {
tracing::warn!(
"UnificationVar({}) encountered in type mapper. Falling back to {}.",
id,
self.unknown_fallback_str()
);
self.unknown_fallback()
}
}
}
pub fn map_return_type(&self, py_type: &PythonType) -> RustType {
trace_decision!(
category = DecisionCategory::TypeMapping,
name = "return_type_mapping",
chosen = &format!("{:?}", py_type),
alternatives = ["unit", "result", "option", "owned"],
confidence = 0.92
);
match py_type {
PythonType::None => RustType::Unit,
PythonType::Unknown => RustType::Unit, _ => self.map_type(py_type),
}
}
pub fn needs_reference(&self, rust_type: &RustType) -> bool {
trace_decision!(
category = DecisionCategory::BorrowStrategy,
name = "needs_reference",
chosen = &format!("{:?}", rust_type),
alternatives = ["by_value", "by_ref", "by_mut_ref"],
confidence = 0.88
);
match rust_type {
RustType::String => false, RustType::Vec(_) | RustType::HashMap(_, _) | RustType::HashSet(_) => true,
RustType::Primitive(_) => false,
RustType::Array { .. } => true, _ => false,
}
}
#[allow(clippy::only_used_in_recursion)]
pub fn can_copy(&self, rust_type: &RustType) -> bool {
match rust_type {
RustType::Primitive(_) | RustType::Unit => true,
RustType::Tuple(types) => types.iter().all(|t| self.can_copy(t)),
RustType::Array { element_type, size } => {
match size {
RustConstGeneric::Literal(n) if *n <= 32 => self.can_copy(element_type),
_ => false, }
}
_ => false,
}
}
pub fn map_const_generic(&self, const_generic: &ConstGeneric) -> RustConstGeneric {
match const_generic {
ConstGeneric::Literal(value) => RustConstGeneric::Literal(*value),
ConstGeneric::Parameter(name) => RustConstGeneric::Parameter(name.clone()),
ConstGeneric::Expression(expr) => RustConstGeneric::Expression(expr.clone()),
}
}
}
impl RustType {
pub fn to_rust_string(&self) -> String {
match self {
RustType::Primitive(p) => p.to_rust_string().to_string(),
RustType::String => "String".to_string(),
RustType::Str { lifetime } => {
if let Some(lt) = lifetime {
format!("&{lt} str")
} else {
"&str".to_string()
}
}
RustType::Cow { lifetime } => format!("Cow<{lifetime}, str>"),
RustType::Vec(inner) => format!("Vec<{}>", inner.to_rust_string()),
RustType::HashMap(k, v) => {
format!("HashMap<{}, {}>", k.to_rust_string(), v.to_rust_string())
}
RustType::HashSet(inner) => format!("HashSet<{}>", inner.to_rust_string()),
RustType::Option(inner) => format!("Option<{}>", inner.to_rust_string()),
RustType::Result(ok, err) => {
format!("Result<{}, {}>", ok.to_rust_string(), err.to_rust_string())
}
RustType::Reference {
lifetime,
mutable,
inner,
} => {
let mut_str = if *mutable { "mut " } else { "" };
if let Some(lt) = lifetime {
format!("&{} {}{}", lt, mut_str, inner.to_rust_string())
} else {
format!("&{}{}", mut_str, inner.to_rust_string())
}
}
RustType::Tuple(types) => {
if types.is_empty() {
"()".to_string()
} else {
let type_strs: Vec<String> = types.iter().map(|t| t.to_rust_string()).collect();
format!("({})", type_strs.join(", "))
}
}
RustType::Unit => "()".to_string(),
RustType::Custom(name) => name.clone(),
RustType::Unsupported(desc) => format!("/* unsupported: {desc} */"),
RustType::TypeParam(name) => name.clone(),
RustType::Generic { base, params } => {
let param_strs: Vec<String> = params.iter().map(|p| p.to_rust_string()).collect();
format!("{}<{}>", base, param_strs.join(", "))
}
RustType::Enum { name, .. } => name.clone(),
RustType::Array { element_type, size } => {
format!(
"[{}; {}]",
element_type.to_rust_string(),
size.to_rust_string()
)
}
}
}
}
impl RustConstGeneric {
pub fn to_rust_string(&self) -> String {
match self {
RustConstGeneric::Literal(value) => value.to_string(),
RustConstGeneric::Parameter(name) => name.clone(),
RustConstGeneric::Expression(expr) => expr.clone(),
}
}
}
impl PrimitiveType {
pub fn to_rust_string(&self) -> &'static str {
match self {
PrimitiveType::Bool => "bool",
PrimitiveType::I8 => "i8",
PrimitiveType::I16 => "i16",
PrimitiveType::I32 => "i32",
PrimitiveType::I64 => "i64",
PrimitiveType::I128 => "i128",
PrimitiveType::ISize => "isize",
PrimitiveType::U8 => "u8",
PrimitiveType::U16 => "u16",
PrimitiveType::U32 => "u32",
PrimitiveType::U64 => "u64",
PrimitiveType::U128 => "u128",
PrimitiveType::USize => "usize",
PrimitiveType::F32 => "f32",
PrimitiveType::F64 => "f64",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_type_mapper() {
let mapper = TypeMapper::default();
assert_eq!(mapper.width_preference, IntWidth::I32);
assert_eq!(mapper.string_type, StringStrategy::AlwaysOwned);
}
#[test]
fn test_type_mapper_creation() {
let mapper = TypeMapper::new();
assert_eq!(mapper.width_preference, IntWidth::I32);
let mapper_i64 = mapper.with_i64();
assert_eq!(mapper_i64.width_preference, IntWidth::I64);
let mapper_borrowing =
TypeMapper::new().with_string_strategy(StringStrategy::InferBorrowing);
assert_eq!(mapper_borrowing.string_type, StringStrategy::InferBorrowing);
}
#[test]
fn test_basic_type_mapping() {
let mapper = TypeMapper::new();
assert_eq!(
mapper.map_type(&PythonType::Int),
RustType::Primitive(PrimitiveType::I32)
);
assert_eq!(
mapper.map_type(&PythonType::Float),
RustType::Primitive(PrimitiveType::F64)
);
assert_eq!(mapper.map_type(&PythonType::String), RustType::String);
assert_eq!(
mapper.map_type(&PythonType::Bool),
RustType::Primitive(PrimitiveType::Bool)
);
assert_eq!(mapper.map_type(&PythonType::None), RustType::Unit);
}
#[test]
fn test_width_preference() {
let mapper_i32 = TypeMapper::new();
assert_eq!(
mapper_i32.map_type(&PythonType::Int),
RustType::Primitive(PrimitiveType::I32)
);
let mapper_i64 = TypeMapper::new().with_i64();
assert_eq!(
mapper_i64.map_type(&PythonType::Int),
RustType::Primitive(PrimitiveType::I64)
);
let mut mapper_isize = TypeMapper::new();
mapper_isize.width_preference = IntWidth::ISize;
assert_eq!(
mapper_isize.map_type(&PythonType::Int),
RustType::Primitive(PrimitiveType::ISize)
);
}
#[test]
fn test_complex_type_mapping() {
let mapper = TypeMapper::new();
let list_type = PythonType::List(Box::new(PythonType::Int));
assert_eq!(
mapper.map_type(&list_type),
RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::I32)))
);
let optional_type = PythonType::Optional(Box::new(PythonType::String));
assert_eq!(
mapper.map_type(&optional_type),
RustType::Option(Box::new(RustType::String))
);
let dict_type = PythonType::Dict(Box::new(PythonType::String), Box::new(PythonType::Int));
assert_eq!(
mapper.map_type(&dict_type),
RustType::HashMap(
Box::new(RustType::String),
Box::new(RustType::Primitive(PrimitiveType::I32))
)
);
}
#[test]
fn test_tuple_mapping() {
let mapper = TypeMapper::new();
let tuple_type =
PythonType::Tuple(vec![PythonType::Int, PythonType::String, PythonType::Bool]);
if let RustType::Tuple(types) = mapper.map_type(&tuple_type) {
assert_eq!(types.len(), 3);
assert_eq!(types[0], RustType::Primitive(PrimitiveType::I32));
assert_eq!(types[1], RustType::String);
assert_eq!(types[2], RustType::Primitive(PrimitiveType::Bool));
} else {
panic!("Expected tuple type");
}
}
#[test]
fn test_return_type_mapping() {
let mapper = TypeMapper::new();
assert_eq!(mapper.map_return_type(&PythonType::None), RustType::Unit);
assert_eq!(
mapper.map_return_type(&PythonType::Int),
RustType::Primitive(PrimitiveType::I32)
);
}
#[test]
fn test_needs_reference() {
let mapper = TypeMapper::new();
assert!(!mapper.needs_reference(&RustType::String));
assert!(!mapper.needs_reference(&RustType::Primitive(PrimitiveType::I32)));
assert!(mapper.needs_reference(&RustType::Vec(Box::new(RustType::String))));
assert!(mapper.needs_reference(&RustType::HashMap(
Box::new(RustType::String),
Box::new(RustType::Primitive(PrimitiveType::I32))
)));
}
#[test]
fn test_can_copy() {
let mapper = TypeMapper::new();
assert!(mapper.can_copy(&RustType::Primitive(PrimitiveType::I32)));
assert!(mapper.can_copy(&RustType::Unit));
assert!(!mapper.can_copy(&RustType::String));
assert!(
!mapper.can_copy(&RustType::Vec(Box::new(RustType::Primitive(
PrimitiveType::I32
))))
);
let copyable_tuple = RustType::Tuple(vec![
RustType::Primitive(PrimitiveType::I32),
RustType::Primitive(PrimitiveType::Bool),
]);
assert!(mapper.can_copy(©able_tuple));
let non_copyable_tuple = RustType::Tuple(vec![
RustType::Primitive(PrimitiveType::I32),
RustType::String,
]);
assert!(!mapper.can_copy(&non_copyable_tuple));
}
#[test]
fn test_rust_type_to_string() {
assert_eq!(
RustType::Primitive(PrimitiveType::I32).to_rust_string(),
"i32"
);
assert_eq!(RustType::String.to_rust_string(), "String");
assert_eq!(RustType::Unit.to_rust_string(), "()");
let vec_type = RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::I32)));
assert_eq!(vec_type.to_rust_string(), "Vec<i32>");
let optional_type = RustType::Option(Box::new(RustType::String));
assert_eq!(optional_type.to_rust_string(), "Option<String>");
let hashmap_type = RustType::HashMap(
Box::new(RustType::String),
Box::new(RustType::Primitive(PrimitiveType::I32)),
);
assert_eq!(hashmap_type.to_rust_string(), "HashMap<String, i32>");
let tuple_type = RustType::Tuple(vec![
RustType::Primitive(PrimitiveType::I32),
RustType::String,
]);
assert_eq!(tuple_type.to_rust_string(), "(i32, String)");
let empty_tuple = RustType::Tuple(vec![]);
assert_eq!(empty_tuple.to_rust_string(), "()");
}
#[test]
fn test_primitive_type_to_string() {
assert_eq!(PrimitiveType::Bool.to_rust_string(), "bool");
assert_eq!(PrimitiveType::I32.to_rust_string(), "i32");
assert_eq!(PrimitiveType::I64.to_rust_string(), "i64");
assert_eq!(PrimitiveType::F64.to_rust_string(), "f64");
assert_eq!(PrimitiveType::ISize.to_rust_string(), "isize");
}
#[test]
fn test_custom_and_unsupported_types() {
let mapper = TypeMapper::new();
let custom_type = PythonType::Custom("MyClass".to_string());
assert_eq!(
mapper.map_type(&custom_type),
RustType::Custom("MyClass".to_string())
);
assert_eq!(
RustType::Custom("MyClass".to_string()).to_rust_string(),
"MyClass"
);
let unknown_type = PythonType::Unknown;
assert_eq!(
mapper.map_type(&unknown_type),
RustType::Custom("DepylerValue".to_string()) );
}
#[test]
fn test_function_type_unsupported() {
let mapper = TypeMapper::new();
let func_type = PythonType::Function {
params: vec![PythonType::Int],
ret: Box::new(PythonType::String),
};
if let RustType::Unsupported(desc) = mapper.map_type(&func_type) {
assert_eq!(desc, "function");
} else {
panic!("Expected unsupported function type");
}
}
#[test]
fn test_depyler_0589_any_type_mapping() {
let mapper = TypeMapper::new();
let any_lower = PythonType::Custom("any".to_string());
assert_eq!(
mapper.map_type(&any_lower),
RustType::Custom("DepylerValue".to_string())
);
let any_upper = PythonType::Custom("Any".to_string());
assert_eq!(
mapper.map_type(&any_upper),
RustType::Custom("DepylerValue".to_string())
);
let typing_any = PythonType::Custom("typing.Any".to_string());
assert_eq!(
mapper.map_type(&typing_any),
RustType::Custom("DepylerValue".to_string())
);
let non_nasa_mapper = TypeMapper::new().with_nasa_mode(false);
assert_eq!(
non_nasa_mapper.map_type(&any_lower),
RustType::Custom("serde_json::Value".to_string())
);
}
#[test]
fn test_depyler_0734_callable_type_mapping() {
let mapper = TypeMapper::new();
let callable_lower = PythonType::Custom("callable".to_string());
if let RustType::Custom(name) = mapper.map_type(&callable_lower) {
assert_eq!(name, "impl Fn()");
} else {
panic!("Expected Custom type for 'callable'");
}
let callable_upper = PythonType::Custom("Callable".to_string());
if let RustType::Custom(name) = mapper.map_type(&callable_upper) {
assert_eq!(name, "impl Fn()");
} else {
panic!("Expected Custom type for 'Callable'");
}
}
#[test]
fn test_set_type_mapping() {
let mapper = TypeMapper::new();
let set_type = PythonType::Set(Box::new(PythonType::Int));
assert_eq!(
mapper.map_type(&set_type),
RustType::HashSet(Box::new(RustType::Primitive(PrimitiveType::I32)))
);
let set_str_type = PythonType::Set(Box::new(PythonType::String));
assert_eq!(
mapper.map_type(&set_str_type),
RustType::HashSet(Box::new(RustType::String))
);
assert_eq!(
RustType::HashSet(Box::new(RustType::Primitive(PrimitiveType::I32))).to_rust_string(),
"HashSet<i32>"
);
assert!(mapper.needs_reference(&RustType::HashSet(Box::new(RustType::String))));
}
#[test]
fn test_date_type_mapping() {
let nasa_mapper = TypeMapper::new();
let date = PythonType::Custom("date".to_string());
if let RustType::Custom(name) = nasa_mapper.map_type(&date) {
assert_eq!(name, "DepylerDate");
} else {
panic!("Expected Custom type for 'date' in NASA mode");
}
let non_nasa_mapper = TypeMapper::new().with_nasa_mode(false);
let datetime_date = PythonType::Custom("datetime.date".to_string());
if let RustType::Custom(name) = non_nasa_mapper.map_type(&datetime_date) {
assert_eq!(name, "chrono::NaiveDate");
} else {
panic!("Expected Custom type for 'datetime.date' in non-NASA mode");
}
}
#[test]
fn test_datetime_type_mapping() {
let nasa_mapper = TypeMapper::new();
let datetime = PythonType::Custom("datetime".to_string());
if let RustType::Custom(name) = nasa_mapper.map_type(&datetime) {
assert_eq!(name, "DepylerDateTime");
} else {
panic!("Expected Custom type for 'datetime' in NASA mode");
}
let non_nasa_mapper = TypeMapper::new().with_nasa_mode(false);
let datetime_datetime = PythonType::Custom("datetime.datetime".to_string());
if let RustType::Custom(name) = non_nasa_mapper.map_type(&datetime_datetime) {
assert_eq!(name, "chrono::NaiveDateTime");
} else {
panic!("Expected Custom type for 'datetime.datetime' in non-NASA mode");
}
}
#[test]
fn test_time_type_mapping() {
let nasa_mapper = TypeMapper::new();
let time = PythonType::Custom("time".to_string());
if let RustType::Custom(name) = nasa_mapper.map_type(&time) {
assert_eq!(name, "(u32, u32, u32)");
} else {
panic!("Expected Custom type for 'time' in NASA mode");
}
let non_nasa_mapper = TypeMapper::new().with_nasa_mode(false);
let datetime_time = PythonType::Custom("datetime.time".to_string());
if let RustType::Custom(name) = non_nasa_mapper.map_type(&datetime_time) {
assert_eq!(name, "chrono::NaiveTime");
} else {
panic!("Expected Custom type for 'datetime.time' in non-NASA mode");
}
}
#[test]
fn test_timedelta_type_mapping() {
let nasa_mapper = TypeMapper::new();
let timedelta = PythonType::Custom("timedelta".to_string());
if let RustType::Custom(name) = nasa_mapper.map_type(&timedelta) {
assert_eq!(name, "DepylerTimeDelta");
} else {
panic!("Expected Custom type for 'timedelta' in NASA mode");
}
let non_nasa_mapper = TypeMapper::new().with_nasa_mode(false);
let datetime_timedelta = PythonType::Custom("datetime.timedelta".to_string());
if let RustType::Custom(name) = non_nasa_mapper.map_type(&datetime_timedelta) {
assert_eq!(name, "chrono::Duration");
} else {
panic!("Expected Custom type for 'datetime.timedelta' in non-NASA mode");
}
}
#[test]
fn test_path_type_mapping() {
let mapper = TypeMapper::new();
let path = PythonType::Custom("Path".to_string());
if let RustType::Custom(name) = mapper.map_type(&path) {
assert_eq!(name, "std::path::PathBuf");
} else {
panic!("Expected Custom type for 'Path'");
}
let pathlib_path = PythonType::Custom("pathlib.Path".to_string());
if let RustType::Custom(name) = mapper.map_type(&pathlib_path) {
assert_eq!(name, "std::path::PathBuf");
} else {
panic!("Expected Custom type for 'pathlib.Path'");
}
}
#[test]
fn test_purepath_type_mapping() {
let mapper = TypeMapper::new();
let pure_path = PythonType::Custom("PurePath".to_string());
if let RustType::Custom(name) = mapper.map_type(&pure_path) {
assert_eq!(name, "std::path::PathBuf");
} else {
panic!("Expected Custom type for 'PurePath'");
}
let pathlib_purepath = PythonType::Custom("pathlib.PurePath".to_string());
if let RustType::Custom(name) = mapper.map_type(&pathlib_purepath) {
assert_eq!(name, "std::path::PathBuf");
} else {
panic!("Expected Custom type for 'pathlib.PurePath'");
}
}
#[test]
fn test_bytes_type_mapping() {
let mapper = TypeMapper::new();
let bytes = PythonType::Custom("bytes".to_string());
assert_eq!(
mapper.map_type(&bytes),
RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::U8)))
);
}
#[test]
fn test_bytearray_type_mapping() {
let mapper = TypeMapper::new();
let bytearray = PythonType::Custom("bytearray".to_string());
assert_eq!(
mapper.map_type(&bytearray),
RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::U8)))
);
}
#[test]
fn test_oserror_type_mapping() {
let mapper = TypeMapper::new();
let oserror = PythonType::Custom("OSError".to_string());
if let RustType::Custom(name) = mapper.map_type(&oserror) {
assert_eq!(name, "std::io::Error");
} else {
panic!("Expected Custom type for 'OSError'");
}
}
#[test]
fn test_ioerror_type_mapping() {
let mapper = TypeMapper::new();
let ioerror = PythonType::Custom("IOError".to_string());
if let RustType::Custom(name) = mapper.map_type(&ioerror) {
assert_eq!(name, "std::io::Error");
} else {
panic!("Expected Custom type for 'IOError'");
}
}
#[test]
fn test_filenotfounderror_type_mapping() {
let mapper = TypeMapper::new();
let fnf = PythonType::Custom("FileNotFoundError".to_string());
if let RustType::Custom(name) = mapper.map_type(&fnf) {
assert_eq!(name, "std::io::Error");
} else {
panic!("Expected Custom type for 'FileNotFoundError'");
}
}
#[test]
fn test_permissionerror_type_mapping() {
let mapper = TypeMapper::new();
let perr = PythonType::Custom("PermissionError".to_string());
if let RustType::Custom(name) = mapper.map_type(&perr) {
assert_eq!(name, "std::io::Error");
} else {
panic!("Expected Custom type for 'PermissionError'");
}
}
#[test]
fn test_namespace_type_mapping() {
let mapper = TypeMapper::new();
let ns = PythonType::Custom("Namespace".to_string());
if let RustType::Custom(name) = mapper.map_type(&ns) {
assert_eq!(name, "Args");
} else {
panic!("Expected Custom type for 'Namespace'");
}
let argparse_ns = PythonType::Custom("argparse.Namespace".to_string());
if let RustType::Custom(name) = mapper.map_type(&argparse_ns) {
assert_eq!(name, "Args");
} else {
panic!("Expected Custom type for 'argparse.Namespace'");
}
}
#[test]
fn test_object_type_mapping() {
let mapper = TypeMapper::new();
let object = PythonType::Custom("object".to_string());
assert_eq!(
mapper.map_type(&object),
RustType::Custom("DepylerValue".to_string())
);
}
#[test]
fn test_bare_dict_type_mapping() {
let mapper = TypeMapper::new();
let dict = PythonType::Custom("Dict".to_string());
if let RustType::HashMap(k, v) = mapper.map_type(&dict) {
assert_eq!(*k, RustType::String); assert_eq!(*v, RustType::Custom("DepylerValue".to_string())); } else {
panic!("Expected HashMap for 'Dict'");
}
}
#[test]
fn test_bare_list_type_mapping() {
let mapper = TypeMapper::new();
let list = PythonType::Custom("List".to_string());
if let RustType::Vec(inner) = mapper.map_type(&list) {
assert_eq!(*inner, RustType::Custom("DepylerValue".to_string())); } else {
panic!("Expected Vec for 'List'");
}
let list_lower = PythonType::Custom("list".to_string());
if let RustType::Vec(inner) = mapper.map_type(&list_lower) {
assert_eq!(*inner, RustType::Custom("DepylerValue".to_string())); } else {
panic!("Expected Vec for 'list'");
}
}
#[test]
fn test_bare_sequence_type_mapping() {
let mapper = TypeMapper::new();
let seq = PythonType::Custom("Sequence".to_string());
if let RustType::Vec(inner) = mapper.map_type(&seq) {
assert_eq!(*inner, RustType::Custom("DepylerValue".to_string()));
} else {
panic!("Expected Vec for 'Sequence'");
}
}
#[test]
fn test_generic_sequence_type_mapping() {
let mapper = TypeMapper::new();
let seq = PythonType::Generic {
base: "Sequence".to_string(),
params: vec![PythonType::Int],
};
assert_eq!(
mapper.map_type(&seq),
RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::I32)))
);
}
#[test]
fn test_bare_set_type_mapping() {
let mapper = TypeMapper::new();
let set = PythonType::Custom("Set".to_string());
if let RustType::HashSet(inner) = mapper.map_type(&set) {
assert_eq!(*inner, RustType::Custom("DepylerValue".to_string())); } else {
panic!("Expected HashSet for 'Set'");
}
}
#[test]
fn test_bare_tuple_type_mapping() {
let mapper = TypeMapper::new();
let tuple = PythonType::Custom("tuple".to_string());
if let RustType::Tuple(types) = mapper.map_type(&tuple) {
assert!(types.is_empty());
} else {
panic!("Expected empty Tuple for 'tuple'");
}
}
#[test]
fn test_single_letter_type_param() {
let mapper = TypeMapper::new();
let t = PythonType::Custom("T".to_string());
assert_eq!(mapper.map_type(&t), RustType::TypeParam("T".to_string()));
let v = PythonType::Custom("V".to_string());
assert_eq!(mapper.map_type(&v), RustType::TypeParam("V".to_string()));
let k = PythonType::Custom("K".to_string());
assert_eq!(mapper.map_type(&k), RustType::TypeParam("K".to_string()));
}
#[test]
fn test_file_type_mapping() {
let mapper = TypeMapper::new();
let file = PythonType::Custom("File".to_string());
if let RustType::Reference { mutable, inner, .. } = mapper.map_type(&file) {
assert!(mutable);
assert_eq!(*inner, RustType::Custom("impl std::io::Write".to_string()));
} else {
panic!("Expected Reference type for 'File'");
}
}
#[test]
fn test_const_generic_literal() {
assert_eq!(RustConstGeneric::Literal(10).to_rust_string(), "10");
assert_eq!(RustConstGeneric::Literal(0).to_rust_string(), "0");
}
#[test]
fn test_const_generic_parameter() {
assert_eq!(
RustConstGeneric::Parameter("N".to_string()).to_rust_string(),
"N"
);
}
#[test]
fn test_const_generic_expression() {
assert_eq!(
RustConstGeneric::Expression("N + 1".to_string()).to_rust_string(),
"N + 1"
);
}
#[test]
fn test_array_type_to_string() {
let array = RustType::Array {
element_type: Box::new(RustType::Primitive(PrimitiveType::I32)),
size: RustConstGeneric::Literal(5),
};
assert_eq!(array.to_rust_string(), "[i32; 5]");
}
#[test]
fn test_array_type_with_param() {
let array = RustType::Array {
element_type: Box::new(RustType::Primitive(PrimitiveType::F64)),
size: RustConstGeneric::Parameter("N".to_string()),
};
assert_eq!(array.to_rust_string(), "[f64; N]");
}
#[test]
fn test_callable_with_single_param() {
let mapper = TypeMapper::new();
let callable = PythonType::Generic {
base: "Callable".to_string(),
params: vec![
PythonType::List(Box::new(PythonType::Int)),
PythonType::String,
],
};
if let RustType::Custom(name) = mapper.map_type(&callable) {
assert!(name.contains("impl Fn"));
assert!(name.contains("i32"));
assert!(name.contains("String"));
} else {
panic!("Expected Custom type for Callable with params");
}
}
#[test]
fn test_callable_with_tuple_params() {
let mapper = TypeMapper::new();
let callable = PythonType::Generic {
base: "Callable".to_string(),
params: vec![
PythonType::Tuple(vec![PythonType::Int, PythonType::String]),
PythonType::Bool,
],
};
if let RustType::Custom(name) = mapper.map_type(&callable) {
assert!(name.contains("impl Fn"));
assert!(name.contains("i32"));
assert!(name.contains("String"));
assert!(name.contains("bool"));
} else {
panic!("Expected Custom type for Callable with tuple params");
}
}
#[test]
fn test_callable_with_no_return() {
let mapper = TypeMapper::new();
let callable = PythonType::Generic {
base: "Callable".to_string(),
params: vec![PythonType::Tuple(vec![PythonType::Int]), PythonType::None],
};
if let RustType::Custom(name) = mapper.map_type(&callable) {
assert!(name.contains("impl Fn"));
assert!(!name.contains("->"));
} else {
panic!("Expected Custom type for Callable with None return");
}
}
#[test]
fn test_callable_empty_params() {
let mapper = TypeMapper::new();
let callable = PythonType::Generic {
base: "Callable".to_string(),
params: vec![
PythonType::None, PythonType::Int,
],
};
if let RustType::Custom(name) = mapper.map_type(&callable) {
assert!(name.contains("impl Fn()"));
assert!(name.contains("i32"));
} else {
panic!("Expected Custom type for Callable with empty params");
}
}
#[test]
fn test_callable_unknown_params() {
let mapper = TypeMapper::new();
let callable = PythonType::Generic {
base: "Callable".to_string(),
params: vec![PythonType::Unknown, PythonType::Int],
};
if let RustType::Custom(name) = mapper.map_type(&callable) {
assert!(name.contains("impl Fn()"));
} else {
panic!("Expected Custom type for Callable with unknown params");
}
}
#[test]
fn test_bare_callable_generic() {
let mapper = TypeMapper::new();
let callable = PythonType::Generic {
base: "Callable".to_string(),
params: vec![],
};
if let RustType::Custom(name) = mapper.map_type(&callable) {
assert_eq!(name, "impl Fn()");
} else {
panic!("Expected Custom type for bare Callable");
}
}
#[test]
fn test_generator_type_mapping() {
let mapper = TypeMapper::new();
let gen = PythonType::Generic {
base: "Generator".to_string(),
params: vec![PythonType::Int, PythonType::None, PythonType::None],
};
if let RustType::Custom(name) = mapper.map_type(&gen) {
assert!(name.contains("impl Iterator"));
assert!(name.contains("Item=i32"));
} else {
panic!("Expected Custom type for Generator");
}
}
#[test]
fn test_iterator_type_mapping() {
let mapper = TypeMapper::new();
let iter = PythonType::Generic {
base: "Iterator".to_string(),
params: vec![PythonType::String],
};
if let RustType::Custom(name) = mapper.map_type(&iter) {
assert!(name.contains("impl Iterator"));
assert!(name.contains("Item=String"));
} else {
panic!("Expected Custom type for Iterator");
}
}
#[test]
fn test_iterable_type_mapping() {
let mapper = TypeMapper::new();
let iterable = PythonType::Generic {
base: "Iterable".to_string(),
params: vec![PythonType::Int],
};
if let RustType::Custom(name) = mapper.map_type(&iterable) {
assert!(name.contains("impl IntoIterator"));
assert!(name.contains("Item=i32"));
} else {
panic!("Expected Custom type for Iterable");
}
}
#[test]
fn test_deque_type_mapping() {
let mapper = TypeMapper::new();
let deque = PythonType::Generic {
base: "deque".to_string(),
params: vec![PythonType::Int],
};
if let RustType::Custom(name) = mapper.map_type(&deque) {
assert!(name.contains("VecDeque"));
assert!(name.contains("i32"));
} else {
panic!("Expected Custom type for deque");
}
}
#[test]
fn test_bare_deque_mapping() {
let mapper = TypeMapper::new();
let deque = PythonType::Custom("deque".to_string());
if let RustType::Custom(name) = mapper.map_type(&deque) {
assert!(name.contains("VecDeque"));
} else {
panic!("Expected Custom type for bare deque");
}
}
#[test]
fn test_counter_mapping() {
let mapper = TypeMapper::new();
let counter = PythonType::Custom("Counter".to_string());
if let RustType::HashMap(k, v) = mapper.map_type(&counter) {
assert_eq!(*k, RustType::String);
assert_eq!(*v, RustType::Primitive(PrimitiveType::I32));
} else {
panic!("Expected HashMap for Counter");
}
}
#[test]
fn test_type_param_mapping() {
let mapper = TypeMapper::new();
let type_t = PythonType::Generic {
base: "type".to_string(),
params: vec![PythonType::Custom("MyClass".to_string())],
};
if let RustType::Custom(name) = mapper.map_type(&type_t) {
assert!(name.contains("PhantomData"));
assert!(name.contains("MyClass"));
} else {
panic!("Expected Custom type for type[T]");
}
}
#[test]
fn test_union_type_mapping() {
let mapper = TypeMapper::new();
let union = PythonType::Union(vec![PythonType::Int, PythonType::String]);
if let RustType::Enum { name: _, variants } = mapper.map_type(&union) {
assert_eq!(variants.len(), 2);
} else {
panic!("Expected Enum for Union");
}
}
#[test]
fn test_union_with_none_is_optional() {
let mapper = TypeMapper::new();
let union = PythonType::Union(vec![PythonType::Int, PythonType::None]);
if let RustType::Option(inner) = mapper.map_type(&union) {
assert_eq!(*inner, RustType::Primitive(PrimitiveType::I32));
} else {
panic!("Expected Option for Union[T, None]");
}
let union2 = PythonType::Union(vec![PythonType::None, PythonType::String]);
if let RustType::Option(inner) = mapper.map_type(&union2) {
assert_eq!(*inner, RustType::String);
} else {
panic!("Expected Option for Union[None, T]");
}
}
#[test]
fn test_os_error_mapping() {
let mapper = TypeMapper::new();
for err in ["OSError", "IOError", "FileNotFoundError", "PermissionError"] {
let error = PythonType::Custom(err.to_string());
if let RustType::Custom(name) = mapper.map_type(&error) {
assert_eq!(name, "std::io::Error");
} else {
panic!("Expected std::io::Error for {}", err);
}
}
}
#[test]
fn test_general_exception_mapping() {
let mapper = TypeMapper::new();
for exc in [
"ValueError",
"TypeError",
"KeyError",
"IndexError",
"RuntimeError",
] {
let error = PythonType::Custom(exc.to_string());
if let RustType::Custom(name) = mapper.map_type(&error) {
assert_eq!(name, "Box<dyn std::error::Error>");
} else {
panic!("Expected Box<dyn Error> for {}", exc);
}
}
}
#[test]
fn test_object_builtins_type_mapping() {
let mapper = TypeMapper::new();
let obj = PythonType::Custom("builtins.object".to_string());
assert_eq!(
mapper.map_type(&obj),
RustType::Custom("DepylerValue".to_string())
);
}
#[test]
fn test_str_type_to_string() {
let str_type = RustType::Str {
lifetime: Some("'a".to_string()),
};
assert_eq!(str_type.to_rust_string(), "&'a str");
let str_no_lt = RustType::Str { lifetime: None };
assert_eq!(str_no_lt.to_rust_string(), "&str");
}
#[test]
fn test_cow_type_to_string() {
let cow = RustType::Cow {
lifetime: "'static".to_string(),
};
assert_eq!(cow.to_rust_string(), "Cow<'static, str>");
}
#[test]
fn test_reference_type_to_string() {
let ref_type = RustType::Reference {
lifetime: Some("'a".to_string()),
mutable: false,
inner: Box::new(RustType::String),
};
assert_eq!(ref_type.to_rust_string(), "&'a String");
let mut_ref = RustType::Reference {
lifetime: None,
mutable: true,
inner: Box::new(RustType::Primitive(PrimitiveType::I32)),
};
assert_eq!(mut_ref.to_rust_string(), "&mut i32");
}
#[test]
fn test_result_type_to_string() {
let result = RustType::Result(
Box::new(RustType::String),
Box::new(RustType::Custom("Error".to_string())),
);
assert_eq!(result.to_rust_string(), "Result<String, Error>");
}
#[test]
fn test_generic_type_to_string() {
let generic = RustType::Generic {
base: "MyType".to_string(),
params: vec![RustType::Primitive(PrimitiveType::I32), RustType::String],
};
assert_eq!(generic.to_rust_string(), "MyType<i32, String>");
}
#[test]
fn test_enum_type_to_string() {
let enum_type = RustType::Enum {
name: "MyUnion".to_string(),
variants: vec![
("Int".to_string(), RustType::Primitive(PrimitiveType::I32)),
("Str".to_string(), RustType::String),
],
};
assert_eq!(enum_type.to_rust_string(), "MyUnion");
}
#[test]
fn test_unsupported_type_to_string() {
let unsup = RustType::Unsupported("SomeType".to_string());
assert_eq!(unsup.to_rust_string(), "/* unsupported: SomeType */");
}
#[test]
fn test_type_param_to_string() {
let param = RustType::TypeParam("T".to_string());
assert_eq!(param.to_rust_string(), "T");
}
#[test]
fn test_all_primitive_types() {
let primitives = [
(PrimitiveType::Bool, "bool"),
(PrimitiveType::I8, "i8"),
(PrimitiveType::I16, "i16"),
(PrimitiveType::I32, "i32"),
(PrimitiveType::I64, "i64"),
(PrimitiveType::I128, "i128"),
(PrimitiveType::ISize, "isize"),
(PrimitiveType::U8, "u8"),
(PrimitiveType::U16, "u16"),
(PrimitiveType::U32, "u32"),
(PrimitiveType::U64, "u64"),
(PrimitiveType::U128, "u128"),
(PrimitiveType::USize, "usize"),
(PrimitiveType::F32, "f32"),
(PrimitiveType::F64, "f64"),
];
for (prim, expected) in primitives {
let rust_type = RustType::Primitive(prim);
assert_eq!(rust_type.to_rust_string(), expected);
}
}
#[test]
fn test_type_var_mapping() {
let mapper = TypeMapper::new();
let type_var = PythonType::TypeVar("T".to_string());
assert_eq!(
mapper.map_type(&type_var),
RustType::TypeParam("T".to_string())
);
}
#[test]
fn test_generic_list_mapping() {
let mapper = TypeMapper::new();
let list = PythonType::Generic {
base: "List".to_string(),
params: vec![PythonType::String],
};
assert_eq!(
mapper.map_type(&list),
RustType::Vec(Box::new(RustType::String))
);
}
#[test]
fn test_generic_dict_mapping() {
let mapper = TypeMapper::new();
let dict = PythonType::Generic {
base: "Dict".to_string(),
params: vec![PythonType::String, PythonType::Int],
};
assert_eq!(
mapper.map_type(&dict),
RustType::HashMap(
Box::new(RustType::String),
Box::new(RustType::Primitive(PrimitiveType::I32))
)
);
}
#[test]
fn test_array_type_mapping() {
let mapper = TypeMapper::new();
let array = PythonType::Array {
element_type: Box::new(PythonType::Int),
size: ConstGeneric::Literal(10),
};
if let RustType::Array { element_type, size } = mapper.map_type(&array) {
assert_eq!(*element_type, RustType::Primitive(PrimitiveType::I32));
assert_eq!(size, RustConstGeneric::Literal(10));
} else {
panic!("Expected Array type");
}
}
#[test]
fn test_array_with_const_param() {
let mapper = TypeMapper::new();
let array = PythonType::Array {
element_type: Box::new(PythonType::Float),
size: ConstGeneric::Parameter("N".to_string()),
};
if let RustType::Array { element_type, size } = mapper.map_type(&array) {
assert_eq!(*element_type, RustType::Primitive(PrimitiveType::F64));
assert_eq!(size, RustConstGeneric::Parameter("N".to_string()));
} else {
panic!("Expected Array type");
}
}
#[test]
fn test_array_with_expression() {
let mapper = TypeMapper::new();
let array = PythonType::Array {
element_type: Box::new(PythonType::Bool),
size: ConstGeneric::Expression("N + 1".to_string()),
};
if let RustType::Array { element_type, size } = mapper.map_type(&array) {
assert_eq!(*element_type, RustType::Primitive(PrimitiveType::Bool));
assert_eq!(size, RustConstGeneric::Expression("N + 1".to_string()));
} else {
panic!("Expected Array type");
}
}
#[test]
fn test_needs_reference_option() {
let mapper = TypeMapper::new();
assert!(
!mapper.needs_reference(&RustType::Option(Box::new(RustType::Primitive(
PrimitiveType::I32
))))
);
}
#[test]
fn test_needs_reference_result() {
let mapper = TypeMapper::new();
let result = RustType::Result(
Box::new(RustType::String),
Box::new(RustType::Custom("Error".to_string())),
);
assert!(!mapper.needs_reference(&result));
}
#[test]
fn test_needs_reference_array() {
let mapper = TypeMapper::new();
let array = RustType::Array {
element_type: Box::new(RustType::Primitive(PrimitiveType::I32)),
size: RustConstGeneric::Literal(100),
};
assert!(mapper.needs_reference(&array));
}
#[test]
fn test_can_copy_option_not_copy() {
let mapper = TypeMapper::new();
assert!(
!mapper.can_copy(&RustType::Option(Box::new(RustType::Primitive(
PrimitiveType::I32
))))
);
}
#[test]
fn test_can_copy_reference_not_copy() {
let mapper = TypeMapper::new();
let ref_type = RustType::Reference {
lifetime: None,
mutable: false,
inner: Box::new(RustType::String),
};
assert!(!mapper.can_copy(&ref_type));
}
#[test]
fn test_can_copy_small_array() {
let mapper = TypeMapper::new();
let small_array = RustType::Array {
element_type: Box::new(RustType::Primitive(PrimitiveType::I32)),
size: RustConstGeneric::Literal(16),
};
assert!(mapper.can_copy(&small_array));
let large_array = RustType::Array {
element_type: Box::new(RustType::Primitive(PrimitiveType::I32)),
size: RustConstGeneric::Literal(100),
};
assert!(!mapper.can_copy(&large_array));
let param_array = RustType::Array {
element_type: Box::new(RustType::Primitive(PrimitiveType::I32)),
size: RustConstGeneric::Parameter("N".to_string()),
};
assert!(!mapper.can_copy(¶m_array));
}
}
#[cfg(test)]
mod coverage_tests {
use super::*;
use depyler_hir::hir::{ConstGeneric, Type as PythonType};
#[test]
fn test_nested_list_of_lists() {
let mapper = TypeMapper::new();
let nested = PythonType::List(Box::new(PythonType::List(Box::new(PythonType::Int))));
let result = mapper.map_type(&nested);
assert_eq!(
result,
RustType::Vec(Box::new(RustType::Vec(Box::new(RustType::Primitive(
PrimitiveType::I32
)))))
);
assert_eq!(result.to_rust_string(), "Vec<Vec<i32>>");
}
#[test]
fn test_nested_dict_with_list_values() {
let mapper = TypeMapper::new();
let nested = PythonType::Dict(
Box::new(PythonType::String),
Box::new(PythonType::List(Box::new(PythonType::Int))),
);
let result = mapper.map_type(&nested);
assert_eq!(result.to_rust_string(), "HashMap<String, Vec<i32>>");
}
#[test]
fn test_optional_of_list() {
let mapper = TypeMapper::new();
let nested =
PythonType::Optional(Box::new(PythonType::List(Box::new(PythonType::String))));
let result = mapper.map_type(&nested);
assert_eq!(result.to_rust_string(), "Option<Vec<String>>");
}
#[test]
fn test_dict_with_tuple_key() {
let mapper = TypeMapper::new();
let nested = PythonType::Dict(
Box::new(PythonType::Tuple(vec![PythonType::Int, PythonType::Int])),
Box::new(PythonType::String),
);
let result = mapper.map_type(&nested);
assert_eq!(result.to_rust_string(), "HashMap<(i32, i32), String>");
}
#[test]
fn test_list_of_optional() {
let mapper = TypeMapper::new();
let nested = PythonType::List(Box::new(PythonType::Optional(Box::new(PythonType::Int))));
let result = mapper.map_type(&nested);
assert_eq!(result.to_rust_string(), "Vec<Option<i32>>");
}
#[test]
fn test_final_int_unwraps() {
let mapper = TypeMapper::new();
let final_int = PythonType::Final(Box::new(PythonType::Int));
assert_eq!(
mapper.map_type(&final_int),
RustType::Primitive(PrimitiveType::I32)
);
}
#[test]
fn test_final_string_unwraps() {
let mapper = TypeMapper::new();
let final_str = PythonType::Final(Box::new(PythonType::String));
assert_eq!(mapper.map_type(&final_str), RustType::String);
}
#[test]
fn test_final_list_unwraps() {
let mapper = TypeMapper::new();
let final_list = PythonType::Final(Box::new(PythonType::List(Box::new(PythonType::Int))));
assert_eq!(
mapper.map_type(&final_list),
RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::I32)))
);
}
#[test]
fn test_list_unknown_uses_fallback() {
let mapper = TypeMapper::new();
let list = PythonType::List(Box::new(PythonType::Unknown));
if let RustType::Vec(inner) = mapper.map_type(&list) {
assert_eq!(*inner, RustType::Custom("DepylerValue".to_string()));
} else {
panic!("Expected Vec");
}
}
#[test]
fn test_dict_unknown_key_uses_string() {
let mapper = TypeMapper::new();
let dict = PythonType::Dict(Box::new(PythonType::Unknown), Box::new(PythonType::Int));
if let RustType::HashMap(k, _v) = mapper.map_type(&dict) {
assert_eq!(*k, RustType::String);
} else {
panic!("Expected HashMap");
}
}
#[test]
fn test_dict_unknown_value_uses_fallback() {
let mapper = TypeMapper::new();
let dict = PythonType::Dict(Box::new(PythonType::String), Box::new(PythonType::Unknown));
if let RustType::HashMap(_k, v) = mapper.map_type(&dict) {
assert_eq!(*v, RustType::Custom("DepylerValue".to_string()));
} else {
panic!("Expected HashMap");
}
}
#[test]
fn test_set_unknown_uses_string() {
let mapper = TypeMapper::new();
let set = PythonType::Set(Box::new(PythonType::Unknown));
if let RustType::HashSet(inner) = mapper.map_type(&set) {
assert_eq!(*inner, RustType::String);
} else {
panic!("Expected HashSet");
}
}
#[test]
fn test_nasa_mode_toggle() {
let nasa = TypeMapper::new(); assert!(nasa.nasa_mode);
let non_nasa = TypeMapper::new().with_nasa_mode(false);
assert!(!non_nasa.nasa_mode);
}
#[test]
fn test_non_nasa_mode_unknown_fallback() {
let mapper = TypeMapper::new().with_nasa_mode(false);
let unknown = PythonType::Unknown;
assert_eq!(
mapper.map_type(&unknown),
RustType::Custom("serde_json::Value".to_string())
);
}
#[test]
fn test_non_nasa_mode_list_unknown() {
let mapper = TypeMapper::new().with_nasa_mode(false);
let list = PythonType::List(Box::new(PythonType::Unknown));
if let RustType::Vec(inner) = mapper.map_type(&list) {
assert_eq!(*inner, RustType::Custom("serde_json::Value".to_string()));
} else {
panic!("Expected Vec");
}
}
#[test]
fn test_return_type_unknown_is_unit() {
let mapper = TypeMapper::new();
assert_eq!(mapper.map_return_type(&PythonType::Unknown), RustType::Unit);
}
#[test]
fn test_return_type_none_is_unit() {
let mapper = TypeMapper::new();
assert_eq!(mapper.map_return_type(&PythonType::None), RustType::Unit);
}
#[test]
fn test_return_type_string() {
let mapper = TypeMapper::new();
assert_eq!(
mapper.map_return_type(&PythonType::String),
RustType::String
);
}
#[test]
fn test_return_type_list() {
let mapper = TypeMapper::new();
let list = PythonType::List(Box::new(PythonType::Int));
assert_eq!(
mapper.map_return_type(&list),
RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::I32)))
);
}
#[test]
fn test_map_const_generic_literal() {
let mapper = TypeMapper::new();
assert_eq!(
mapper.map_const_generic(&ConstGeneric::Literal(42)),
RustConstGeneric::Literal(42)
);
}
#[test]
fn test_map_const_generic_parameter() {
let mapper = TypeMapper::new();
assert_eq!(
mapper.map_const_generic(&ConstGeneric::Parameter("N".to_string())),
RustConstGeneric::Parameter("N".to_string())
);
}
#[test]
fn test_map_const_generic_expression() {
let mapper = TypeMapper::new();
assert_eq!(
mapper.map_const_generic(&ConstGeneric::Expression("N * 2".to_string())),
RustConstGeneric::Expression("N * 2".to_string())
);
}
#[test]
fn test_can_copy_empty_tuple() {
let mapper = TypeMapper::new();
assert!(mapper.can_copy(&RustType::Tuple(vec![])));
}
#[test]
fn test_can_copy_single_element_tuple() {
let mapper = TypeMapper::new();
assert!(mapper.can_copy(&RustType::Tuple(vec![RustType::Primitive(
PrimitiveType::F64
)])));
}
#[test]
fn test_can_copy_mixed_tuple_not_copy() {
let mapper = TypeMapper::new();
let tuple = RustType::Tuple(vec![
RustType::Primitive(PrimitiveType::I32),
RustType::Vec(Box::new(RustType::String)),
]);
assert!(!mapper.can_copy(&tuple));
}
#[test]
fn test_needs_reference_hashset() {
let mapper = TypeMapper::new();
assert!(mapper.needs_reference(&RustType::HashSet(Box::new(RustType::String))));
}
#[test]
fn test_needs_reference_unit() {
let mapper = TypeMapper::new();
assert!(!mapper.needs_reference(&RustType::Unit));
}
#[test]
fn test_needs_reference_custom() {
let mapper = TypeMapper::new();
assert!(!mapper.needs_reference(&RustType::Custom("MyType".to_string())));
}
#[test]
fn test_hashset_to_rust_string() {
let hs = RustType::HashSet(Box::new(RustType::String));
assert_eq!(hs.to_rust_string(), "HashSet<String>");
}
#[test]
fn test_option_of_option_to_rust_string() {
let oo = RustType::Option(Box::new(RustType::Option(Box::new(RustType::Primitive(
PrimitiveType::I32,
)))));
assert_eq!(oo.to_rust_string(), "Option<Option<i32>>");
}
#[test]
fn test_vec_of_hashmap_to_rust_string() {
let vh = RustType::Vec(Box::new(RustType::HashMap(
Box::new(RustType::String),
Box::new(RustType::Primitive(PrimitiveType::I32)),
)));
assert_eq!(vh.to_rust_string(), "Vec<HashMap<String, i32>>");
}
#[test]
fn test_union_three_types() {
let mapper = TypeMapper::new();
let union = PythonType::Union(vec![
PythonType::Int,
PythonType::String,
PythonType::Bool,
]);
if let RustType::Enum { variants, .. } = mapper.map_type(&union) {
assert_eq!(variants.len(), 3);
assert_eq!(variants[0].0, "Integer");
assert_eq!(variants[1].0, "Text");
assert_eq!(variants[2].0, "Boolean");
} else {
panic!("Expected Enum for 3-way Union");
}
}
#[test]
fn test_union_with_float_variant_name() {
let mapper = TypeMapper::new();
let union = PythonType::Union(vec![PythonType::Float, PythonType::Int]);
if let RustType::Enum { variants, .. } = mapper.map_type(&union) {
assert_eq!(variants[0].0, "Float");
assert_eq!(variants[1].0, "Integer");
} else {
panic!("Expected Enum");
}
}
#[test]
fn test_i64_width_in_list() {
let mapper = TypeMapper::new().with_i64();
let list = PythonType::List(Box::new(PythonType::Int));
assert_eq!(
mapper.map_type(&list),
RustType::Vec(Box::new(RustType::Primitive(PrimitiveType::I64)))
);
}
#[test]
fn test_i64_width_in_dict_value() {
let mapper = TypeMapper::new().with_i64();
let dict = PythonType::Dict(Box::new(PythonType::String), Box::new(PythonType::Int));
if let RustType::HashMap(_, v) = mapper.map_type(&dict) {
assert_eq!(*v, RustType::Primitive(PrimitiveType::I64));
} else {
panic!("Expected HashMap");
}
}
#[test]
fn test_i64_width_in_optional() {
let mapper = TypeMapper::new().with_i64();
let opt = PythonType::Optional(Box::new(PythonType::Int));
assert_eq!(
mapper.map_type(&opt),
RustType::Option(Box::new(RustType::Primitive(PrimitiveType::I64)))
);
}
}