//! Rust type -> Nim type mapping. //! //! Integer width is preserved exactly. Anything that cannot be represented //! faithfully in Nim is reported as an error rather than approximated: a //! silently widened integer would change the meaning of wrapping arithmetic, //! which is precisely the kind of code people write in Rust. use syn::{GenericArgument, PathArguments, Type, TypeParamBound}; #[derive(Debug, Clone, PartialEq)] pub enum Nim { Prim(String), Seq(Box), OpenArray(Box), Array(usize, Box), Tuple(Vec), Named(String, Vec), Var(Box), Unit, } impl Nim { pub fn render(&self) -> String { match self { Nim::Prim(s) => s.clone(), Nim::Seq(t) => format!("seq[{}]", t.render()), Nim::OpenArray(t) => format!("openArray[{}]", t.render()), Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()), Nim::Tuple(ts) => { let inner: Vec = ts.iter().map(|t| t.render()).collect(); format!("({})", inner.join(", ")) } Nim::Named(n, args) if args.is_empty() => n.clone(), Nim::Named(n, args) => { let inner: Vec = args.iter().map(|t| t.render()).collect(); format!("{}[{}]", n, inner.join(", ")) } Nim::Var(t) => format!("var {}", t.render()), Nim::Unit => "void".into(), } } /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same /// type in an owned position (a field, a return value) must be `seq[T]`. pub fn owned(self) -> Nim { match self { Nim::OpenArray(t) => Nim::Seq(t), Nim::Var(t) => t.owned(), other => other, } } pub fn is_integer(&self) -> bool { matches!(self, Nim::Prim(p) if matches!(p.as_str(), "int8"|"int16"|"int32"|"int64"|"int"| "uint8"|"uint16"|"uint32"|"uint64"|"uint")) } pub fn is_unsigned(&self) -> bool { matches!(self, Nim::Prim(p) if p.starts_with("uint")) } } pub fn prim(name: &str) -> Option { let mapped = match name { "i8" => "int8", "i16" => "int16", "i32" => "int32", "i64" => "int64", "isize" => "int", "u8" => "uint8", "u16" => "uint16", "u32" => "uint32", "u64" => "uint64", "usize" => "uint", "f32" => "float32", "f64" => "float64", "bool" => "bool", "char" => "Rune", "str" | "String" => "string", _ => return None, }; Some(Nim::Prim(mapped.into())) } /// Types we refuse rather than approximate. pub fn rejected(name: &str) -> Option<&'static str> { match name { "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"), _ => None, } } pub fn map(t: &Type) -> Result { match t { Type::Path(p) => { let seg = p .path .segments .last() .ok_or_else(|| "empty type path".to_string())?; let name = seg.ident.to_string(); if let Some(why) = rejected(&name) { return Err(format!("unsupported type `{}`: {}", name, why)); } let args: Vec = match &seg.arguments { PathArguments::AngleBracketed(a) => a .args .iter() .filter_map(|g| match g { GenericArgument::Type(t) => Some(map(t)), _ => None, }) .collect::>()?, _ => vec![], }; match (name.as_str(), args.len()) { ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))), ("Option", 1) => Ok(Nim::Named("Option".into(), args)), ("Result", 2) => Ok(Nim::Named("Result".into(), args)), ("Box", 1) => Ok(args[0].clone()), _ => { if let Some(p) = prim(&name) { Ok(p) } else { Ok(Nim::Named(name, args)) } } } } // &T is a value in Nim; &mut T becomes a `var` parameter. The caller // decides whether a `var` is legal in the position it is used. Type::Reference(r) => { let inner = map(&r.elem)?; if r.mutability.is_some() { Ok(Nim::Var(Box::new(inner))) } else { Ok(inner) } } Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), Type::Array(a) => { let len = match &a.len { syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(i), .. }) => i .base10_parse::() .map_err(|e| format!("array length: {}", e))?, _ => return Err("array length must be a literal".into()), }; Ok(Nim::Array(len, Box::new(map(&a.elem)?))) } Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit), Type::Tuple(t) => Ok(Nim::Tuple( t.elems.iter().map(map).collect::>()?, )), Type::Paren(p) => map(&p.elem), Type::Group(g) => map(&g.elem), Type::ImplTrait(i) => { // `impl AsRef<[u8]>` and friends: fall back to the bound's own // shape where we can recognise it, since Nim has no impl-trait. for b in &i.bounds { if let TypeParamBound::Trait(tb) = b { if let Some(seg) = tb.path.segments.last() { if seg.ident == "AsRef" || seg.ident == "Into" { if let PathArguments::AngleBracketed(a) = &seg.arguments { for g in &a.args { if let GenericArgument::Type(t) = g { return map(t); } } } } } } } Err("unsupported `impl Trait` type".into()) } Type::Infer(_) => Err("inferred type in a position that needs a name".into()), other => Err(format!("unsupported type form: {:?}", discriminant(other))), } } fn discriminant(t: &Type) -> &'static str { match t { Type::BareFn(_) => "bare fn", Type::Ptr(_) => "raw pointer", Type::TraitObject(_) => "trait object", Type::Never(_) => "never", Type::Macro(_) => "macro", _ => "other", } }