| Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago | 1 | //! Rust type -> Nim type mapping. |
| 2 | //! |
| 3 | //! Integer width is preserved exactly. Anything that cannot be represented |
| 4 | //! faithfully in Nim is reported as an error rather than approximated: a |
| 5 | //! silently widened integer would change the meaning of wrapping arithmetic, |
| 6 | //! which is precisely the kind of code people write in Rust. |
| 7 | |
| 8 | use syn::{GenericArgument, PathArguments, Type, TypeParamBound}; |
| 9 | |
| 10 | #[derive(Debug, Clone, PartialEq)] |
| 11 | pub enum Nim { |
| 12 | Prim(String), |
| 13 | Seq(Box<Nim>), |
| 14 | OpenArray(Box<Nim>), |
| 15 | Array(usize, Box<Nim>), |
| 16 | Tuple(Vec<Nim>), |
| 17 | Named(String, Vec<Nim>), |
| 18 | Var(Box<Nim>), |
| 19 | Unit, |
| 20 | } |
| 21 | |
| 22 | impl Nim { |
| 23 | pub fn render(&self) -> String { |
| 24 | match self { |
| 25 | Nim::Prim(s) => s.clone(), |
| 26 | Nim::Seq(t) => format!("seq[{}]", t.render()), |
| 27 | Nim::OpenArray(t) => format!("openArray[{}]", t.render()), |
| 28 | Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()), |
| 29 | Nim::Tuple(ts) => { |
| 30 | let inner: Vec<String> = ts.iter().map(|t| t.render()).collect(); |
| 31 | format!("({})", inner.join(", ")) |
| 32 | } |
| 33 | Nim::Named(n, args) if args.is_empty() => n.clone(), |
| 34 | Nim::Named(n, args) => { |
| 35 | let inner: Vec<String> = args.iter().map(|t| t.render()).collect(); |
| 36 | format!("{}[{}]", n, inner.join(", ")) |
| 37 | } |
| 38 | Nim::Var(t) => format!("var {}", t.render()), |
| 39 | Nim::Unit => "void".into(), |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same |
| 44 | /// type in an owned position (a field, a return value) must be `seq[T]`. |
| 45 | pub fn owned(self) -> Nim { |
| 46 | match self { |
| 47 | Nim::OpenArray(t) => Nim::Seq(t), |
| 48 | Nim::Var(t) => t.owned(), |
| 49 | other => other, |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | pub fn is_integer(&self) -> bool { |
| 54 | matches!(self, Nim::Prim(p) if matches!(p.as_str(), |
| 55 | "int8"|"int16"|"int32"|"int64"|"int"| |
| 56 | "uint8"|"uint16"|"uint32"|"uint64"|"uint")) |
| 57 | } |
| 58 | |
| 59 | pub fn is_unsigned(&self) -> bool { |
| 60 | matches!(self, Nim::Prim(p) if p.starts_with("uint")) |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | pub fn prim(name: &str) -> Option<Nim> { |
| 65 | let mapped = match name { |
| 66 | "i8" => "int8", |
| 67 | "i16" => "int16", |
| 68 | "i32" => "int32", |
| 69 | "i64" => "int64", |
| 70 | "isize" => "int", |
| 71 | "u8" => "uint8", |
| 72 | "u16" => "uint16", |
| 73 | "u32" => "uint32", |
| 74 | "u64" => "uint64", |
| 75 | "usize" => "uint", |
| 76 | "f32" => "float32", |
| 77 | "f64" => "float64", |
| 78 | "bool" => "bool", |
| 79 | "char" => "Rune", |
| 80 | "str" | "String" => "string", |
| 81 | _ => return None, |
| 82 | }; |
| 83 | Some(Nim::Prim(mapped.into())) |
| 84 | } |
| 85 | |
| 86 | /// Types we refuse rather than approximate. |
| 87 | pub fn rejected(name: &str) -> Option<&'static str> { |
| 88 | match name { |
| 89 | "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"), |
| 90 | _ => None, |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | pub fn map(t: &Type) -> Result<Nim, String> { |
| 95 | match t { |
| 96 | Type::Path(p) => { |
| 97 | let seg = p |
| 98 | .path |
| 99 | .segments |
| 100 | .last() |
| 101 | .ok_or_else(|| "empty type path".to_string())?; |
| 102 | let name = seg.ident.to_string(); |
| 103 | |
| 104 | if let Some(why) = rejected(&name) { |
| 105 | return Err(format!("unsupported type `{}`: {}", name, why)); |
| 106 | } |
| 107 | |
| 108 | let args: Vec<Nim> = match &seg.arguments { |
| 109 | PathArguments::AngleBracketed(a) => a |
| 110 | .args |
| 111 | .iter() |
| 112 | .filter_map(|g| match g { |
| 113 | GenericArgument::Type(t) => Some(map(t)), |
| 114 | _ => None, |
| 115 | }) |
| 116 | .collect::<Result<_, _>>()?, |
| 117 | _ => vec![], |
| 118 | }; |
| 119 | |
| 120 | match (name.as_str(), args.len()) { |
| 121 | ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))), |
| 122 | ("Option", 1) => Ok(Nim::Named("Option".into(), args)), |
| 123 | ("Result", 2) => Ok(Nim::Named("Result".into(), args)), |
| 124 | ("Box", 1) => Ok(args[0].clone()), |
| 125 | _ => { |
| 126 | if let Some(p) = prim(&name) { |
| 127 | Ok(p) |
| 128 | } else { |
| 129 | Ok(Nim::Named(name, args)) |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | // &T is a value in Nim; &mut T becomes a `var` parameter. The caller |
| 135 | // decides whether a `var` is legal in the position it is used. |
| 136 | Type::Reference(r) => { |
| 137 | let inner = map(&r.elem)?; |
| 138 | if r.mutability.is_some() { |
| 139 | Ok(Nim::Var(Box::new(inner))) |
| 140 | } else { |
| 141 | Ok(inner) |
| 142 | } |
| 143 | } |
| 144 | Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), |
| 145 | Type::Array(a) => { |
| 146 | let len = match &a.len { |
| 147 | syn::Expr::Lit(syn::ExprLit { |
| 148 | lit: syn::Lit::Int(i), |
| 149 | .. |
| 150 | }) => i |
| 151 | .base10_parse::<usize>() |
| 152 | .map_err(|e| format!("array length: {}", e))?, |
| 153 | _ => return Err("array length must be a literal".into()), |
| 154 | }; |
| 155 | Ok(Nim::Array(len, Box::new(map(&a.elem)?))) |
| 156 | } |
| 157 | Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit), |
| 158 | Type::Tuple(t) => Ok(Nim::Tuple( |
| 159 | t.elems.iter().map(map).collect::<Result<_, _>>()?, |
| 160 | )), |
| 161 | Type::Paren(p) => map(&p.elem), |
| 162 | Type::Group(g) => map(&g.elem), |
| 163 | Type::ImplTrait(i) => { |
| 164 | // `impl AsRef<[u8]>` and friends: fall back to the bound's own |
| 165 | // shape where we can recognise it, since Nim has no impl-trait. |
| 166 | for b in &i.bounds { |
| 167 | if let TypeParamBound::Trait(tb) = b { |
| 168 | if let Some(seg) = tb.path.segments.last() { |
| 169 | if seg.ident == "AsRef" || seg.ident == "Into" { |
| 170 | if let PathArguments::AngleBracketed(a) = &seg.arguments { |
| 171 | for g in &a.args { |
| 172 | if let GenericArgument::Type(t) = g { |
| 173 | return map(t); |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | Err("unsupported `impl Trait` type".into()) |
| 182 | } |
| 183 | Type::Infer(_) => Err("inferred type in a position that needs a name".into()), |
| 184 | other => Err(format!("unsupported type form: {:?}", discriminant(other))), |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | fn discriminant(t: &Type) -> &'static str { |
| 189 | match t { |
| 190 | Type::BareFn(_) => "bare fn", |
| 191 | Type::Ptr(_) => "raw pointer", |
| 192 | Type::TraitObject(_) => "trait object", |
| 193 | Type::Never(_) => "never", |
| 194 | Type::Macro(_) => "macro", |
| 195 | _ => "other", |
| 196 | } |
| 197 | } |