| 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>), |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago | 19 | /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling |
| 20 | /// convention (`closure`), which accepts both a plain top-level proc and |
| 21 | /// a closure that captures -- and Rust's `impl Fn` accepts both too. |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago | 22 | Proc(Vec<Nim>, Box<Nim>), |
| Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago | 23 | Unit, |
| 24 | } |
| 25 | |
| 26 | impl Nim { |
| 27 | pub fn render(&self) -> String { |
| 28 | match self { |
| 29 | Nim::Prim(s) => s.clone(), |
| 30 | Nim::Seq(t) => format!("seq[{}]", t.render()), |
| 31 | Nim::OpenArray(t) => format!("openArray[{}]", t.render()), |
| 32 | Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()), |
| 33 | Nim::Tuple(ts) => { |
| 34 | let inner: Vec<String> = ts.iter().map(|t| t.render()).collect(); |
| 35 | format!("({})", inner.join(", ")) |
| 36 | } |
| 37 | Nim::Named(n, args) if args.is_empty() => n.clone(), |
| 38 | Nim::Named(n, args) => { |
| 39 | let inner: Vec<String> = args.iter().map(|t| t.render()).collect(); |
| 40 | format!("{}[{}]", n, inner.join(", ")) |
| 41 | } |
| 42 | Nim::Var(t) => format!("var {}", t.render()), |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago | 43 | Nim::Proc(args, ret) => { |
| Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago | 44 | // Nim's proc types name their parameters even when the name is |
| 45 | // never used. |
| 46 | let inner: Vec<String> = args |
| 47 | .iter() |
| 48 | .enumerate() |
| 49 | .map(|(i, t)| format!("a{}: {}", i, t.render())) |
| 50 | .collect(); |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago | 51 | match &**ret { |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago | 52 | Nim::Unit => format!("proc ({})", inner.join(", ")), |
| 53 | r => format!("proc ({}): {}", inner.join(", "), r.render()), |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago | 54 | } |
| 55 | } |
| Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago | 56 | Nim::Unit => "void".into(), |
| 57 | } |
| 58 | } |
| 59 | |
| Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago | 60 | /// Strip a `var`, which is a parameter-passing mode rather than a type. |
| 61 | /// Unlike `owned`, this keeps a view a view. |
| 62 | pub fn unvar(self) -> Nim { |
| 63 | match self { |
| 64 | Nim::Var(t) => t.unvar(), |
| 65 | other => other, |
| 66 | } |
| 67 | } |
| 68 | |
| Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago | 69 | /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same |
| 70 | /// type in an owned position (a field, a return value) must be `seq[T]`. |
| 71 | pub fn owned(self) -> Nim { |
| 72 | match self { |
| 73 | Nim::OpenArray(t) => Nim::Seq(t), |
| 74 | Nim::Var(t) => t.owned(), |
| 75 | other => other, |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | pub fn is_integer(&self) -> bool { |
| 80 | matches!(self, Nim::Prim(p) if matches!(p.as_str(), |
| 81 | "int8"|"int16"|"int32"|"int64"|"int"| |
| 82 | "uint8"|"uint16"|"uint32"|"uint64"|"uint")) |
| 83 | } |
| 84 | |
| 85 | pub fn is_unsigned(&self) -> bool { |
| 86 | matches!(self, Nim::Prim(p) if p.starts_with("uint")) |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | pub fn prim(name: &str) -> Option<Nim> { |
| 91 | let mapped = match name { |
| 92 | "i8" => "int8", |
| 93 | "i16" => "int16", |
| 94 | "i32" => "int32", |
| 95 | "i64" => "int64", |
| 96 | "isize" => "int", |
| 97 | "u8" => "uint8", |
| 98 | "u16" => "uint16", |
| 99 | "u32" => "uint32", |
| 100 | "u64" => "uint64", |
| 101 | "usize" => "uint", |
| 102 | "f32" => "float32", |
| 103 | "f64" => "float64", |
| 104 | "bool" => "bool", |
| 105 | "char" => "Rune", |
| 106 | "str" | "String" => "string", |
| 107 | _ => return None, |
| 108 | }; |
| 109 | Some(Nim::Prim(mapped.into())) |
| 110 | } |
| 111 | |
| 112 | /// Types we refuse rather than approximate. |
| 113 | pub fn rejected(name: &str) -> Option<&'static str> { |
| 114 | match name { |
| 115 | "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"), |
| 116 | _ => None, |
| 117 | } |
| 118 | } |
| 119 | |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago | 120 | fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> { |
| 121 | match r { |
| 122 | syn::ReturnType::Default => Ok(Nim::Unit), |
| 123 | syn::ReturnType::Type(_, t) => Ok(map(t)?.owned()), |
| 124 | } |
| 125 | } |
| 126 | |
| Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago | 127 | /// Types from `core::fmt` that must not be confused with a user type of the |
| 128 | /// same short name. `fmt::Error` and a crate's own `Error` are different |
| 129 | /// types, and collapsing a path to its last segment would merge them. |
| 130 | fn std_qualified(p: &syn::Path) -> Option<Nim> { |
| 131 | let segs: Vec<String> = p.segments.iter().map(|s| s.ident.to_string()).collect(); |
| 132 | if segs.len() < 2 { |
| 133 | return None; |
| 134 | } |
| 135 | let (q, name) = (&segs[segs.len() - 2], segs.last()?.as_str()); |
| 136 | if q != "fmt" { |
| 137 | return None; |
| 138 | } |
| 139 | Some(Nim::Prim( |
| 140 | match name { |
| 141 | "Error" => "FmtError", |
| 142 | "Formatter" => "Formatter", |
| 143 | // `fmt::Result` is `Result<(), fmt::Error>`. A formatting impl is |
| 144 | // lowered to a proc that returns the formatted string, so the |
| 145 | // result type is erased there; this spelling exists so that a |
| 146 | // signature mentioning it still maps to something. |
| 147 | "Result" => "FmtResult", |
| 148 | _ => return None, |
| 149 | } |
| 150 | .into(), |
| 151 | )) |
| 152 | } |
| 153 | |
| Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago | 154 | pub fn map(t: &Type) -> Result<Nim, String> { |
| 155 | match t { |
| 156 | Type::Path(p) => { |
| Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago | 157 | if let Some(n) = std_qualified(&p.path) { |
| 158 | return Ok(n); |
| 159 | } |
| Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago | 160 | let seg = p |
| 161 | .path |
| 162 | .segments |
| 163 | .last() |
| 164 | .ok_or_else(|| "empty type path".to_string())?; |
| 165 | let name = seg.ident.to_string(); |
| 166 | |
| 167 | if let Some(why) = rejected(&name) { |
| 168 | return Err(format!("unsupported type `{}`: {}", name, why)); |
| 169 | } |
| 170 | |
| 171 | let args: Vec<Nim> = match &seg.arguments { |
| 172 | PathArguments::AngleBracketed(a) => a |
| 173 | .args |
| 174 | .iter() |
| 175 | .filter_map(|g| match g { |
| 176 | GenericArgument::Type(t) => Some(map(t)), |
| 177 | _ => None, |
| 178 | }) |
| 179 | .collect::<Result<_, _>>()?, |
| 180 | _ => vec![], |
| 181 | }; |
| 182 | |
| 183 | match (name.as_str(), args.len()) { |
| 184 | ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))), |
| 185 | ("Option", 1) => Ok(Nim::Named("Option".into(), args)), |
| 186 | ("Result", 2) => Ok(Nim::Named("Result".into(), args)), |
| 187 | ("Box", 1) => Ok(args[0].clone()), |
| 188 | _ => { |
| 189 | if let Some(p) = prim(&name) { |
| 190 | Ok(p) |
| 191 | } else { |
| 192 | Ok(Nim::Named(name, args)) |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | // &T is a value in Nim; &mut T becomes a `var` parameter. The caller |
| 198 | // decides whether a `var` is legal in the position it is used. |
| 199 | Type::Reference(r) => { |
| Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 7h ago | 200 | // `&str` is a borrowed view of characters, not an owned string. |
| 201 | // Nim accepts a `string` argument for an `openArray[char]` |
| 202 | // parameter, so a literal still passes straight through. |
| 203 | if let Type::Path(p) = &*r.elem { |
| 204 | if p.path.is_ident("str") { |
| 205 | return Ok(Nim::OpenArray(Box::new(Nim::Prim("char".into())))); |
| 206 | } |
| 207 | } |
| Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago | 208 | let inner = map(&r.elem)?; |
| 209 | if r.mutability.is_some() { |
| 210 | Ok(Nim::Var(Box::new(inner))) |
| 211 | } else { |
| 212 | Ok(inner) |
| 213 | } |
| 214 | } |
| 215 | Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))), |
| 216 | Type::Array(a) => { |
| 217 | let len = match &a.len { |
| 218 | syn::Expr::Lit(syn::ExprLit { |
| 219 | lit: syn::Lit::Int(i), |
| 220 | .. |
| 221 | }) => i |
| 222 | .base10_parse::<usize>() |
| 223 | .map_err(|e| format!("array length: {}", e))?, |
| 224 | _ => return Err("array length must be a literal".into()), |
| 225 | }; |
| 226 | Ok(Nim::Array(len, Box::new(map(&a.elem)?))) |
| 227 | } |
| 228 | Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit), |
| 229 | Type::Tuple(t) => Ok(Nim::Tuple( |
| 230 | t.elems.iter().map(map).collect::<Result<_, _>>()?, |
| 231 | )), |
| 232 | Type::Paren(p) => map(&p.elem), |
| 233 | Type::Group(g) => map(&g.elem), |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago | 234 | Type::FnPtr(f) => { |
| 235 | let args: Vec<Nim> = f |
| 236 | .inputs |
| 237 | .iter() |
| 238 | .map(|a| map(&a.ty)) |
| 239 | .collect::<Result<_, _>>()?; |
| 240 | Ok(Nim::Proc(args, Box::new(ret_ty(&f.output)?))) |
| 241 | } |
| Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago | 242 | Type::ImplTrait(i) => { |
| 243 | // `impl AsRef<[u8]>` and friends: fall back to the bound's own |
| 244 | // shape where we can recognise it, since Nim has no impl-trait. |
| 245 | for b in &i.bounds { |
| 246 | if let TypeParamBound::Trait(tb) = b { |
| 247 | if let Some(seg) = tb.path.segments.last() { |
| Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago | 248 | // `impl Fn(A) -> B` is a callable; Nim has a proc type |
| 249 | // for exactly this. |
| 250 | if seg.ident == "Fn" || seg.ident == "FnMut" || seg.ident == "FnOnce" { |
| 251 | if let PathArguments::Parenthesized(a) = &seg.arguments { |
| 252 | let args: Vec<Nim> = a |
| 253 | .inputs |
| 254 | .iter() |
| 255 | .map(|a| map(&a.ty)) |
| 256 | .collect::<Result<_, _>>()?; |
| 257 | return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?))); |
| 258 | } |
| 259 | } |
| Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 9h ago | 260 | if seg.ident == "AsRef" || seg.ident == "Into" { |
| 261 | if let PathArguments::AngleBracketed(a) = &seg.arguments { |
| 262 | for g in &a.args { |
| 263 | if let GenericArgument::Type(t) = g { |
| 264 | return map(t); |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | Err("unsupported `impl Trait` type".into()) |
| 273 | } |
| 274 | Type::Infer(_) => Err("inferred type in a position that needs a name".into()), |
| 275 | other => Err(format!("unsupported type form: {:?}", discriminant(other))), |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | fn discriminant(t: &Type) -> &'static str { |
| 280 | match t { |
| 281 | Type::Ptr(_) => "raw pointer", |
| 282 | Type::TraitObject(_) => "trait object", |
| 283 | Type::Never(_) => "never", |
| 284 | Type::Macro(_) => "macro", |
| 285 | _ => "other", |
| 286 | } |
| 287 | } |