nandi/rustnimpublic Fork 0
8ac32afd8fe6eb71bb6de56905986a93ee2cde00
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.

lower.rs · 1610 lines · 64.0 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1//! Rust AST -> Nim source.
2//!
3//! The governing rule is in DESIGN.md and it shapes every function here:
4//! anything whose Rust semantics cannot be reproduced exactly in Nim returns
5//! `Err` with a reason. Nothing is emitted on a guess. Where a construct maps
6//! one-to-one (signed `shr`, unsigned wrapping, truncating `div`/`mod`) the
7//! mapping is direct and there is a comment saying why that is safe.
8
9use crate::fmt;
10use crate::ty::{self, Nim};
11use std::collections::HashMap;
12use syn::{
13 BinOp, Expr, FnArg, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp,
14};
15
16// --------------------------------------------------------------- vocabulary
17
18/// Nim keywords. Rust code may legally use any of these as an identifier.
19const NIM_KEYWORDS: &[&str] = &[
20 "addr", "and", "as", "asm", "bind", "block", "break", "case", "cast",
21 "concept", "const", "continue", "converter", "defer", "discard", "distinct",
22 "div", "do", "elif", "else", "end", "enum", "except", "export", "finally",
23 "for", "from", "func", "if", "import", "in", "include", "interface", "is",
24 "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not",
25 "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref",
26 "return", "shl", "shr", "static", "template", "try", "tuple", "type",
27 "using", "var", "when", "while", "xor", "result", "echo",
28];
29
30fn ident(name: &str) -> String {
31 if NIM_KEYWORDS.contains(&name) {
32 format!("{name}_r")
33 } else {
34 name.to_string()
35 }
36}
37
38/// A lowered expression: its Nim text, and its type where we know it.
39///
40/// The type is not decoration. Nim needs it to pick `div` over `/`, to size a
41/// `cast`, and to annotate every binding so that Nim's own type checker
42/// catches a mistake in this file rather than letting it through as output
43/// that runs and is wrong.
44#[derive(Clone, Debug)]
45struct Val {
46 code: String,
47 ty: Option<Nim>,
48}
49
50impl Val {
51 fn new(code: impl Into<String>, ty: Option<Nim>) -> Self {
52 Val { code: code.into(), ty }
53 }
54 fn untyped(code: impl Into<String>) -> Self {
55 Val { code: code.into(), ty: None }
56 }
57}
58
59struct Sig {
60 params: Vec<Nim>,
61 ret: Nim,
62}
63
64pub struct Lowerer {
65 out: String,
66 indent: usize,
67 scopes: Vec<HashMap<String, Nim>>,
68 fns: HashMap<String, Sig>,
69 /// struct name -> (field, type)
70 structs: HashMap<String, Vec<(String, Nim)>>,
71 /// Return type of the proc being lowered, so `return e` and a trailing
72 /// expression can type their literals the way Rust's inference would.
73 ret: Option<Nim>,
74 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
75 /// statement must assign their value to.
76 target: Option<(String, Option<Nim>)>,
77 tmp: usize,
78}
79
80impl Lowerer {
81 pub fn new() -> Self {
82 Lowerer {
83 out: String::new(),
84 indent: 0,
85 scopes: vec![HashMap::new()],
86 fns: HashMap::new(),
87 structs: HashMap::new(),
88 ret: None,
89 target: None,
90 tmp: 0,
91 }
92 }
93
94 // ------------------------------------------------------------ emission
95
96 fn line(&mut self, s: &str) {
97 for _ in 0..self.indent {
98 self.out.push_str(" ");
99 }
100 self.out.push_str(s);
101 self.out.push('\n');
102 }
103
104 fn blank(&mut self) {
105 self.out.push('\n');
106 }
107
108 fn fresh(&mut self, hint: &str) -> String {
109 self.tmp += 1;
110 format!("rsTmp{}{}", hint, self.tmp)
111 }
112
113 // --------------------------------------------------------------- scope
114
115 fn push_scope(&mut self) {
116 self.scopes.push(HashMap::new());
117 }
118 fn pop_scope(&mut self) {
119 self.scopes.pop();
120 }
121 fn bind(&mut self, name: &str, t: Nim) {
122 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
123 }
124 fn lookup(&self, name: &str) -> Option<Nim> {
125 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
126 }
127
128 // ---------------------------------------------------------------- file
129
130 pub fn lower_file(&mut self, file: &syn::File) -> Result<String, String> {
131 self.out.push_str(include_str!("prelude.nim"));
132 self.blank();
133
134 // Pass 1: signatures and struct shapes, so that a call can be typed
135 // regardless of declaration order (Rust has no forward declarations).
136 for item in &file.items {
137 self.collect(item)?;
138 }
139 // Pass 2: bodies.
140 for item in &file.items {
141 self.item(item)?;
142 }
143
144 if self.fns.contains_key("main") {
145 self.blank();
146 self.line("when isMainModule:");
147 self.indent += 1;
148 self.line("try:");
149 self.line(" main()");
150 // Rust's panic exits 101 with a message on stderr. Nim's Defects
151 // exit 1. Mapping them here is what keeps the differential runner's
152 // exit-status comparison meaningful for panicking programs.
153 self.line("except RustPanic as e:");
154 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
155 self.line(" quit(101)");
156 self.line("except Defect as e:");
157 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
158 self.line(" quit(101)");
159 self.indent -= 1;
160 }
161 Ok(std::mem::take(&mut self.out))
162 }
163
164 fn collect(&mut self, item: &Item) -> Result<(), String> {
165 match item {
166 Item::Fn(f) => {
167 let (params, ret) = self.signature(&f.sig)?;
168 self.fns.insert(f.sig.ident.to_string(), Sig { params, ret });
169 }
170 Item::Struct(s) => {
171 let mut fields = Vec::new();
172 for (i, f) in s.fields.iter().enumerate() {
173 let name = match &f.ident {
174 Some(id) => id.to_string(),
175 None => format!("f{i}"), // tuple struct
176 };
177 fields.push((name, ty::map(&f.ty)?.owned()));
178 }
179 self.structs.insert(s.ident.to_string(), fields);
180 }
181 Item::Impl(im) => {
182 let self_ty = ty::map(&im.self_ty)?;
183 for it in &im.items {
184 if let syn::ImplItem::Fn(m) = it {
185 let (mut params, ret) = self.signature(&m.sig)?;
186 if takes_self(&m.sig) {
187 params.insert(0, self_ty.clone());
188 }
189 self.fns.insert(m.sig.ident.to_string(), Sig { params, ret });
190 }
191 }
192 }
193 _ => {}
194 }
195 Ok(())
196 }
197
198 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
199 if sig.asyncness.is_some() {
200 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
201 }
202 if !sig.generics.params.is_empty() {
203 return Err(format!(
204 "`fn {}` is generic: generics are not implemented yet",
205 sig.ident
206 ));
207 }
208 let mut params = Vec::new();
209 for a in &sig.inputs {
210 if let FnArg::Typed(t) = a {
211 params.push(ty::map(&t.ty)?);
212 }
213 }
214 let ret = match &sig.output {
215 ReturnType::Default => Nim::Unit,
216 ReturnType::Type(_, t) => ty::map(t)?.owned(),
217 };
218 Ok((params, ret))
219 }
220
221 // --------------------------------------------------------------- items
222
223 fn item(&mut self, item: &Item) -> Result<(), String> {
224 match item {
225 Item::Fn(f) => self.func(&f.sig, &f.block, None),
226 Item::Struct(s) => {
227 let name = s.ident.to_string();
228 let fields = self.structs[&name].clone();
229 self.line(&format!("type {}* = object", ident(&name)));
230 self.indent += 1;
231 if fields.is_empty() {
232 self.line("discard");
233 }
234 for (fname, fty) in &fields {
235 self.line(&format!("{}*: {}", ident(fname), fty.render()));
236 }
237 self.indent -= 1;
238 self.blank();
239 Ok(())
240 }
241 Item::Const(c) => {
242 let t = ty::map(&c.ty)?.owned();
243 let v = self.expr(&c.expr)?;
244 self.bind(&c.ident.to_string(), t.clone());
245 let line = format!("const {}*: {} = {}", ident(&c.ident.to_string()), t.render(), v.code);
246 self.line(&line);
247 self.blank();
248 Ok(())
249 }
250 Item::Impl(im) => {
251 let self_ty = ty::map(&im.self_ty)?;
252 if im.trait_.is_some() {
253 return Err(format!(
254 "`impl Trait for {}`: trait impls are not implemented yet",
255 self_ty.render()
256 ));
257 }
258 for it in &im.items {
259 match it {
260 syn::ImplItem::Fn(m) => {
261 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
262 self.func(&m.sig, &m.block, recv)?;
263 }
264 _ => return Err("only `fn` items are supported inside `impl`".into()),
265 }
266 }
267 Ok(())
268 }
269 Item::Use(_) => Ok(()), // `use` has no Nim analogue in a single module
270 Item::Mod(m) if m.content.is_none() => {
271 Err(format!("`mod {};` (external file) is not implemented yet", m.ident))
272 }
273 other => Err(format!("unsupported item: {}", item_kind(other))),
274 }
275 }
276
277 fn func(
278 &mut self,
279 sig: &syn::Signature,
280 body: &syn::Block,
281 recv: Option<Nim>,
282 ) -> Result<(), String> {
283 let name = sig.ident.to_string();
284 let (ptys, ret) = self.signature(sig)?;
285
286 self.push_scope();
287 let mut rendered: Vec<String> = Vec::new();
288
289 if let Some(self_ty) = recv {
290 // `&mut self` and `mut self` both mean the body may mutate the
291 // receiver; only the former is observable by the caller, and a Nim
292 // `var` parameter is the faithful spelling of that.
293 let mutable = matches!(
294 sig.inputs.first(),
295 Some(FnArg::Receiver(r))
296 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
297 );
298 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
299 rendered.push(format!("self: {}", t.render()));
300 self.bind("self", self_ty);
301 }
302
303 let typed: Vec<&syn::PatType> = sig
304 .inputs
305 .iter()
306 .filter_map(|a| match a {
307 FnArg::Typed(t) => Some(t),
308 _ => None,
309 })
310 .collect();
311 for (p, t) in typed.iter().zip(ptys.iter()) {
312 let pname = match &*p.pat {
313 Pat::Ident(i) => i.ident.to_string(),
314 _ => return Err("only plain identifier parameters are supported".into()),
315 };
316 rendered.push(format!("{}: {}", ident(&pname), t.render()));
317 // Inside the body a `var T` parameter is used exactly like a `T`.
318 self.bind(&pname, t.clone().owned());
319 }
320
321 let head = if ret == Nim::Unit {
322 format!("proc {}*({}) =", ident(&name), rendered.join(", "))
323 } else {
324 format!("proc {}*({}): {} =", ident(&name), rendered.join(", "), ret.render())
325 };
326 self.line(&head);
327 self.indent += 1;
328 let outer_ret = self.ret.replace(ret.clone());
329
330 // A Rust fn's trailing expression is its return value. Naming Nim's
331 // implicit `result` as the target makes that true whether the tail is
332 // a plain expression or an `if`/`match` with statement arms.
333 let outer_target = if ret == Nim::Unit {
334 self.target.take()
335 } else {
336 self.target.replace(("result".to_string(), Some(ret.clone())))
337 };
338 let before = self.out.len();
339 let tail = self.block_body_at(body, Some(&ret))?;
340 self.target = outer_target;
341 match tail {
342 Some(v) if ret != Nim::Unit => {
343 let code = v.code.clone();
344 self.line(&format!("result = {code}"));
345 }
346 Some(v) => {
347 // A trailing expression in a `()`-returning fn is evaluated for
348 // its effect; Nim requires an explicit discard.
349 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
350 if needs_discard && !v.code.is_empty() {
351 let code = v.code.clone();
352 self.line(&format!("discard {code}"));
353 }
354 }
355 None => {}
356 }
357 if self.out.len() == before {
358 self.line("discard");
359 }
360
361 self.indent -= 1;
362 self.ret = outer_ret;
363 self.pop_scope();
364 self.blank();
365 Ok(())
366 }
367
368 // ---------------------------------------------------------- statements
369
370 /// Lower a block's statements. Returns the block's trailing expression,
371 /// if it has one, *without* emitting it — the caller decides whether that
372 /// value is a return value, a binding, or discarded.
373 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
374 self.block_body_at(b, None)
375 }
376
377 fn block_body_at(
378 &mut self,
379 b: &syn::Block,
380 expect: Option<&Nim>,
381 ) -> Result<Option<Val>, String> {
382 // An assignment target belongs to *this* block's trailing expression
383 // only. A non-final `if` is a statement and must not assign anything.
384 let target = self.target.take();
385 let n = b.stmts.len();
386 let mut tail = None;
387 for (i, st) in b.stmts.iter().enumerate() {
388 let last = i + 1 == n;
389 match st {
390 Stmt::Expr(e, None) if last && expressible(e) => {
391 tail = Some(self.expr_at(e, expect)?)
392 }
393 Stmt::Expr(e, None) if last => {
394 // A trailing `if`/`match` with statement arms, or a loop.
395 // Lower it as statements; if this block's value is wanted,
396 // each arm assigns it.
397 match &target {
398 Some((t, ty)) => {
399 let (t, ty) = (t.clone(), ty.clone());
400 self.assign_from(e, &t, ty.as_ref())?;
401 }
402 None => self.stmt(st)?,
403 }
404 }
405 _ => self.stmt(st)?,
406 }
407 }
408 self.target = target;
409 Ok(tail)
410 }
411
412 /// Lower a block in statement position (loop bodies, `if` arms).
413 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
414 self.push_scope();
415 self.indent += 1;
416 let before = self.out.len();
417 let want = self.target.clone().and_then(|(_, t)| t);
418 let tail = self.block_body_at(b, want.as_ref())?;
419 self.emit_tail(tail);
420 if self.out.len() == before {
421 self.line("discard");
422 }
423 self.indent -= 1;
424 self.pop_scope();
425 Ok(())
426 }
427
428 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
429 match s {
430 Stmt::Local(l) => self.local(l),
431 Stmt::Expr(e, _) => {
432 let v = self.expr_stmt(e)?;
433 if let Some(v) = v {
434 // A bare expression with a value must be discarded in Nim.
435 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
436 let code = v.code.clone();
437 if needs {
438 self.line(&format!("discard {code}"));
439 } else if !code.is_empty() {
440 self.line(&code);
441 }
442 }
443 Ok(())
444 }
445 Stmt::Item(i) => self.item(i),
446 Stmt::Macro(m) => {
447 let line = self.macro_call(&m.mac)?;
448 self.line(&line);
449 Ok(())
450 }
451 }
452 }
453
454 fn local(&mut self, l: &Local) -> Result<(), String> {
455 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
456 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
457 Pat::Type(t) => match &*t.pat {
458 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), Some(ty::map(&t.ty)?)),
459 _ => return Err("only `let <ident>` bindings are supported".into()),
460 },
461 Pat::Wild(_) => ("_".into(), false, None),
462 _ => return Err("destructuring `let` is not implemented yet".into()),
463 };
464
465 let Some(init) = &l.init else {
466 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
467 // not. Rust's own rules make reading it before assignment illegal,
468 // so the two agree on every program rustc accepts.
469 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
470 let t = t.owned();
471 self.line(&format!("var {}: {}", ident(&name), t.render()));
472 self.bind(&name, t);
473 return Ok(());
474 };
475 if init.diverge.is_some() {
476 return Err("`let ... else` is not implemented yet".into());
477 }
478
479 if !expressible(&init.expr) && name != "_" {
480 // The initialiser is an `if`/`match` whose arms are statements.
481 // Declare first, then let each arm assign into the binding.
482 let t = ann
483 .clone()
484 .ok_or_else(|| {
485 format!(
486 "`let {name} = match/if ...` needs a type annotation: \
487 its arms are statements, so the binding must be \
488 declared before they run"
489 )
490 })?
491 .owned();
492 self.line(&format!("var {}: {}", ident(&name), t.render()));
493 self.bind(&name, t.clone());
494 let target = ident(&name);
495 return self.assign_from(&init.expr, &target, Some(&t));
496 }
497
498 let v = self.expr_at(&init.expr, ann.as_ref())?;
499 let t = match (ann, &v.ty) {
500 (Some(a), _) => a.owned(),
501 (None, Some(t)) => t.clone().owned(),
502 (None, None) => {
503 return Err(format!(
504 "cannot infer the type of `let {name}`; annotate it — \
505 guessing here would change integer width, and with it the \
506 meaning of any arithmetic on `{name}`"
507 ))
508 }
509 };
510
511 if name == "_" {
512 let code = v.code.clone();
513 self.line(&format!("discard {code}"));
514 return Ok(());
515 }
516 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
517 // works in both, so a re-`let` of the same name needs no rename.
518 let kw = if mutable { "var" } else { "let" };
519 let line = format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code);
520 self.line(&line);
521 self.bind(&name, t);
522 Ok(())
523 }
524
525 /// Expressions that are statements in Rust and statements in Nim too
526 /// (control flow). Returns `None` when it emitted lines itself.
527 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
528 match e {
529 Expr::If(_) => {
530 self.if_stmt(e)?;
531 Ok(None)
532 }
533 Expr::While(w) => {
534 if w.label.is_some() {
535 return Err("loop labels are not implemented yet".into());
536 }
537 let c = self.expr(&w.cond)?;
538 self.line(&format!("while {}:", c.code));
539 let saved = self.target.take();
540 self.nested_block(&w.body)?;
541 self.target = saved;
542 Ok(None)
543 }
544 Expr::Loop(l) => {
545 if l.label.is_some() {
546 return Err("loop labels are not implemented yet".into());
547 }
548 self.line("while true:");
549 let saved = self.target.take();
550 self.nested_block(&l.body)?;
551 self.target = saved;
552 Ok(None)
553 }
554 Expr::ForLoop(f) => {
555 self.for_loop(f)?;
556 Ok(None)
557 }
558 Expr::Block(b) => {
559 if b.label.is_some() {
560 return Err("block labels are not implemented yet".into());
561 }
562 self.line("block:");
563 self.nested_block(&b.block)?;
564 Ok(None)
565 }
566 Expr::Match(_) => {
567 self.match_stmt(e)?;
568 Ok(None)
569 }
570 Expr::Return(r) => {
571 match &r.expr {
572 Some(e) => {
573 let want = self.ret.clone();
574 let v = self.expr_at(e, want.as_ref())?;
575 self.line(&format!("return {}", v.code));
576 }
577 None => self.line("return"),
578 }
579 Ok(None)
580 }
581 Expr::Break(b) => {
582 if b.expr.is_some() || b.label.is_some() {
583 return Err("`break` with a value or a label is not implemented yet".into());
584 }
585 self.line("break");
586 Ok(None)
587 }
588 Expr::Continue(c) => {
589 if c.label.is_some() {
590 return Err("labelled `continue` is not implemented yet".into());
591 }
592 self.line("continue");
593 Ok(None)
594 }
595 Expr::Assign(a) => {
596 let lhs = self.expr(&a.left)?;
597 if !expressible(&a.right) {
598 let target = lhs.code.clone();
599 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
600 }
601 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
602 self.line(&format!("{} = {}", lhs.code, rhs.code));
603 Ok(None)
604 }
605 Expr::Binary(b) if is_compound(&b.op) => {
606 let lhs = self.expr(&b.left)?;
607 // `i += 1` must widen the literal to `i`'s type, not to the
608 // i32 an unconstrained Rust literal would default to.
609 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
610 let op = self.bin_op(&b.op, &lhs, &rhs)?;
611 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
612 // both languages, so the expanded form is always correct.
613 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
614 Ok(None)
615 }
616 Expr::Macro(m) => {
617 let line = self.macro_call(&m.mac)?;
618 self.line(&line);
619 Ok(None)
620 }
621 _ => Ok(Some(self.expr(e)?)),
622 }
623 }
624
625 /// Lower `e` in statement position, assigning each arm's value to
626 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
627 /// the trip when their arms are too big for a Nim `if`-expression.
628 fn assign_from(
629 &mut self,
630 e: &Expr,
631 target: &str,
632 expect: Option<&Nim>,
633 ) -> Result<(), String> {
634 let saved = self.target.replace((target.to_string(), expect.cloned()));
635 let r = match e {
636 Expr::If(_) => self.if_stmt(e),
637 Expr::Match(_) => self.match_stmt(e),
638 other => {
639 let v = self.expr_at(other, expect)?;
640 self.line(&format!("{} = {}", target, v.code));
641 Ok(())
642 }
643 };
644 self.target = saved;
645 r
646 }
647
648 /// Emit a block's value into the active assignment target, if there is
649 /// one, or discard it if there is not.
650 fn emit_tail(&mut self, v: Option<Val>) {
651 let Some(v) = v else { return };
652 match self.target.clone() {
653 Some((t, _)) => {
654 let code = v.code.clone();
655 self.line(&format!("{t} = {code}"));
656 }
657 None => {
658 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
659 let code = v.code.clone();
660 if needs {
661 self.line(&format!("discard {code}"));
662 } else if !code.is_empty() {
663 self.line(&code);
664 }
665 }
666 }
667 }
668
669 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
670 let Expr::If(i) = e else { unreachable!() };
671 if let Expr::Let(_) = &*i.cond {
672 return Err("`if let` is not implemented yet".into());
673 }
674 let c = self.expr(&i.cond)?;
675 self.line(&format!("if {}:", c.code));
676 self.nested_block(&i.then_branch)?;
677 match &i.else_branch {
678 None => {}
679 Some((_, els)) => match &**els {
680 Expr::If(_) => {
681 // Nim needs `elif`; splice the nested `if` in as one.
682 let mark = self.out.len();
683 self.if_stmt(els)?;
684 let tail = self.out.split_off(mark);
685 let indent = " ".repeat(self.indent);
686 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
687 }
688 Expr::Block(b) => {
689 self.line("else:");
690 self.nested_block(&b.block)?;
691 }
692 _ => return Err("unsupported `else` form".into()),
693 },
694 }
695 Ok(())
696 }
697
698 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
699 if f.label.is_some() {
700 return Err("loop labels are not implemented yet".into());
701 }
702 let name = match &*f.pat {
703 Pat::Ident(i) => i.ident.to_string(),
704 Pat::Wild(_) => "_".into(),
705 _ => return Err("destructuring `for` patterns are not implemented yet".into()),
706 };
707
708 // Strip the iterator adaptors that are no-ops once we are iterating a
709 // Nim container directly. Anything else (`.map`, `.filter`, `.rev`)
710 // is a real iterator and is rejected rather than silently dropped.
711 let mut src = &*f.expr;
712 loop {
713 match src {
714 Expr::MethodCall(m)
715 if matches!(m.method.to_string().as_str(), "iter" | "into_iter" | "iter_mut")
716 && m.args.is_empty() =>
717 {
718 src = &m.receiver
719 }
720 Expr::Reference(r) => src = &r.expr,
721 _ => break,
722 }
723 }
724
725 let (header, elem) = match src {
726 Expr::Range(r) => {
727 let lo = match &r.start {
728 Some(e) => self.expr(e)?,
729 None => return Err("a `for` over `..n` needs a start bound".into()),
730 };
731 let hi = match &r.end {
732 Some(e) => self.expr(e)?,
733 None => return Err("a `for` over an unbounded range would not terminate".into()),
734 };
735 let op = match r.limits {
736 syn::RangeLimits::HalfOpen(_) => "..<",
737 syn::RangeLimits::Closed(_) => "..",
738 };
739 let t = lo.ty.clone().or(hi.ty.clone());
740 (format!("{} {} {}", lo.code, op, hi.code), t)
741 }
742 other => {
743 let v = self.expr(other)?;
744 let elem = match v.ty.clone() {
745 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
746 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
747 _ => None,
748 };
749 (v.code, elem)
750 }
751 };
752
753 self.line(&format!("for {} in {}:", ident(&name), header));
754 self.push_scope();
755 if let Some(t) = elem {
756 self.bind(&name, t);
757 }
758 self.indent += 1;
759 let before = self.out.len();
760 let saved = self.target.take();
761 if let Some(v) = self.block_body(&f.body)? {
762 let code = v.code.clone();
763 self.line(&format!("discard {code}"));
764 }
765 self.target = saved;
766 if self.out.len() == before {
767 self.line("discard");
768 }
769 self.indent -= 1;
770 self.pop_scope();
771 Ok(())
772 }
773
774 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
775 let Expr::Match(m) = e else { unreachable!() };
776 let scrut = self.expr(&m.expr)?;
777 // A `match` whose arms are all literal or `_` patterns is a Nim `case`,
778 // which is exhaustiveness-checked the same way. Anything richer is
779 // rejected rather than flattened into an if-chain that loses the
780 // check.
781 let name = self.fresh("Match");
782 let t = scrut
783 .ty
784 .clone()
785 .ok_or("cannot infer the type of a `match` scrutinee")?;
786 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
787 self.line(&format!("case {}", name));
788
789 let mut saw_wild = false;
790 for arm in &m.arms {
791 match &arm.pat {
792 Pat::Guard(_) => {
793 return Err("`match` guards are not implemented yet".into())
794 }
795 Pat::Wild(_) => {
796 saw_wild = true;
797 self.line("else:");
798 }
799 p => {
800 let labels = self.pat_labels(p, Some(&t))?;
801 self.line(&format!("of {}:", labels.join(", ")));
802 }
803 }
804 self.indent += 1;
805 let before = self.out.len();
806 match &*arm.body {
807 Expr::Block(b) => {
808 self.indent -= 1;
809 self.nested_block(&b.block)?;
810 self.indent += 1;
811 }
812 other => {
813 let v = self.expr_stmt(other)?;
814 self.emit_tail(v);
815 }
816 }
817 if self.out.len() == before {
818 self.line("discard");
819 }
820 self.indent -= 1;
821 }
822 if !saw_wild {
823 // Rust checked exhaustiveness already, but Nim cannot always see
824 // it (an integer `case` needs every value covered), so make the
825 // unreachable arm explicit rather than leaving a compile error.
826 self.line("else:");
827 self.line(" rsPanic(\"unreachable match arm\")");
828 }
829 Ok(())
830 }
831
832 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
833 match p {
834 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
835 Pat::Or(o) => {
836 let mut out = Vec::new();
837 for p in &o.cases {
838 out.extend(self.pat_labels(p, expect)?);
839 }
840 Ok(out)
841 }
842 Pat::Range(r) => {
843 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
844 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
845 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
846 let op = match r.limits {
847 syn::RangeLimits::HalfOpen(_) => "..<",
848 syn::RangeLimits::Closed(_) => "..",
849 };
850 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
851 }
852 Pat::Path(p) => Ok(vec![ident(&path_name(&p.path))]),
853 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
854 alternatives and `_` are implemented"
855 .into()),
856 }
857 }
858
859 // --------------------------------------------------------- expressions
860
861 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
862 self.expr_at(e, None)
863 }
864
865 /// Lower `e`, with the type the surrounding code expects of it.
866 ///
867 /// Rust infers an unsuffixed integer literal's type from its context and
868 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
869 /// expected type down to the literal is what makes `let x: u8 = 255` and
870 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
871 /// widths silently diverge, which is exactly the class of bug this
872 /// project refuses to ship.
873 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
874 match e {
875 Expr::Lit(l) => self.lit_at(&l.lit, expect),
876 Expr::Path(p) => {
877 let name = path_name(&p.path);
878 match name.as_str() {
879 "None" => Ok(Val::untyped("rsNone()")),
880 _ => {
881 let t = self.lookup(&name);
882 Ok(Val::new(ident(&name), t))
883 }
884 }
885 }
886 Expr::Paren(p) => {
887 let v = self.expr_at(&p.expr, expect)?;
888 Ok(Val::new(format!("({})", v.code), v.ty))
889 }
890 Expr::Group(g) => self.expr_at(&g.expr, expect),
891 // `&x` is a value in Nim; `&mut x` in an argument position binds to
892 // a `var` parameter, which is also just `x` at the call site.
893 Expr::Reference(r) => self.expr_at(&r.expr, expect),
894 Expr::Unary(u) => self.unary(u, expect),
895 Expr::Binary(b) => self.binary(b, expect),
896 Expr::Cast(c) => self.cast(c),
897 Expr::Index(i) => {
898 let base = self.expr(&i.expr)?;
899 let idx = self.expr(&i.index)?;
900 // Rust indexes with usize; Nim wants an `int`, and a `uint`
901 // index is a type error there rather than a silent conversion.
902 let idx_code = match &idx.ty {
903 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
904 _ => idx.code.clone(),
905 };
906 let elem = match base.ty.clone() {
907 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
908 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
909 _ => None,
910 };
911 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
912 }
913 Expr::Field(f) => {
914 let base = self.expr(&f.base)?;
915 let name = match &f.member {
916 syn::Member::Named(n) => n.to_string(),
917 syn::Member::Unnamed(i) => format!("f{}", i.index),
918 };
919 let t = match &base.ty {
920 Some(Nim::Named(s, _)) => self
921 .structs
922 .get(s)
923 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
924 .map(|(_, t)| t.clone()),
925 _ => None,
926 };
927 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
928 }
929 Expr::Call(c) => self.call(c),
930 Expr::MethodCall(m) => self.method(m),
931 Expr::Macro(m) => {
932 let code = self.macro_call(&m.mac)?;
933 Ok(Val::new(code, None))
934 }
935 Expr::Struct(s) => {
936 let name = path_name(&s.path);
937 let mut parts = Vec::new();
938 for f in &s.fields {
939 let fname = match &f.member {
940 syn::Member::Named(n) => n.to_string(),
941 syn::Member::Unnamed(i) => format!("f{}", i.index),
942 };
943 let v = self.expr(&f.expr)?;
944 parts.push(format!("{}: {}", ident(&fname), v.code));
945 }
946 if s.rest.is_some() {
947 return Err("struct update syntax `..rest` is not implemented yet".into());
948 }
949 Ok(Val::new(
950 format!("{}({})", ident(&name), parts.join(", ")),
951 Some(Nim::Named(name, vec![])),
952 ))
953 }
954 Expr::Array(a) => {
955 let mut parts = Vec::new();
956 let mut elem = match expect {
957 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
958 Some((**t).clone())
959 }
960 _ => None,
961 };
962 for e in &a.elems {
963 let want = elem.clone();
964 let v = self.expr_at(e, want.as_ref())?;
965 elem = elem.or(v.ty.clone());
966 parts.push(v.code);
967 }
968 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
969 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
970 }
971 Expr::Repeat(r) => {
972 let v = self.expr(&r.expr)?;
973 let n = self.expr(&r.len)?;
974 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
975 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
976 }
977 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
978 Expr::Tuple(t) => {
979 let mut parts = Vec::new();
980 let mut tys = Vec::new();
981 for e in &t.elems {
982 let v = self.expr(e)?;
983 tys.push(v.ty.clone());
984 parts.push(v.code);
985 }
986 let ty = tys
987 .iter()
988 .cloned()
989 .collect::<Option<Vec<_>>>()
990 .map(Nim::Tuple);
991 Ok(Val::new(format!("({})", parts.join(", ")), ty))
992 }
993 // `if` and `match` are expressions in both languages, but only
994 // when every arm is itself a single expression.
995 Expr::If(i) => self.if_expr(i, expect),
996 Expr::Block(b) if b.block.stmts.len() == 1 => {
997 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
998 self.expr_at(e, expect)
999 } else {
1000 Err("block expression with statements in value position is not implemented yet".into())
1001 }
1002 }
1003 other => Err(format!(
1004 "unsupported expression in value position: {}",
1005 expr_kind(other)
1006 )),
1007 }
1008 }
1009
1010 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
1011 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
1012 return Err(
1013 "an `if` used as a value must have an `else` and single-expression arms".into(),
1014 );
1015 };
1016 let c = self.expr(&i.cond)?;
1017 let t = self.expr_at(then, expect)?;
1018 let want = expect.cloned().or_else(|| t.ty.clone());
1019 let e = match &**els {
1020 Expr::Block(b) => match single_expr(&b.block) {
1021 Some(x) => self.expr_at(x, want.as_ref())?,
1022 None => return Err("an `if` used as a value must have single-expression arms".into()),
1023 },
1024 other => self.expr_at(other, want.as_ref())?,
1025 };
1026 let ty = t.ty.clone().or(e.ty.clone());
1027 Ok(Val::new(
1028 format!("(if {}: {} else: {})", c.code, t.code, e.code),
1029 ty,
1030 ))
1031 }
1032
1033 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
1034 match l {
1035 Lit::Int(i) => {
1036 let suffix = i.suffix();
1037 if let Some(why) = ty::rejected(suffix) {
1038 return Err(format!("integer literal `{}`: {}", i, why));
1039 }
1040 let digits = i.base10_digits().to_string();
1041 // Rust's default for an unconstrained integer literal is i32.
1042 // Nim's is `int` (64-bit). Making the width explicit is what
1043 // keeps overflow behaviour the same on both sides.
1044 let t = if suffix.is_empty() {
1045 match expect {
1046 Some(t) if t.is_integer() => t.clone(),
1047 // Rust's fallback for an otherwise-unconstrained
1048 // integer literal.
1049 _ => Nim::Prim("int32".into()),
1050 }
1051 } else {
1052 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
1053 };
1054 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
1055 }
1056 Lit::Float(f) => {
1057 let t = match f.suffix() {
1058 "" => match expect {
1059 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
1060 _ => Nim::Prim("float64".into()),
1061 },
1062 "f64" => Nim::Prim("float64".into()),
1063 "f32" => Nim::Prim("float32".into()),
1064 s => return Err(format!("unknown float suffix `{s}`")),
1065 };
1066 let d = f.base10_digits();
1067 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
1068 Ok(Val::new(d, Some(t)))
1069 }
1070 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
1071 Lit::Str(s) => Ok(Val::new(
1072 fmt::nim_str(&s.value()),
1073 Some(Nim::Prim("string".into())),
1074 )),
1075 Lit::Char(c) => Ok(Val::new(
1076 format!("Rune({})", c.value() as u32),
1077 Some(Nim::Prim("Rune".into())),
1078 )),
1079 Lit::Byte(b) => Ok(Val::new(
1080 format!("{}'u8", b.value()),
1081 Some(Nim::Prim("uint8".into())),
1082 )),
1083 Lit::ByteStr(b) => {
1084 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
1085 Ok(Val::new(
1086 format!("@[{}]", bytes.join(", ")),
1087 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
1088 ))
1089 }
1090 other => Err(format!("unsupported literal: {other:?}")),
1091 }
1092 }
1093
1094 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
1095 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
1096 // the positive half of the range before the negation runs. Folding the
1097 // sign into the literal keeps `i8::MIN` and friends expressible.
1098 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
1099 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
1100 let v = self.lit_at(&l.lit, expect)?;
1101 return Ok(Val::new(format!("-{}", v.code), v.ty));
1102 }
1103 }
1104 let v = self.expr_at(&u.expr, expect)?;
1105 match u.op {
1106 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
1107 // Rust's `!` is logical on bool and bitwise-complement on integers.
1108 // Nim spells those `not` and `not` as well, so one mapping covers
1109 // both — but only because Nim overloads `not` the same way.
1110 UnOp::Not(_) => Ok(Val::new(format!("(not {})", v.code), v.ty)),
1111 UnOp::Deref(_) => Ok(v),
1112 _ => Err("unsupported unary operator".into()),
1113 }
1114 }
1115
1116 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
1117 // A comparison's operands are unrelated to the `bool` it produces, so
1118 // the outer expectation is not passed through to them.
1119 let down = match b.op {
1120 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
1121 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
1122 _ => expect,
1123 };
1124 let mut l = self.expr_at(&b.left, down)?;
1125 // Rust unifies the two operand types; propagating whichever side is
1126 // known to the other reproduces that, and disagreement then surfaces
1127 // as a Nim type error rather than as a silent width change.
1128 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
1129 if l.ty.is_none() && r.ty.is_some() {
1130 l = self.expr_at(&b.left, r.ty.as_ref())?;
1131 }
1132 let r = std::mem::replace(&mut r, Val::untyped(""));
1133 let op = self.bin_op(&b.op, &l, &r)?;
1134 let ty = match b.op {
1135 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
1136 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
1137 // Rust's shift takes its result type from the *left* operand, and
1138 // the right may be a different width entirely.
1139 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
1140 _ => l.ty.clone().or(r.ty.clone()),
1141 };
1142 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
1143 }
1144
1145 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
1146 Ok(match op {
1147 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
1148 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
1149 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
1150 BinOp::Div(_) | BinOp::DivAssign(_) => {
1151 // Nim spells integer division `div`. Both languages truncate
1152 // toward zero, so once the right operator is chosen the
1153 // semantics match, including for negative operands.
1154 let t = l.ty.clone().or(r.ty.clone()).ok_or(
1155 "cannot tell integer from float division here; annotate the operands",
1156 )?;
1157 if t.is_integer() { "div" } else { "/" }
1158 }
1159 BinOp::Rem(_) | BinOp::RemAssign(_) => {
1160 let t = l.ty.clone().or(r.ty.clone()).ok_or(
1161 "cannot tell integer from float remainder here; annotate the operands",
1162 )?;
1163 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
1164 }
1165 BinOp::And(_) => "and",
1166 BinOp::Or(_) => "or",
1167 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
1168 // bools, exactly as Rust's `&`/`|`/`^` are.
1169 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
1170 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
1171 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
1172 // Settled empirically: Nim's `shr` on a signed integer is
1173 // arithmetic, matching Rust. See DESIGN.md.
1174 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
1175 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
1176 BinOp::Eq(_) => "==",
1177 BinOp::Ne(_) => "!=",
1178 BinOp::Lt(_) => "<",
1179 BinOp::Le(_) => "<=",
1180 BinOp::Gt(_) => ">",
1181 BinOp::Ge(_) => ">=",
1182 other => return Err(format!("unsupported binary operator {other:?}")),
1183 })
1184 }
1185
1186 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
1187 let v = self.expr(&c.expr)?;
1188 let to = ty::map(&c.ty)?;
1189 let from = v.ty.clone().ok_or_else(|| {
1190 format!(
1191 "cannot lower `as {}`: the source type is unknown, and `as` \
1192 truncates, so the source width decides the result",
1193 to.render()
1194 )
1195 })?;
1196
1197 let code = match (&from, &to) {
1198 (f, t) if f.is_integer() && t.is_integer() => {
1199 // Rust's `as` between integers is a pure bit-width truncation
1200 // or sign-extension — never a range check. Nim's `T(x)` *does*
1201 // range-check and would raise where Rust wraps, so `cast` is
1202 // the only faithful spelling. Probed against both compilers.
1203 format!("cast[{}]({})", t.render(), v.code)
1204 }
1205 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
1206 format!("{}({})", p, v.code)
1207 }
1208 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
1209 format!("{}(ord({}))", t.render(), v.code)
1210 }
1211 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
1212 format!("cast[{}](int32({}))", t.render(), v.code)
1213 }
1214 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
1215 format!("Rune(int32({}))", v.code)
1216 }
1217 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
1218 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
1219 // Rust saturates float->int casts; Nim rounds and range-errors.
1220 // Not the same operation, so it is refused rather than mapped.
1221 return Err(format!(
1222 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
1223 no faithful mapping is implemented",
1224 t.render()
1225 ));
1226 }
1227 (f, t) => {
1228 return Err(format!(
1229 "unsupported cast from `{}` to `{}`",
1230 f.render(),
1231 t.render()
1232 ))
1233 }
1234 };
1235 Ok(Val::new(code, Some(to)))
1236 }
1237
1238 fn call(&mut self, c: &syn::ExprCall) -> Result<Val, String> {
1239 let Expr::Path(p) = &*c.func else {
1240 return Err("only calls to named functions are supported".into());
1241 };
1242 let name = path_name(&p.path);
1243 let ptys: Vec<Nim> = self
1244 .fns
1245 .get(&name)
1246 .map(|s| s.params.clone())
1247 .unwrap_or_default();
1248 let mut args = Vec::new();
1249 for (i, a) in c.args.iter().enumerate() {
1250 let want = ptys.get(i).cloned();
1251 args.push(self.expr_at(a, want.as_ref())?);
1252 }
1253 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
1254
1255 // Constructors from the prelude.
1256 // Constructors that live in the prelude rather than in the input file.
1257 if let Some(ctor) = match name.as_str() {
1258 "Some" => Some("rsSome"),
1259 "Ok" => Some("rsOk"),
1260 "Err" => Some("rsErr"),
1261 _ => None,
1262 } {
1263 return Ok(Val::new(format!("{}({})", ctor, codes.join(", ")), None));
1264 }
1265
1266 // A bare path that names a primitive type is Rust's tuple-struct-like
1267 // conversion, e.g. `String::from(..)`; handled by the method path.
1268 let ret = self.fns.get(&name).map(|s| s.ret.clone());
1269 if ret.is_none() && !self.structs.contains_key(&name) {
1270 return Err(format!(
1271 "call to unknown function `{name}`; only functions defined in \
1272 this file and the supported standard-library subset can be lowered"
1273 ));
1274 }
1275 Ok(Val::new(
1276 format!("{}({})", ident(&name), codes.join(", ")),
1277 ret,
1278 ))
1279 }
1280
1281 fn method(&mut self, m: &syn::ExprMethodCall) -> Result<Val, String> {
1282 let recv = self.expr(&m.receiver)?;
1283 let name = m.method.to_string();
1284 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
1285 // own type; `v.push(e)` takes the element type.
1286 let arg_want = match (name.as_str(), &recv.ty) {
1287 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
1288 (_, t) => t.clone(),
1289 };
1290 let mut args = Vec::new();
1291 for a in &m.args {
1292 args.push(self.expr_at(a, arg_want.as_ref())?);
1293 }
1294 let a0 = args.first().map(|a| a.code.clone());
1295 let rt = recv.ty.clone();
1296
1297 let (code, ty) = match name.as_str() {
1298 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
1299 // explicit so that a `usize` binding type-checks on the Nim side.
1300 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
1301 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
1302 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
1303 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
1304 | "into_iter" => (recv.code.clone(), rt.clone()),
1305 "unwrap" | "expect" => {
1306 let inner = match &rt {
1307 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
1308 Some(a[0].clone())
1309 }
1310 _ => None,
1311 };
1312 (format!("unwrap({})", recv.code), inner)
1313 }
1314 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
1315 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
1316 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
1317 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
1318
1319 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
1320 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
1321 // Nim raises OverflowDefect, so the operation is routed through
1322 // the unsigned view of the same width, which is what Rust's
1323 // wrapping_* is defined to compute.
1324 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
1325 let op = match name.as_str() {
1326 "wrapping_add" => "+",
1327 "wrapping_sub" => "-",
1328 _ => "*",
1329 };
1330 let t = rt.clone().ok_or_else(|| {
1331 format!("`{name}` needs a known receiver type to pick the wrapping width")
1332 })?;
1333 if !t.is_integer() {
1334 return Err(format!("`{name}` on a non-integer type"));
1335 }
1336 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
1337 if t.is_unsigned() {
1338 (format!("({} {} {})", recv.code, op, arg), Some(t))
1339 } else {
1340 let u = unsigned_peer(&t)?;
1341 (
1342 format!(
1343 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
1344 t.render(), u, recv.code, op, u, arg
1345 ),
1346 Some(t),
1347 )
1348 }
1349 }
1350 "abs" => (format!("abs({})", recv.code), rt.clone()),
1351 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
1352 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
1353 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
1354 "as_bytes" | "into_bytes" => (
1355 format!("rsBytes({})", recv.code),
1356 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
1357 ),
1358
1359 _ => {
1360 // A method defined in this file via `impl`. Nim's UFCS makes
1361 // the call site spelling identical.
1362 if let Some(sig) = self.fns.get(&name) {
1363 let ret = sig.ret.clone();
1364 let mut all = vec![recv.code.clone()];
1365 all.extend(args.iter().map(|a| a.code.clone()));
1366 (format!("{}({})", ident(&name), all.join(", ")), Some(ret))
1367 } else {
1368 return Err(format!(
1369 "unsupported method `.{name}()`; it is neither defined in \
1370 this file nor part of the standard-library subset that \
1371 has a verified Nim equivalent"
1372 ));
1373 }
1374 }
1375 };
1376 Ok(Val::new(code, ty))
1377 }
1378
1379 // -------------------------------------------------------------- macros
1380
1381 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
1382 let name = path_name(&mac.path);
1383 match name.as_str() {
1384 "println" | "print" | "eprintln" | "eprint" => {
1385 let s = self.format_args(mac)?;
1386 let nl = name.ends_with("ln");
1387 Ok(match (name.starts_with('e'), nl) {
1388 (false, true) => format!("echo {s}"),
1389 (false, false) => format!("stdout.write({s})"),
1390 (true, true) => format!("stderr.writeLine({s})"),
1391 (true, false) => format!("stderr.write({s})"),
1392 })
1393 }
1394 "format" => self.format_args(mac),
1395 "panic" => {
1396 let s = self.format_args(mac)?;
1397 Ok(format!("rsPanic({s})"))
1398 }
1399 "assert" => {
1400 let e: Expr = mac.parse_body().map_err(|e| format!("assert!: {e}"))?;
1401 let v = self.expr(&e)?;
1402 Ok(format!(
1403 "(if not ({}): rsPanic(\"assertion failed\"))",
1404 v.code
1405 ))
1406 }
1407 "vec" => {
1408 let body = mac.tokens.to_string();
1409 if body.trim().is_empty() {
1410 return Ok("@[]".into());
1411 }
1412 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
1413 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
1414 .map_err(|e| format!("vec!: {e}"))?;
1415 let mut parts = Vec::new();
1416 for e in &elems {
1417 parts.push(self.expr(e)?.code);
1418 }
1419 Ok(format!("@[{}]", parts.join(", ")))
1420 }
1421 other => Err(format!(
1422 "unsupported macro `{other}!`; a macro whose expansion is not \
1423 known cannot be lowered faithfully"
1424 )),
1425 }
1426 }
1427
1428 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
1429 fn format_args(&mut self, mac: &syn::Macro) -> Result<String, String> {
1430 let args: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
1431 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
1432 .map_err(|e| format!("format arguments: {e}"))?;
1433 let mut it = args.iter();
1434 let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = it.next() else {
1435 if args.is_empty() {
1436 return Ok("\"\"".into());
1437 }
1438 return Err("the first argument must be a literal format string".into());
1439 };
1440 let rest: Vec<&Expr> = it.collect();
1441
1442 let pieces = fmt::parse(&s.value())?;
1443 let mut parts: Vec<String> = Vec::new();
1444 let mut next = 0usize;
1445 let mut used = vec![false; rest.len()];
1446 for p in &pieces {
1447 match p {
1448 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
1449 fmt::Piece::Arg { r#ref, spec } => {
1450 let v = match r#ref {
1451 fmt::Ref::Next => {
1452 let e = rest.get(next).ok_or("too few arguments for format string")?;
1453 used[next] = true;
1454 next += 1;
1455 self.expr(e)?
1456 }
1457 fmt::Ref::Index(i) => {
1458 let e = rest.get(*i).ok_or("format index out of range")?;
1459 used[*i] = true;
1460 self.expr(e)?
1461 }
1462 fmt::Ref::Named(n) => {
1463 let t = self.lookup(n).ok_or_else(|| {
1464 format!("`{{{n}}}` captures `{n}`, which is not in scope")
1465 })?;
1466 Val::new(ident(n), Some(t))
1467 }
1468 };
1469 parts.push(fmt::render_arg(&v.code, spec));
1470 }
1471 }
1472 }
1473 // Rust rejects an argument that no `{}` consumes; so do we, rather
1474 // than dropping it from the output.
1475 if let Some(i) = used.iter().position(|u| !u) {
1476 return Err(format!(
1477 "argument {} is never used by the format string",
1478 i + 1
1479 ));
1480 }
1481 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
1482 }
1483}
1484
1485/// Whether an expression has a direct Nim expression form.
1486///
1487/// Nim's `if` is an expression only when every arm is a single expression, and
1488/// its `case` is never one here. Anything else has to be lowered as statements
1489/// that assign into a target.
1490fn expressible(e: &Expr) -> bool {
1491 match e {
1492 Expr::If(i) => {
1493 let Some(then) = single_expr(&i.then_branch) else { return false };
1494 if !expressible(then) {
1495 return false;
1496 }
1497 match &i.else_branch {
1498 None => false,
1499 Some((_, els)) => match &**els {
1500 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
1501 other => expressible(other),
1502 },
1503 }
1504 }
1505 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
1506 _ => true,
1507 }
1508}
1509
1510/// The single expression a block consists of, if that is all it is. An `if`
1511/// can only be lowered as a Nim `if`-expression when both arms are this shape.
1512fn single_expr(b: &syn::Block) -> Option<&Expr> {
1513 match (b.stmts.len(), b.stmts.first()) {
1514 (1, Some(Stmt::Expr(e, None))) => Some(e),
1515 _ => None,
1516 }
1517}
1518
1519// --------------------------------------------------------------- utilities
1520
1521fn takes_self(sig: &syn::Signature) -> bool {
1522 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
1523}
1524
1525fn path_name(p: &syn::Path) -> String {
1526 p.segments
1527 .last()
1528 .map(|s| s.ident.to_string())
1529 .unwrap_or_default()
1530}
1531
1532fn is_compound(op: &BinOp) -> bool {
1533 matches!(
1534 op,
1535 BinOp::AddAssign(_)
1536 | BinOp::SubAssign(_)
1537 | BinOp::MulAssign(_)
1538 | BinOp::DivAssign(_)
1539 | BinOp::RemAssign(_)
1540 | BinOp::BitAndAssign(_)
1541 | BinOp::BitOrAssign(_)
1542 | BinOp::BitXorAssign(_)
1543 | BinOp::ShlAssign(_)
1544 | BinOp::ShrAssign(_)
1545 )
1546}
1547
1548/// The Nim literal suffix for an integer type (`5'i32`).
1549fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
1550 let Nim::Prim(p) = t else {
1551 return Err("not a primitive integer".into());
1552 };
1553 Ok(match p.as_str() {
1554 "int8" => "i8",
1555 "int16" => "i16",
1556 "int32" => "i32",
1557 "int64" => "i64",
1558 "int" => "i",
1559 "uint8" => "u8",
1560 "uint16" => "u16",
1561 "uint32" => "u32",
1562 "uint64" => "u64",
1563 "uint" => "u",
1564 other => return Err(format!("no Nim literal suffix for `{other}`")),
1565 })
1566}
1567
1568/// The unsigned integer type of the same width, used to spell `wrapping_*`.
1569fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
1570 let Nim::Prim(p) = t else {
1571 return Err("not a primitive integer".into());
1572 };
1573 Ok(match p.as_str() {
1574 "int8" => "uint8",
1575 "int16" => "uint16",
1576 "int32" => "uint32",
1577 "int64" => "uint64",
1578 "int" => "uint",
1579 other => return Err(format!("`{other}` has no unsigned peer")),
1580 })
1581}
1582
1583fn item_kind(i: &Item) -> &'static str {
1584 match i {
1585 Item::Trait(_) => "`trait`",
1586 Item::Enum(_) => "`enum`",
1587 Item::Type(_) => "`type` alias",
1588 Item::Static(_) => "`static`",
1589 Item::Macro(_) => "macro definition",
1590 Item::Union(_) => "`union`",
1591 Item::ExternCrate(_) => "`extern crate`",
1592 Item::ForeignMod(_) => "`extern` block",
1593 _ => "item",
1594 }
1595}
1596
1597fn expr_kind(e: &Expr) -> &'static str {
1598 match e {
1599 Expr::Closure(_) => "closure",
1600 Expr::Async(_) => "`async` block",
1601 Expr::Await(_) => "`.await`",
1602 Expr::Try(_) => "`?`",
1603 Expr::Range(_) => "range",
1604 Expr::Match(_) => "`match` (only statement position is implemented)",
1605 Expr::Let(_) => "`let` expression",
1606 Expr::Unsafe(_) => "`unsafe` block",
1607 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
1608 _ => "expression",
1609 }
1610}