nandi/rustnimpublic Fork 0
4e4d09dcdd22d17ba510de5639fc3a952ac73f6e
Commits
Clone
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

ty.rs · 420 lines · 15.9 KBRust Blame HistoryRaw
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago1//! 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
8use syn::{GenericArgument, PathArguments, Type, TypeParamBound};
9
10#[derive(Debug, Clone, PartialEq)]
11pub 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>),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 6h ago19 /// `*mut T`. Nim's `ptr` is the same thing: an unmanaged address.
20 Ptr(Box<Nim>),
21 /// `*const T`. The const matters at the C level even though it does not at
22 /// the Rust one: Nim emits a real prototype where Rust emits none, and a
23 /// `char *` declaration against C's `const char *` is a compile error.
24 ConstPtr(Box<Nim>),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago25 /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling
26 /// convention (`closure`), which accepts both a plain top-level proc and
27 /// 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 9h ago28 Proc(Vec<Nim>, Box<Nim>),
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago29 Unit,
30}
31
32impl Nim {
33 pub fn render(&self) -> String {
34 match self {
35 Nim::Prim(s) => s.clone(),
36 Nim::Seq(t) => format!("seq[{}]", t.render()),
37 Nim::OpenArray(t) => format!("openArray[{}]", t.render()),
38 Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()),
39 Nim::Tuple(ts) => {
40 let inner: Vec<String> = ts.iter().map(|t| t.render()).collect();
41 format!("({})", inner.join(", "))
42 }
43 Nim::Named(n, args) if args.is_empty() => n.clone(),
44 Nim::Named(n, args) => {
45 let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
46 format!("{}[{}]", n, inner.join(", "))
47 }
48 Nim::Var(t) => format!("var {}", t.render()),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 6h ago49 Nim::Ptr(t) => match &**t {
50 Nim::Prim(p) if p == "void" => "pointer".into(),
51 inner => format!("ptr {}", inner.render()),
52 },
53 Nim::ConstPtr(t) => match &**t {
54 Nim::Prim(p) if p == "void" => "pointer".into(),
55 inner => const_ptr_alias(inner),
56 },
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 9h ago57 Nim::Proc(args, ret) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 9h ago58 // Nim's proc types name their parameters even when the name is
59 // never used.
60 let inner: Vec<String> = args
61 .iter()
62 .enumerate()
63 .map(|(i, t)| format!("a{}: {}", i, t.render()))
64 .collect();
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 9h ago65 match &**ret {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago66 Nim::Unit => format!("proc ({})", inner.join(", ")),
67 r => format!("proc ({}): {}", inner.join(", "), r.render()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 9h ago68 }
69 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago70 Nim::Unit => "void".into(),
71 }
72 }
73
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 8h ago74 /// Strip a `var`, which is a parameter-passing mode rather than a type.
75 /// Unlike `owned`, this keeps a view a view.
76 pub fn unvar(self) -> Nim {
77 match self {
78 Nim::Var(t) => t.unvar(),
79 other => other,
80 }
81 }
82
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago83 /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same
84 /// type in an owned position (a field, a return value) must be `seq[T]`.
85 pub fn owned(self) -> Nim {
86 match self {
87 Nim::OpenArray(t) => Nim::Seq(t),
88 Nim::Var(t) => t.owned(),
89 other => other,
90 }
91 }
92
93 pub fn is_integer(&self) -> bool {
94 matches!(self, Nim::Prim(p) if matches!(p.as_str(),
95 "int8"|"int16"|"int32"|"int64"|"int"|
96 "uint8"|"uint16"|"uint32"|"uint64"|"uint"))
97 }
98
99 pub fn is_unsigned(&self) -> bool {
100 matches!(self, Nim::Prim(p) if p.starts_with("uint"))
101 }
102}
103
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 6h ago104/// C types, as `libc` spells them. Nim has the same set under its own names,
105/// and both are defined by the platform's C compiler, so these are equal by
106/// construction rather than by assumption.
107pub fn c_type(name: &str) -> Option<&'static str> {
108 Some(match name {
109 "c_char" => "cchar",
110 "c_schar" => "cschar",
111 "c_uchar" => "cuchar",
112 "c_short" => "cshort",
113 "c_ushort" => "cushort",
114 "c_int" => "cint",
115 "c_uint" => "cuint",
116 "c_long" => "clong",
117 "c_ulong" => "culong",
118 "c_longlong" => "clonglong",
119 "c_ulonglong" => "culonglong",
120 "c_float" => "cfloat",
121 "c_double" => "cdouble",
122 "size_t" => "csize_t",
123 "ssize_t" => "int",
124 "intptr_t" => "int",
125 "uintptr_t" => "uint",
126 "c_void" => "void",
127 _ => return None,
128 })
129}
130
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago131pub fn prim(name: &str) -> Option<Nim> {
132 let mapped = match name {
133 "i8" => "int8",
134 "i16" => "int16",
135 "i32" => "int32",
136 "i64" => "int64",
137 "isize" => "int",
138 "u8" => "uint8",
139 "u16" => "uint16",
140 "u32" => "uint32",
141 "u64" => "uint64",
142 "usize" => "uint",
143 "f32" => "float32",
144 "f64" => "float64",
145 "bool" => "bool",
146 "char" => "Rune",
147 "str" | "String" => "string",
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 6h ago148 other => match c_type(other) {
149 Some(c) => c,
150 None => return None,
151 },
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago152 };
153 Some(Nim::Prim(mapped.into()))
154}
155
156/// Types we refuse rather than approximate.
157pub fn rejected(name: &str) -> Option<&'static str> {
158 match name {
159 "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"),
160 _ => None,
161 }
162}
163
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 6h ago164/// The Nim type name for a const-qualified C pointer to `t`.
165pub fn const_ptr_alias(t: &Nim) -> String {
166 format!("RsConstPtr{}", t.render().replace(' ', ""))
167}
168
169/// The C spelling of a Nim type, for a const-qualified pointer declaration.
170pub fn c_spelling(t: &Nim) -> Option<&'static str> {
171 let Nim::Prim(p) = t else { return None };
172 Some(match p.as_str() {
173 "cchar" => "char",
174 "cschar" => "signed char",
175 "cuchar" => "unsigned char",
176 "cshort" => "short",
177 "cushort" => "unsigned short",
178 "cint" => "int",
179 "cuint" => "unsigned int",
180 "clong" => "long",
181 "culong" => "unsigned long",
182 "clonglong" => "long long",
183 "culonglong" => "unsigned long long",
184 "cfloat" => "float",
185 "cdouble" => "double",
186 "uint8" => "unsigned char",
187 "int8" => "signed char",
188 "uint16" => "unsigned short",
189 "int16" => "short",
190 "uint32" => "unsigned int",
191 "int32" => "int",
192 "uint64" => "unsigned long long",
193 "int64" => "long long",
194 _ => return None,
195 })
196}
197
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 9h ago198fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
199 match r {
200 syn::ReturnType::Default => Ok(Nim::Unit),
201 syn::ReturnType::Type(_, t) => Ok(map(t)?.owned()),
202 }
203}
204
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 9h ago205/// Types from `core::fmt` that must not be confused with a user type of the
206/// same short name. `fmt::Error` and a crate's own `Error` are different
207/// types, and collapsing a path to its last segment would merge them.
208fn std_qualified(p: &syn::Path) -> Option<Nim> {
209 let segs: Vec<String> = p.segments.iter().map(|s| s.ident.to_string()).collect();
210 if segs.len() < 2 {
211 return None;
212 }
213 let (q, name) = (&segs[segs.len() - 2], segs.last()?.as_str());
214 if q != "fmt" {
215 return None;
216 }
217 Some(Nim::Prim(
218 match name {
219 "Error" => "FmtError",
220 "Formatter" => "Formatter",
221 // `fmt::Result` is `Result<(), fmt::Error>`. A formatting impl is
222 // lowered to a proc that returns the formatted string, so the
223 // result type is erased there; this spelling exists so that a
224 // signature mentioning it still maps to something.
225 "Result" => "FmtResult",
226 _ => return None,
227 }
228 .into(),
229 ))
230}
231
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago232pub fn map(t: &Type) -> Result<Nim, String> {
233 match t {
234 Type::Path(p) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 9h ago235 if let Some(n) = std_qualified(&p.path) {
236 return Ok(n);
237 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago238 let seg = p
239 .path
240 .segments
241 .last()
242 .ok_or_else(|| "empty type path".to_string())?;
243 let name = seg.ident.to_string();
244
245 if let Some(why) = rejected(&name) {
246 return Err(format!("unsupported type `{}`: {}", name, why));
247 }
248
249 let args: Vec<Nim> = match &seg.arguments {
250 PathArguments::AngleBracketed(a) => a
251 .args
252 .iter()
253 .filter_map(|g| match g {
254 GenericArgument::Type(t) => Some(map(t)),
255 _ => None,
256 })
257 .collect::<Result<_, _>>()?,
258 _ => vec![],
259 };
260
261 match (name.as_str(), args.len()) {
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 6h ago262 // The `log` facade's types, under shim names.
263 ("Level", 0) => Ok(Nim::Prim("RsLogLevel".into())),
264 ("LevelFilter", 0) => Ok(Nim::Prim("RsLogFilter".into())),
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago265 ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))),
266 ("Option", 1) => Ok(Nim::Named("Option".into(), args)),
267 ("Result", 2) => Ok(Nim::Named("Result".into(), args)),
268 ("Box", 1) => Ok(args[0].clone()),
269 _ => {
270 if let Some(p) = prim(&name) {
271 Ok(p)
272 } else {
273 Ok(Nim::Named(name, args))
274 }
275 }
276 }
277 }
278 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller
279 // decides whether a `var` is legal in the position it is used.
280 Type::Reference(r) => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago281 // `&str` is a borrowed view of characters, not an owned string.
282 // Nim accepts a `string` argument for an `openArray[char]`
283 // parameter, so a literal still passes straight through.
284 if let Type::Path(p) = &*r.elem {
285 if p.path.is_ident("str") {
286 return Ok(Nim::OpenArray(Box::new(Nim::Prim("char".into()))));
287 }
288 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago289 let inner = map(&r.elem)?;
290 if r.mutability.is_some() {
291 Ok(Nim::Var(Box::new(inner)))
292 } else {
293 Ok(inner)
294 }
295 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 6h ago296 Type::Ptr(p) => {
297 let inner = Box::new(map(&p.elem)?);
298 Ok(if matches!(p.mutability, syn::PointerMutability::Mut(_)) {
299 Nim::Ptr(inner)
300 } else {
301 Nim::ConstPtr(inner)
302 })
303 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago304 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
305 Type::Array(a) => {
306 let len = match &a.len {
307 syn::Expr::Lit(syn::ExprLit {
308 lit: syn::Lit::Int(i),
309 ..
310 }) => i
311 .base10_parse::<usize>()
312 .map_err(|e| format!("array length: {}", e))?,
313 _ => return Err("array length must be a literal".into()),
314 };
315 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
316 }
317 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),
318 Type::Tuple(t) => Ok(Nim::Tuple(
319 t.elems.iter().map(map).collect::<Result<_, _>>()?,
320 )),
321 Type::Paren(p) => map(&p.elem),
322 Type::Group(g) => map(&g.elem),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 9h ago323 Type::FnPtr(f) => {
324 let args: Vec<Nim> = f
325 .inputs
326 .iter()
327 .map(|a| map(&a.ty))
328 .collect::<Result<_, _>>()?;
329 Ok(Nim::Proc(args, Box::new(ret_ty(&f.output)?)))
330 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 6h ago331 // `dyn Fn(A) -> B` is a callable, exactly as `impl Fn(A) -> B` is.
332 // Other trait objects need a vtable, which the lowering builds from
333 // the trait's declaration; `dyn` is spelled `<Trait>Dyn` there.
334 Type::TraitObject(t) => {
335 for b in &t.bounds {
336 if let TypeParamBound::Trait(tb) = b {
337 if let Some(seg) = tb.path.segments.last() {
338 let n = seg.ident.to_string();
339 if n == "Fn" || n == "FnMut" || n == "FnOnce" {
340 if let PathArguments::Parenthesized(a) = &seg.arguments {
341 let args: Vec<Nim> = a
342 .inputs
343 .iter()
344 .map(|a| map(&a.ty))
345 .collect::<Result<_, _>>()?;
346 return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
347 }
348 }
349 // `dyn T + Send + Sync`: the auto traits carry no
350 // methods, so the first real bound names the object.
351 if !matches!(n.as_str(), "Send" | "Sync" | "Unpin" | "Sized") {
352 return Ok(Nim::Named(format!("{n}Dyn"), vec![]));
353 }
354 }
355 }
356 }
357 Err("a trait object with no nameable trait bound".into())
358 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago359 Type::ImplTrait(i) => {
360 // `impl AsRef<[u8]>` and friends: fall back to the bound's own
361 // shape where we can recognise it, since Nim has no impl-trait.
362 for b in &i.bounds {
363 if let TypeParamBound::Trait(tb) = b {
364 if let Some(seg) = tb.path.segments.last() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 9h ago365 // `impl Fn(A) -> B` is a callable; Nim has a proc type
366 // for exactly this.
367 if seg.ident == "Fn" || seg.ident == "FnMut" || seg.ident == "FnOnce" {
368 if let PathArguments::Parenthesized(a) = &seg.arguments {
369 let args: Vec<Nim> = a
370 .inputs
371 .iter()
372 .map(|a| map(&a.ty))
373 .collect::<Result<_, _>>()?;
374 return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
375 }
376 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago377 if seg.ident == "AsRef" || seg.ident == "Into" {
378 if let PathArguments::AngleBracketed(a) = &seg.arguments {
379 for g in &a.args {
380 if let GenericArgument::Type(t) = g {
381 return map(t);
382 }
383 }
384 }
385 }
386 }
387 }
388 }
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 5h ago389 // Named so the message says which trait and, by implication, that
390 // the caller is in return position -- argument position is handled
391 // by the lowering, which turns it into a generic parameter.
392 let named = i
393 .bounds
394 .iter()
395 .find_map(|b| match b {
396 TypeParamBound::Trait(tb) => {
397 tb.path.segments.last().map(|s| s.ident.to_string())
398 }
399 _ => None,
400 })
401 .unwrap_or_else(|| "?".into());
402 Err(format!(
403 "`impl {named}` in return position is an opaque type: the caller \
404 cannot name it, and Nim has no equivalent. In argument position \
405 `impl {named}` lowers fine, as the generic parameter it is"
406 ))
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 10h ago407 }
408 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),
409 other => Err(format!("unsupported type form: {:?}", discriminant(other))),
410 }
411}
412
413fn discriminant(t: &Type) -> &'static str {
414 match t {
415 Type::TraitObject(_) => "trait object",
416 Type::Never(_) => "never",
417 Type::Macro(_) => "macro",
418 _ => "other",
419 }
420}