//! Rust AST -> Nim source. //! //! The governing rule is in DESIGN.md and it shapes every function here: //! anything whose Rust semantics cannot be reproduced exactly in Nim returns //! `Err` with a reason. Nothing is emitted on a guess. Where a construct maps //! one-to-one (signed `shr`, unsigned wrapping, truncating `div`/`mod`) the //! mapping is direct and there is a comment saying why that is safe. use crate::fmt; use crate::ty::{self, Nim}; use std::collections::HashMap; use syn::{ BinOp, Expr, FnArg, GenericArgument, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp, }; // --------------------------------------------------------------- vocabulary /// Nim keywords. Rust code may legally use any of these as an identifier. const NIM_KEYWORDS: &[&str] = &[ "addr", "and", "as", "asm", "bind", "block", "break", "case", "cast", "concept", "const", "continue", "converter", "defer", "discard", "distinct", "div", "do", "elif", "else", "end", "enum", "except", "export", "finally", "for", "from", "func", "if", "import", "in", "include", "interface", "is", "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not", "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref", "return", "shl", "shr", "static", "template", "try", "tuple", "type", "using", "var", "when", "while", "xor", "result", "echo", ]; fn ident(name: &str) -> String { if NIM_KEYWORDS.contains(&name) { format!("{name}_r") } else { name.to_string() } } /// A lowered expression: its Nim text, and its type where we know it. /// /// The type is not decoration. Nim needs it to pick `div` over `/`, to size a /// `cast`, and to annotate every binding so that Nim's own type checker /// catches a mistake in this file rather than letting it through as output /// that runs and is wrong. #[derive(Clone, Debug)] struct Val { code: String, ty: Option, } impl Val { fn new(code: impl Into, ty: Option) -> Self { Val { code: code.into(), ty } } fn untyped(code: impl Into) -> Self { Val { code: code.into(), ty: None } } } struct Sig { params: Vec, ret: Nim, } /// One variant of a Rust enum. #[derive(Clone)] struct Variant { name: String, /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get /// `f0`, `f1`, ...; every field is prefixed with the variant name because /// Nim requires the branches of a variant object to have distinct fields. fields: Vec<(String, Nim)>, } #[derive(Clone)] struct EnumDef { name: String, /// True when every variant is a unit variant, which Nim represents as a /// plain `enum` rather than an object variant. simple: bool, variants: Vec, } impl EnumDef { fn kind_ident(&self, v: &str) -> String { format!("k{}{}", self.name, v) } fn ctor_ident(&self, v: &str) -> String { format!("{}{}", self.name, v) } fn get(&self, v: &str) -> Option<&Variant> { self.variants.iter().find(|x| x.name == v) } } pub struct Lowerer { out: String, indent: usize, scopes: Vec>, fns: HashMap, /// struct name -> (field, type) structs: HashMap>, enums: HashMap, /// variant name -> enums declaring it. A variant named by more than one /// enum must be written qualified, or it is rejected as ambiguous. variant_owner: HashMap>, /// `type X = ...`, expanded before any type is mapped. aliases: HashMap, syn::Type)>, /// Cargo features that are on, as `--cfg feature=`. `#[cfg]` is /// evaluated against these exactly as rustc would, so an item that is /// dropped here is genuinely not part of the program being compiled. pub features: Vec, dropped_by_cfg: usize, /// Return type of the proc being lowered, so `return e` and a trailing /// expression can type their literals the way Rust's inference would. ret: Option, /// `(name, type)` that the arms of the `if`/`match` being lowered as a /// statement must assign their value to. target: Option<(String, Option)>, /// Set while lowering a `while` condition, which Nim re-evaluates each /// iteration and so cannot have statements hoisted out of it. in_loop_cond: bool, tmp: usize, } impl Lowerer { pub fn new() -> Self { Lowerer { out: String::new(), indent: 0, scopes: vec![HashMap::new()], fns: HashMap::new(), structs: HashMap::new(), enums: HashMap::new(), variant_owner: HashMap::new(), aliases: HashMap::new(), features: Vec::new(), dropped_by_cfg: 0, ret: None, target: None, in_loop_cond: false, tmp: 0, } } // ------------------------------------------------------------ emission fn line(&mut self, s: &str) { for _ in 0..self.indent { self.out.push_str(" "); } self.out.push_str(s); self.out.push('\n'); } fn blank(&mut self) { self.out.push('\n'); } fn fresh(&mut self, hint: &str) -> String { self.tmp += 1; format!("rsTmp{}{}", hint, self.tmp) } // --------------------------------------------------------------- scope fn push_scope(&mut self) { self.scopes.push(HashMap::new()); } fn pop_scope(&mut self) { self.scopes.pop(); } fn bind(&mut self, name: &str, t: Nim) { self.scopes.last_mut().unwrap().insert(name.to_string(), t); } fn lookup(&self, name: &str) -> Option { self.scopes.iter().rev().find_map(|s| s.get(name).cloned()) } // ---------------------------------------------------------------- file pub fn lower_file(&mut self, file: &syn::File) -> Result { self.out.push_str(include_str!("prelude.nim")); self.blank(); // Pass 1: signatures and struct shapes, so that a call can be typed // regardless of declaration order (Rust has no forward declarations). for item in &file.items { self.collect(item)?; } // Pass 2: bodies. for item in &file.items { self.item(item)?; } if self.fns.contains_key("main") { self.blank(); self.line("when isMainModule:"); self.indent += 1; self.line("try:"); self.line(" main()"); // Rust's panic exits 101 with a message on stderr. Nim's Defects // exit 1. Mapping them here is what keeps the differential runner's // exit-status comparison meaningful for panicking programs. self.line("except RustPanic as e:"); self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)"); self.line(" quit(101)"); self.line("except Defect as e:"); self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)"); self.line(" quit(101)"); self.indent -= 1; } Ok(std::mem::take(&mut self.out)) } fn collect(&mut self, item: &Item) -> Result<(), String> { // A `#[cfg(..)]` item exists only under some feature set. Dropping it // silently would change what the program does; picking a feature set // on the user's behalf would be a guess. So it is reported, except on // items that carry no runtime meaning here anyway. if !self.cfg_keeps(item_attrs(item))? { self.dropped_by_cfg += 1; return Ok(()); } match item { Item::Fn(f) => { let (params, ret) = self.signature(&f.sig)?; self.fns.insert(f.sig.ident.to_string(), Sig { params, ret }); } Item::Struct(s) => { let mut fields = Vec::new(); for (i, f) in s.fields.iter().enumerate() { let name = match &f.ident { Some(id) => id.to_string(), None => format!("f{i}"), // tuple struct }; fields.push((name, self.map_ty(&f.ty)?.owned())); } self.structs.insert(s.ident.to_string(), fields); } Item::Type(t) => { let params: Vec = t .generics .params .iter() .filter_map(|g| match g { syn::GenericParam::Type(t) => Some(t.ident.to_string()), _ => None, }) .collect(); self.aliases .insert(t.ident.to_string(), (params, (*t.ty).clone())); } Item::Enum(e) => { let name = e.ident.to_string(); if e.generics.params.iter().any(|p| !matches!(p, syn::GenericParam::Lifetime(_))) { return Err(format!("`enum {name}` is generic: not implemented yet")); } let mut variants = Vec::new(); for v in &e.variants { let vname = v.ident.to_string(); if v.discriminant.is_some() { return Err(format!( "`{name}::{vname}` has an explicit discriminant; Rust's \ `as` on such an enum has a value this lowering does not \ yet preserve" )); } let mut fields = Vec::new(); for (i, f) in v.fields.iter().enumerate() { // Nim requires the branches of a variant object to have // distinct field names, so each is prefixed. let fname = match &f.ident { Some(id) => format!("{vname}_{id}"), None => format!("{vname}_f{i}"), }; fields.push((fname, self.map_ty(&f.ty)?.owned())); } variants.push(Variant { name: vname, fields }); } let simple = variants.iter().all(|v| v.fields.is_empty()); for v in &variants { self.variant_owner .entry(v.name.clone()) .or_default() .push(name.clone()); } self.enums.insert( name.clone(), EnumDef { name, simple, variants }, ); } Item::Impl(im) => { let self_ty = self.map_ty(&im.self_ty)?; for it in &im.items { if let syn::ImplItem::Fn(m) = it { let (mut params, ret) = self.signature(&m.sig)?; if takes_self(&m.sig) { params.insert(0, self_ty.clone()); } self.fns.insert(m.sig.ident.to_string(), Sig { params, ret }); } } } _ => {} } Ok(()) } /// Whether `#[cfg(..)]` keeps this item, given the enabled features. /// /// This is evaluation, not approximation: rustc does the same thing, and /// an item whose predicate is false is not part of the compiled program. /// A predicate that cannot be evaluated is reported rather than assumed. fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result { for a in attrs { if a.path().is_ident("cfg") { let pred: syn::Meta = a .parse_args() .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?; if !self.cfg_eval(&pred)? { return Ok(false); } } } Ok(true) } fn cfg_eval(&self, m: &syn::Meta) -> Result { match m { syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => { let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { return Err("`feature = ..` expects a string".into()); }; Ok(self.features.iter().any(|f| *f == s.value())) } syn::Meta::List(l) if l.path.is_ident("not") => { let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?; Ok(!self.cfg_eval(&inner)?) } syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => { let items: syn::punctuated::Punctuated = l .parse_args_with(syn::punctuated::Punctuated::parse_terminated) .map_err(|e| e.to_string())?; let all = l.path.is_ident("all"); let mut acc = all; for i in &items { let v = self.cfg_eval(i)?; acc = if all { acc && v } else { acc || v }; } Ok(acc) } other => Err(format!( "`#[cfg({})]` is not a predicate rustnim can evaluate; only \ `feature = \"..\"`, `not`, `all` and `any` are implemented", quote_meta(other) )), } } /// Map a Rust type, expanding any `type` alias first. Every type in the /// lowering goes through here rather than calling `ty::map` directly, so /// an alias cannot be missed in one position and honoured in another. fn map_ty(&self, t: &syn::Type) -> Result { ty::map(&self.expand(t, 0)?) } fn expand(&self, t: &syn::Type, depth: usize) -> Result { if depth > 16 { return Err("type alias expansion did not terminate; is it cyclic?".into()); } let syn::Type::Path(p) = t else { return Ok(t.clone()) }; // Only an unqualified name can be one of this file's aliases. // `fmt::Result` and `core::result::Result` are different types that // merely end in the same segment. if p.path.segments.len() != 1 { return Ok(t.clone()); } let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) }; let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else { return Ok(t.clone()); }; let args: Vec = match &seg.arguments { syn::PathArguments::AngleBracketed(a) => a .args .iter() .filter_map(|g| match g { GenericArgument::Type(t) => Some(t.clone()), _ => None, }) .collect(), _ => vec![], }; if args.len() != params.len() { // Flattening several files into one module can bring a crate's own // alias (`type Result = Result`) into scope at a site // that meant the builtin (`Result`). Rust kept them apart by // module; here they are told apart by arity, and a use that fits // neither is left for `ty::map` to report. return Ok(t.clone()); } self.expand(&substitute(target, params, &args), depth + 1) } fn signature(&self, sig: &syn::Signature) -> Result<(Vec, Nim), String> { if sig.asyncness.is_some() { return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident)); } // Lifetime parameters carry no runtime meaning and Nim is GC'd, so // `fn encode<'a>(..)` is not generic for our purposes. Type and const // parameters genuinely are, and are rejected. if let Some(p) = sig.generics.params.iter().find(|p| !matches!(p, syn::GenericParam::Lifetime(_))) { let what = match p { syn::GenericParam::Const(_) => "const", _ => "type", }; return Err(format!( "`fn {}` has a {what} parameter: generics are not implemented yet", sig.ident )); } let mut params = Vec::new(); for a in &sig.inputs { if let FnArg::Typed(t) = a { params.push(self.map_ty(&t.ty)?); } } let ret = match &sig.output { ReturnType::Default => Nim::Unit, ReturnType::Type(_, t) => self.map_ty(t)?.owned(), }; Ok((params, ret)) } // --------------------------------------------------------------- items fn item(&mut self, item: &Item) -> Result<(), String> { if !self.cfg_keeps(item_attrs(item))? { return Ok(()); } match item { Item::Fn(f) => self.func(&f.sig, &f.block, None), Item::Struct(s) => { let name = s.ident.to_string(); let fields = self.structs[&name].clone(); self.line(&format!("type {}* = object", ident(&name))); self.indent += 1; if fields.is_empty() { self.line("discard"); } for (fname, fty) in &fields { self.line(&format!("{}*: {}", ident(fname), fty.render())); } self.indent -= 1; self.blank(); Ok(()) } Item::Type(_) => Ok(()), // expanded at every use site Item::Enum(e) => { let def = self.enums[&e.ident.to_string()].clone(); self.emit_enum(&def); Ok(()) } Item::Const(c) => { let t = self.map_ty(&c.ty)?.owned(); let v = self.expr(&c.expr)?; self.bind(&c.ident.to_string(), t.clone()); let line = format!("const {}*: {} = {}", ident(&c.ident.to_string()), t.render(), v.code); self.line(&line); self.blank(); Ok(()) } Item::Impl(im) => { let self_ty = self.map_ty(&im.self_ty)?; if im.trait_.is_some() { return Err(format!( "`impl Trait for {}`: trait impls are not implemented yet", self_ty.render() )); } for it in &im.items { match it { syn::ImplItem::Fn(m) => { let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; self.func(&m.sig, &m.block, recv)?; } _ => return Err("only `fn` items are supported inside `impl`".into()), } } Ok(()) } // `use` and `extern crate` are resolution directives with no Nim // analogue once everything is one module. Item::Use(_) | Item::ExternCrate(_) => Ok(()), Item::Mod(m) if m.content.is_some() => { // An inline `mod` is flattened; Nim has no nested modules in a // single file. let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); for i in &items { self.collect(i)?; } for i in &items { self.item(i)?; } Ok(()) } Item::Mod(m) => Err(format!( "`mod {};` refers to another file; pass that file to rustnim as \ an additional input instead", m.ident )), other => Err(format!("unsupported item: {}", item_kind(other))), } } /// `None` carries no type of its own, so Nim needs the `Option[T]` named. fn none_of(&self, expect: Option<&Nim>) -> String { match expect { Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => { format!("rsNone[{}]()", a[0].render()) } _ => "rsNone()".to_string(), } } fn emit_enum(&mut self, def: &EnumDef) { let name = ident(&def.name); if def.simple { // Every variant is a unit variant, so a plain Nim enum is an exact // fit: it compares, orders and `case`-checks like Rust's. self.line(&format!("type {name}* = enum")); self.indent += 1; for v in &def.variants { self.line(&format!("{}", ident(&v.name))); } self.indent -= 1; self.blank(); self.line(&format!("proc rsDebug*(x: {name}): string =")); self.indent += 1; self.line("case x"); for v in &def.variants { self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name)); } self.indent -= 1; self.blank(); return; } // A data-carrying enum is a Nim object variant: one discriminant enum // plus a branch per variant. This is the same shape the prelude uses // for `Option` and `Result`. self.line("type"); self.indent += 1; self.line(&format!("{}Kind* = enum", name)); self.indent += 1; for v in &def.variants { self.line(&def.kind_ident(&v.name)); } self.indent -= 1; self.blank(); self.line(&format!("{}* = object", name)); self.indent += 1; self.line(&format!("case kind*: {}Kind", name)); for v in &def.variants { if v.fields.is_empty() { self.line(&format!("of {}: discard", def.kind_ident(&v.name))); } else { self.line(&format!("of {}:", def.kind_ident(&v.name))); self.indent += 1; for (f, t) in &v.fields { self.line(&format!("{}*: {}", ident(f), t.render())); } self.indent -= 1; } } self.indent -= 2; self.blank(); for v in &def.variants { let args: Vec = v .fields .iter() .enumerate() .map(|(i, (_, t))| format!("a{}: {}", i, t.render())) .collect(); let inits: Vec = v .fields .iter() .enumerate() .map(|(i, (f, _))| format!("{}: a{}", ident(f), i)) .collect(); let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))]; all.extend(inits); self.line(&format!( "proc {}*({}): {} = {}({})", def.ctor_ident(&v.name), args.join(", "), name, name, all.join(", ") )); } self.blank(); self.line(&format!("proc rsDebug*(x: {name}): string =")); self.indent += 1; self.line("case x.kind"); for v in &def.variants { if v.fields.is_empty() { self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name)); } else { let parts: Vec = v .fields .iter() .map(|(f, _)| format!("rsDebug(x.{})", ident(f))) .collect(); self.line(&format!( "of {}: \"{}(\" & {} & \")\"", def.kind_ident(&v.name), v.name, parts.join(" & \", \" & ") )); } } self.indent -= 1; self.blank(); } /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength` /// to the enum that declares it. fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> { let segs: Vec = path.segments.iter().map(|s| s.ident.to_string()).collect(); let last = segs.last()?.clone(); if segs.len() >= 2 { if let Some(def) = self.enums.get(&segs[segs.len() - 2]) { if def.get(&last).is_some() { return Some((def.clone(), last)); } } } // Unqualified: only unambiguous if exactly one enum declares it. match self.variant_owner.get(&last) { Some(owners) if owners.len() == 1 => { let def = self.enums.get(&owners[0])?; Some((def.clone(), last)) } _ => None, } } fn func( &mut self, sig: &syn::Signature, body: &syn::Block, recv: Option, ) -> Result<(), String> { let name = sig.ident.to_string(); let (ptys, ret) = self.signature(sig)?; self.push_scope(); let mut rendered: Vec = Vec::new(); if let Some(self_ty) = recv { // `&mut self` and `mut self` both mean the body may mutate the // receiver; only the former is observable by the caller, and a Nim // `var` parameter is the faithful spelling of that. let mutable = matches!( sig.inputs.first(), Some(FnArg::Receiver(r)) if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some()) ); let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() }; rendered.push(format!("self: {}", t.render())); self.bind("self", self_ty); } let typed: Vec<&syn::PatType> = sig .inputs .iter() .filter_map(|a| match a { FnArg::Typed(t) => Some(t), _ => None, }) .collect(); for (p, t) in typed.iter().zip(ptys.iter()) { let pname = match &*p.pat { Pat::Ident(i) => i.ident.to_string(), _ => return Err("only plain identifier parameters are supported".into()), }; rendered.push(format!("{}: {}", ident(&pname), t.render())); // Inside the body a `var T` parameter is used exactly like a `T`. self.bind(&pname, t.clone().owned()); } let head = if ret == Nim::Unit { format!("proc {}*({}) =", ident(&name), rendered.join(", ")) } else { format!("proc {}*({}): {} =", ident(&name), rendered.join(", "), ret.render()) }; self.line(&head); self.indent += 1; let outer_ret = self.ret.replace(ret.clone()); // A Rust fn's trailing expression is its return value. Naming Nim's // implicit `result` as the target makes that true whether the tail is // a plain expression or an `if`/`match` with statement arms. let outer_target = if ret == Nim::Unit { self.target.take() } else { self.target.replace(("result".to_string(), Some(ret.clone()))) }; let before = self.out.len(); let tail = self.block_body_at(body, Some(&ret))?; self.target = outer_target; match tail { Some(v) if ret != Nim::Unit => { let code = v.code.clone(); self.line(&format!("result = {code}")); } Some(v) => { // A trailing expression in a `()`-returning fn is evaluated for // its effect; Nim requires an explicit discard. let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit); if needs_discard && !v.code.is_empty() { let code = v.code.clone(); self.line(&format!("discard {code}")); } } None => {} } if self.out.len() == before { self.line("discard"); } self.indent -= 1; self.ret = outer_ret; self.pop_scope(); self.blank(); Ok(()) } // ---------------------------------------------------------- statements /// Lower a block's statements. Returns the block's trailing expression, /// if it has one, *without* emitting it — the caller decides whether that /// value is a return value, a binding, or discarded. fn block_body(&mut self, b: &syn::Block) -> Result, String> { self.block_body_at(b, None) } fn block_body_at( &mut self, b: &syn::Block, expect: Option<&Nim>, ) -> Result, String> { // An assignment target belongs to *this* block's trailing expression // only. A non-final `if` is a statement and must not assign anything. let target = self.target.take(); let n = b.stmts.len(); let mut tail = None; for (i, st) in b.stmts.iter().enumerate() { let last = i + 1 == n; match st { Stmt::Expr(e, None) if last && expressible(e) => { tail = Some(self.expr_at(e, expect)?) } Stmt::Expr(e, None) if last => { // A trailing `if`/`match` with statement arms, or a loop. // Lower it as statements; if this block's value is wanted, // each arm assigns it. match &target { Some((t, ty)) => { let (t, ty) = (t.clone(), ty.clone()); self.assign_from(e, &t, ty.as_ref())?; } None => self.stmt(st)?, } } _ => self.stmt(st)?, } } self.target = target; Ok(tail) } /// Lower a block in statement position (loop bodies, `if` arms). fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> { self.push_scope(); self.indent += 1; let before = self.out.len(); let want = self.target.clone().and_then(|(_, t)| t); let tail = self.block_body_at(b, want.as_ref())?; self.emit_tail(tail); if self.out.len() == before { self.line("discard"); } self.indent -= 1; self.pop_scope(); Ok(()) } fn stmt(&mut self, s: &Stmt) -> Result<(), String> { match s { Stmt::Local(l) => self.local(l), Stmt::Expr(e, _) => { let v = self.expr_stmt(e)?; if let Some(v) = v { // A bare expression with a value must be discarded in Nim. let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit); let code = v.code.clone(); if needs { self.line(&format!("discard {code}")); } else if !code.is_empty() { self.line(&code); } } Ok(()) } Stmt::Item(i) => self.item(i), Stmt::Macro(m) => { let line = self.macro_call(&m.mac)?; self.line(&line); Ok(()) } } } fn local(&mut self, l: &Local) -> Result<(), String> { let (name, mutable, ann): (String, bool, Option) = match &l.pat { Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None), Pat::Type(t) => match &*t.pat { Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), Some(self.map_ty(&t.ty)?)), _ => return Err("only `let ` bindings are supported".into()), }, Pat::Wild(_) => ("_".into(), false, None), _ => return Err("destructuring `let` is not implemented yet".into()), }; let Some(init) = &l.init else { // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does // not. Rust's own rules make reading it before assignment illegal, // so the two agree on every program rustc accepts. let t = ann.ok_or("`let` without an initialiser needs a type annotation")?; let t = t.owned(); self.line(&format!("var {}: {}", ident(&name), t.render())); self.bind(&name, t); return Ok(()); }; if init.diverge.is_some() { return Err("`let ... else` is not implemented yet".into()); } if !expressible(&init.expr) && name != "_" { // The initialiser is an `if`/`match` whose arms are statements. // Declare first, then let each arm assign into the binding. let t = ann .clone() .ok_or_else(|| { format!( "`let {name} = match/if ...` needs a type annotation: \ its arms are statements, so the binding must be \ declared before they run" ) })? .owned(); self.line(&format!("var {}: {}", ident(&name), t.render())); self.bind(&name, t.clone()); let target = ident(&name); return self.assign_from(&init.expr, &target, Some(&t)); } let v = self.expr_at(&init.expr, ann.as_ref())?; let t = match (ann, &v.ty) { (Some(a), _) => a.owned(), (None, Some(t)) => t.clone().owned(), (None, None) => { return Err(format!( "cannot infer the type of `let {name}`; annotate it — \ guessing here would change integer width, and with it the \ meaning of any arithmetic on `{name}`" )) } }; if name == "_" { let code = v.code.clone(); self.line(&format!("discard {code}")); return Ok(()); } // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing // works in both, so a re-`let` of the same name needs no rename. let kw = if mutable { "var" } else { "let" }; let line = format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code); self.line(&line); self.bind(&name, t); Ok(()) } /// Expressions that are statements in Rust and statements in Nim too /// (control flow). Returns `None` when it emitted lines itself. fn expr_stmt(&mut self, e: &Expr) -> Result, String> { match e { Expr::If(_) => { self.if_stmt(e)?; Ok(None) } Expr::While(w) => { if w.label.is_some() { return Err("loop labels are not implemented yet".into()); } self.in_loop_cond = true; let c = self.expr(&w.cond); self.in_loop_cond = false; let c = c?; self.line(&format!("while {}:", c.code)); let saved = self.target.take(); self.nested_block(&w.body)?; self.target = saved; Ok(None) } Expr::Loop(l) => { if l.label.is_some() { return Err("loop labels are not implemented yet".into()); } self.line("while true:"); let saved = self.target.take(); self.nested_block(&l.body)?; self.target = saved; Ok(None) } Expr::ForLoop(f) => { self.for_loop(f)?; Ok(None) } Expr::Block(b) => { if b.label.is_some() { return Err("block labels are not implemented yet".into()); } self.line("block:"); self.nested_block(&b.block)?; Ok(None) } Expr::Match(_) => { self.match_stmt(e)?; Ok(None) } Expr::Return(r) => { match &r.expr { Some(e) => { let want = self.ret.clone(); let v = self.expr_at(e, want.as_ref())?; self.line(&format!("return {}", v.code)); } None => self.line("return"), } Ok(None) } Expr::Break(b) => { if b.expr.is_some() || b.label.is_some() { return Err("`break` with a value or a label is not implemented yet".into()); } self.line("break"); Ok(None) } Expr::Continue(c) => { if c.label.is_some() { return Err("labelled `continue` is not implemented yet".into()); } self.line("continue"); Ok(None) } Expr::Assign(a) => { let lhs = self.expr(&a.left)?; if !expressible(&a.right) { let target = lhs.code.clone(); return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None); } let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?; self.line(&format!("{} = {}", lhs.code, rhs.code)); Ok(None) } Expr::Binary(b) if is_compound(&b.op) => { let lhs = self.expr(&b.left)?; // `i += 1` must widen the literal to `i`'s type, not to the // i32 an unconstrained Rust literal would default to. let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?; let op = self.bin_op(&b.op, &lhs, &rhs)?; // Nim has no `shl=` etc., and `+=` on a `let` is illegal in // both languages, so the expanded form is always correct. self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code)); Ok(None) } Expr::Macro(m) => { let line = self.macro_call(&m.mac)?; self.line(&line); Ok(None) } _ => Ok(Some(self.expr(e)?)), } } /// Lower `e` in statement position, assigning each arm's value to /// `target`. This is how Rust's expression-oriented `if`/`match` survive /// the trip when their arms are too big for a Nim `if`-expression. fn assign_from( &mut self, e: &Expr, target: &str, expect: Option<&Nim>, ) -> Result<(), String> { let saved = self.target.replace((target.to_string(), expect.cloned())); let r = match e { Expr::If(_) => self.if_stmt(e), Expr::Match(_) => self.match_stmt(e), other => { let v = self.expr_at(other, expect)?; self.line(&format!("{} = {}", target, v.code)); Ok(()) } }; self.target = saved; r } /// Emit a block's value into the active assignment target, if there is /// one, or discard it if there is not. fn emit_tail(&mut self, v: Option) { let Some(v) = v else { return }; match self.target.clone() { Some((t, _)) => { let code = v.code.clone(); self.line(&format!("{t} = {code}")); } None => { let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit); let code = v.code.clone(); if needs { self.line(&format!("discard {code}")); } else if !code.is_empty() { self.line(&code); } } } } fn if_stmt(&mut self, e: &Expr) -> Result<(), String> { let Expr::If(i) = e else { unreachable!() }; if let Expr::Let(_) = &*i.cond { return Err("`if let` is not implemented yet".into()); } let c = self.expr(&i.cond)?; self.line(&format!("if {}:", c.code)); self.nested_block(&i.then_branch)?; match &i.else_branch { None => {} Some((_, els)) => match &**els { Expr::If(_) => { // Nim needs `elif`; splice the nested `if` in as one. let mark = self.out.len(); self.if_stmt(els)?; let tail = self.out.split_off(mark); let indent = " ".repeat(self.indent); self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1)); } Expr::Block(b) => { self.line("else:"); self.nested_block(&b.block)?; } _ => return Err("unsupported `else` form".into()), }, } Ok(()) } fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> { if f.label.is_some() { return Err("loop labels are not implemented yet".into()); } let name = match &*f.pat { Pat::Ident(i) => i.ident.to_string(), Pat::Wild(_) => "_".into(), _ => return Err("destructuring `for` patterns are not implemented yet".into()), }; // Strip the iterator adaptors that are no-ops once we are iterating a // Nim container directly. Anything else (`.map`, `.filter`, `.rev`) // is a real iterator and is rejected rather than silently dropped. let mut src = &*f.expr; loop { match src { Expr::MethodCall(m) if matches!(m.method.to_string().as_str(), "iter" | "into_iter" | "iter_mut") && m.args.is_empty() => { src = &m.receiver } Expr::Reference(r) => src = &r.expr, _ => break, } } let (header, elem) = match src { Expr::Range(r) => { let lo = match &r.start { Some(e) => self.expr(e)?, None => return Err("a `for` over `..n` needs a start bound".into()), }; let hi = match &r.end { Some(e) => self.expr(e)?, None => return Err("a `for` over an unbounded range would not terminate".into()), }; let op = match r.limits { syn::RangeLimits::HalfOpen(_) => "..<", syn::RangeLimits::Closed(_) => "..", }; let t = lo.ty.clone().or(hi.ty.clone()); (format!("{} {} {}", lo.code, op, hi.code), t) } other => { let v = self.expr(other)?; let elem = match v.ty.clone() { Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t), Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())), _ => None, }; (v.code, elem) } }; self.line(&format!("for {} in {}:", ident(&name), header)); self.push_scope(); if let Some(t) = elem { self.bind(&name, t); } self.indent += 1; let before = self.out.len(); let saved = self.target.take(); if let Some(v) = self.block_body(&f.body)? { let code = v.code.clone(); self.line(&format!("discard {code}")); } self.target = saved; if self.out.len() == before { self.line("discard"); } self.indent -= 1; self.pop_scope(); Ok(()) } fn match_stmt(&mut self, e: &Expr) -> Result<(), String> { let Expr::Match(m) = e else { unreachable!() }; let scrut = self.expr(&m.expr)?; let t = scrut .ty .clone() .ok_or("cannot infer the type of a `match` scrutinee")?; let name = self.fresh("Match"); self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code)); // A `match` whose arms neither bind nor guard is a Nim `case`, which // is exhaustiveness-checked the way Rust's is. Anything richer becomes // an if/elif chain, because Nim's `case` cannot destructure. let plain = m.arms.iter().all(|a| { !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat) }); if plain { self.match_case(m, &name, &t) } else { self.match_chain(m, &name, &t) } } fn match_case( &mut self, m: &syn::ExprMatch, name: &str, t: &Nim, ) -> Result<(), String> { // A variant object is discriminated by its `kind` field. let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple)); self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" })); let mut saw_wild = false; for arm in &m.arms { match &arm.pat { Pat::Wild(_) => { saw_wild = true; self.line("else:"); } p => { let labels = self.pat_labels(p, Some(t))?; self.line(&format!("of {}:", labels.join(", "))); } } self.arm_body(&arm.body)?; } if !saw_wild && !self.case_is_total(t, m) { // Rust checked exhaustiveness already, but Nim cannot always see // it -- an integer `case` needs every value covered -- so make the // unreachable arm explicit rather than leave a compile error. self.line("else:"); self.line(" rsPanic(\"unreachable match arm\")"); } Ok(()) } /// Whether a Nim `case` over this type is already total, in which case /// adding an `else` would be a compile error rather than a safety net. fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool { let Nim::Named(n, _) = t else { return false }; let Some(def) = self.enums.get(n) else { return false }; def.variants.len() == m.arms.len() } /// The if/elif form, for arms that bind or destructure. fn match_chain( &mut self, m: &syn::ExprMatch, name: &str, t: &Nim, ) -> Result<(), String> { let mut first = true; let mut closed = false; for arm in &m.arms { let (pat, guard) = match &arm.pat { Pat::Guard(g) => (&*g.pat, Some(&*g.guard)), p => (p, None), }; if guard.is_some() && binds(pat) { return Err("a `match` guard on a binding pattern is not \ implemented yet" .into()); } let test = self.pat_test(pat, name, t)?; let test = match (test, guard) { (Some(t), Some(g)) => { let g = self.expr(g)?; Some(format!("({}) and ({})", t, g.code)) } (None, Some(g)) => Some(self.expr(g)?.code), (t, None) => t, }; match test { Some(test) => { self.line(&format!( "{} {}:", if first { "if" } else { "elif" }, test )); first = false; } None => { // An irrefutable pattern: everything left falls here. if first { self.line("block:"); } else { self.line("else:"); } closed = true; } } self.indent += 1; self.push_scope(); let before = self.out.len(); self.pat_bind(pat, name, t)?; self.indent -= 1; self.arm_body_at(&arm.body, before)?; self.pop_scope(); if closed { break; } } if !closed { // Rust proved this unreachable; Nim cannot see that, and leaving // the chain open would silently fall through instead. self.line("else:"); self.line(" rsPanic(\"unreachable match arm\")"); } Ok(()) } /// The condition that selects this arm, or `None` if it always matches. fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result, String> { Ok(match p { Pat::Wild(_) => None, Pat::Ident(i) if i.subpat.is_none() => None, Pat::Or(o) => { let mut parts = Vec::new(); for c in &o.cases { match self.pat_test(c, name, t)? { Some(x) => parts.push(x), None => return Ok(None), } } Some(format!("({})", parts.join(" or "))) } Pat::Lit(_) | Pat::Range(_) => { let labels = self.pat_labels(p, Some(t))?; Some(match p { Pat::Range(_) => format!("({} in {})", name, labels[0]), _ => format!("({} == {})", name, labels[0]), }) } Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?), Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?), Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?), Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t), Pat::Reference(r) => return self.pat_test(&r.pat, name, t), _ => return Err("unsupported `match` pattern".into()), }) } /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant. fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result { let last = path_name(path); match last.as_str() { "Ok" => return Ok(format!("{name}.ok")), "Err" => return Ok(format!("(not {name}.ok)")), "Some" => return Ok(format!("{name}.has")), "None" => return Ok(format!("(not {name}.has)")), _ => {} } let Some((def, v)) = self.resolve_variant(path) else { return Err(format!( "`{last}` in a pattern is not a known enum variant; if it names \ an enum declared in another module, that is not implemented yet" )); }; if let Nim::Named(n, _) = t { if *n != def.name { return Err(format!( "pattern `{}::{}` does not match the scrutinee type `{}`", def.name, v, n )); } } Ok(if def.simple { format!("({} == {}.{})", name, ident(&def.name), ident(&v)) } else { format!("({}.kind == {})", name, def.kind_ident(&v)) }) } /// Emit the `let`s that a pattern's bindings introduce. fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> { match p { Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()), Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t), Pat::Reference(r) => self.pat_bind(&r.pat, name, t), Pat::Ident(i) if i.subpat.is_none() => { let b = i.ident.to_string(); self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name)); self.bind(&b, t.clone()); Ok(()) } Pat::TupleStruct(ts) => { let fields = self.variant_fields(&ts.path, t)?; for (i, sub) in ts.elems.iter().enumerate() { let Some((fname, fty)) = fields.get(i) else { return Err(format!( "pattern binds {} field(s) but the variant has {}", ts.elems.len(), fields.len() )); }; let access = format!("{}.{}", name, ident(fname)); self.pat_bind(sub, &access, fty)?; } Ok(()) } Pat::Struct(st) => { let fields = self.variant_fields(&st.path, t)?; for f in &st.fields { let syn::Member::Named(m) = &f.member else { return Err("unsupported struct pattern field".into()); }; let m = m.to_string(); let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else { return Err(format!("unknown field `{m}` in pattern")); }; let access = format!("{}.{}", name, ident(fname)); self.pat_bind(&f.pat, &access, fty)?; } Ok(()) } _ => Err("unsupported `match` pattern".into()), } } /// The payload fields a variant pattern destructures. fn variant_fields( &self, path: &syn::Path, t: &Nim, ) -> Result, String> { let last = path_name(path); // `Ok`/`Err`/`Some` read the prelude's own field names. if let Nim::Named(n, a) = t { match (n.as_str(), last.as_str()) { ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]), ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]), ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]), _ => {} } } let Some((def, v)) = self.resolve_variant(path) else { return Err(format!("`{last}` is not a known enum variant")); }; Ok(def.get(&v).map(|v| v.fields.clone()).unwrap_or_default()) } fn arm_body(&mut self, body: &Expr) -> Result<(), String> { self.indent += 1; let before = self.out.len(); self.indent -= 1; self.arm_body_at(body, before) } fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> { match body { Expr::Block(b) => self.nested_block(&b.block)?, other => { self.indent += 1; // An arm's value is the `match`'s value, so it is typed by // whatever the `match` is being assigned to -- without which // an `Ok(..)` arm has no way to know its `Result`. let want = self.target.clone().and_then(|(_, t)| t); let v = match (want, expressible(other)) { (Some(t), true) => Some(self.expr_at(other, Some(&t))?), _ => self.expr_stmt(other)?, }; self.emit_tail(v); self.indent -= 1; } } if self.out.len() == before { self.indent += 1; self.line("discard"); self.indent -= 1; } Ok(()) } fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result, String> { match p { Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]), Pat::Or(o) => { let mut out = Vec::new(); for p in &o.cases { out.extend(self.pat_labels(p, expect)?); } Ok(out) } Pat::Range(r) => { let lo = r.start.as_ref().ok_or("open-ended range pattern")?; let hi = r.end.as_ref().ok_or("open-ended range pattern")?; let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?); let op = match r.limits { syn::RangeLimits::HalfOpen(_) => "..<", syn::RangeLimits::Closed(_) => "..", }; Ok(vec![format!("{} {} {}", lo.code, op, hi.code)]) } Pat::Path(pp) => { if let Some((def, v)) = self.resolve_variant(&pp.path) { return Ok(vec![if def.simple { format!("{}.{}", ident(&def.name), ident(&v)) } else { def.kind_ident(&v) }]); } Ok(vec![ident(&path_name(&pp.path))]) } _ => Err("unsupported `match` pattern; only literals, ranges, `|` \ alternatives, enum variants and `_` are implemented" .into()), } } // --------------------------------------------------------- expressions fn expr(&mut self, e: &Expr) -> Result { self.expr_at(e, None) } /// Lower `e`, with the type the surrounding code expects of it. /// /// Rust infers an unsuffixed integer literal's type from its context and /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the /// expected type down to the literal is what makes `let x: u8 = 255` and /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the /// widths silently diverge, which is exactly the class of bug this /// project refuses to ship. fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result { match e { Expr::Lit(l) => self.lit_at(&l.lit, expect), Expr::Path(p) => { let name = path_name(&p.path); if name == "None" { return Ok(Val::new(self.none_of(expect), expect.cloned())); } // A unit enum variant used as a value: `Error::InvalidLength`. if let Some((def, v)) = self.resolve_variant(&p.path) { let ty = Some(Nim::Named(def.name.clone(), vec![])); return Ok(if def.simple { Val::new(format!("{}.{}", ident(&def.name), ident(&v)), ty) } else { Val::new(format!("{}()", def.ctor_ident(&v)), ty) }); } if let Some(t) = self.lookup(&name) { return Ok(Val::new(ident(&name), Some(t))); } // A top-level function used as a value, e.g. passed to a // parameter of `impl Fn(..)` type. if let Some(sig) = self.fns.get(&name) { let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone())); return Ok(Val::new(ident(&name), Some(t))); } Ok(Val::new(ident(&name), None)) } Expr::Paren(p) => { let v = self.expr_at(&p.expr, expect)?; Ok(Val::new(format!("({})", v.code), v.ty)) } Expr::Group(g) => self.expr_at(&g.expr, expect), // `&x` is a value in Nim; `&mut x` in an argument position binds to // a `var` parameter, which is also just `x` at the call site. Expr::Reference(r) => self.expr_at(&r.expr, expect), Expr::Unary(u) => self.unary(u, expect), Expr::Binary(b) => self.binary(b, expect), Expr::Cast(c) => self.cast(c), Expr::Index(i) => { let base = self.expr(&i.expr)?; let idx = self.expr(&i.index)?; // Rust indexes with usize; Nim wants an `int`, and a `uint` // index is a type error there rather than a silent conversion. let idx_code = match &idx.ty { Some(t) if t.is_unsigned() => format!("int({})", idx.code), _ => idx.code.clone(), }; let elem = match base.ty.clone() { Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t), Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())), _ => None, }; Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem)) } Expr::Field(f) => { let base = self.expr(&f.base)?; let name = match &f.member { syn::Member::Named(n) => n.to_string(), syn::Member::Unnamed(i) => format!("f{}", i.index), }; let t = match &base.ty { Some(Nim::Named(s, _)) => self .structs .get(s) .and_then(|fs| fs.iter().find(|(f, _)| *f == name)) .map(|(_, t)| t.clone()), _ => None, }; Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t)) } Expr::Try(t) => self.try_op(t), Expr::Call(c) => self.call(c, expect), Expr::MethodCall(m) => self.method(m), Expr::Macro(m) => { let code = self.macro_call(&m.mac)?; Ok(Val::new(code, None)) } Expr::Struct(s) => { if s.rest.is_some() { return Err("struct update syntax `..rest` is not implemented yet".into()); } // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*, // which is constructed positionally in Nim. if let Some((def, v)) = self.resolve_variant(&s.path) { let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default(); let mut args = vec![String::new(); fields.len()]; for f in &s.fields { let syn::Member::Named(m) = &f.member else { return Err("unsupported enum variant field".into()); }; let want = format!("{}_{}", v, m); let i = fields .iter() .position(|(n, _)| *n == want) .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?; args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code; } if let Some(i) = args.iter().position(|a| a.is_empty()) { return Err(format!( "`{}::{}` is missing field `{}`", def.name, v, fields[i].0 )); } return Ok(Val::new( format!("{}({})", def.ctor_ident(&v), args.join(", ")), Some(Nim::Named(def.name.clone(), vec![])), )); } let name = path_name(&s.path); let mut parts = Vec::new(); for f in &s.fields { let fname = match &f.member { syn::Member::Named(n) => n.to_string(), syn::Member::Unnamed(i) => format!("f{}", i.index), }; let want = self .structs .get(&name) .and_then(|fs| fs.iter().find(|(n, _)| *n == fname)) .map(|(_, t)| t.clone()); let v = self.expr_at(&f.expr, want.as_ref())?; parts.push(format!("{}: {}", ident(&fname), v.code)); } Ok(Val::new( format!("{}({})", ident(&name), parts.join(", ")), Some(Nim::Named(name, vec![])), )) } Expr::Array(a) => { let mut parts = Vec::new(); let mut elem = match expect { Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => { Some((**t).clone()) } _ => None, }; for e in &a.elems { let want = elem.clone(); let v = self.expr_at(e, want.as_ref())?; elem = elem.or(v.ty.clone()); parts.push(v.code); } let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t))); Ok(Val::new(format!("[{}]", parts.join(", ")), t)) } Expr::Repeat(r) => { let v = self.expr(&r.expr)?; let n = self.expr(&r.len)?; let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t))); Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t)) } Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))), Expr::Tuple(t) => { let mut parts = Vec::new(); let mut tys = Vec::new(); for e in &t.elems { let v = self.expr(e)?; tys.push(v.ty.clone()); parts.push(v.code); } let ty = tys .iter() .cloned() .collect::>>() .map(Nim::Tuple); Ok(Val::new(format!("({})", parts.join(", ")), ty)) } // `if` and `match` are expressions in both languages, but only // when every arm is itself a single expression. Expr::If(i) => self.if_expr(i, expect), Expr::Block(b) if b.block.stmts.len() == 1 => { if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() { self.expr_at(e, expect) } else { Err("block expression with statements in value position is not implemented yet".into()) } } other => Err(format!( "unsupported expression in value position: {}", expr_kind(other) )), } } fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result { let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else { return Err( "an `if` used as a value must have an `else` and single-expression arms".into(), ); }; let c = self.expr(&i.cond)?; let t = self.expr_at(then, expect)?; let want = expect.cloned().or_else(|| t.ty.clone()); let e = match &**els { Expr::Block(b) => match single_expr(&b.block) { Some(x) => self.expr_at(x, want.as_ref())?, None => return Err("an `if` used as a value must have single-expression arms".into()), }, other => self.expr_at(other, want.as_ref())?, }; let ty = t.ty.clone().or(e.ty.clone()); Ok(Val::new( format!("(if {}: {} else: {})", c.code, t.code, e.code), ty, )) } fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result { match l { Lit::Int(i) => { let suffix = i.suffix(); if let Some(why) = ty::rejected(suffix) { return Err(format!("integer literal `{}`: {}", i, why)); } let digits = i.base10_digits().to_string(); // Rust's default for an unconstrained integer literal is i32. // Nim's is `int` (64-bit). Making the width explicit is what // keeps overflow behaviour the same on both sides. let t = if suffix.is_empty() { match expect { Some(t) if t.is_integer() => t.clone(), // Rust's fallback for an otherwise-unconstrained // integer literal. _ => Nim::Prim("int32".into()), } } else { ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))? }; Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t))) } Lit::Float(f) => { let t = match f.suffix() { "" => match expect { Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()), _ => Nim::Prim("float64".into()), }, "f64" => Nim::Prim("float64".into()), "f32" => Nim::Prim("float32".into()), s => return Err(format!("unknown float suffix `{s}`")), }; let d = f.base10_digits(); let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") }; Ok(Val::new(d, Some(t))) } Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))), Lit::Str(s) => Ok(Val::new( fmt::nim_str(&s.value()), Some(Nim::Prim("string".into())), )), Lit::Char(c) => Ok(Val::new( format!("Rune({})", c.value() as u32), Some(Nim::Prim("Rune".into())), )), Lit::Byte(b) => Ok(Val::new( format!("{}'u8", b.value()), Some(Nim::Prim("uint8".into())), )), Lit::ByteStr(b) => { let bytes: Vec = b.value().iter().map(|x| format!("{x}'u8")).collect(); Ok(Val::new( format!("@[{}]", bytes.join(", ")), Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))), )) } other => Err(format!("unsupported literal: {other:?}")), } } fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result { // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow // the positive half of the range before the negation runs. Folding the // sign into the literal keeps `i8::MIN` and friends expressible. if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) { if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) { let v = self.lit_at(&l.lit, expect)?; return Ok(Val::new(format!("-{}", v.code), v.ty)); } } let v = self.expr_at(&u.expr, expect)?; match u.op { UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)), // Rust's `!` is logical on bool and bitwise-complement on integers. // Nim spells those `not` and `not` as well, so one mapping covers // both — but only because Nim overloads `not` the same way. UnOp::Not(_) => Ok(Val::new(format!("(not {})", v.code), v.ty)), UnOp::Deref(_) => Ok(v), _ => Err("unsupported unary operator".into()), } } fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result { // A comparison's operands are unrelated to the `bool` it produces, so // the outer expectation is not passed through to them. let down = match b.op { BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_) | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None, _ => expect, }; let mut l = self.expr_at(&b.left, down)?; // Rust unifies the two operand types; propagating whichever side is // known to the other reproduces that, and disagreement then surfaces // as a Nim type error rather than as a silent width change. let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?; if l.ty.is_none() && r.ty.is_some() { l = self.expr_at(&b.left, r.ty.as_ref())?; } let r = std::mem::replace(&mut r, Val::untyped("")); let op = self.bin_op(&b.op, &l, &r)?; let ty = match b.op { BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_) | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())), // Rust's shift takes its result type from the *left* operand, and // the right may be a different width entirely. BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(), _ => l.ty.clone().or(r.ty.clone()), }; Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty)) } fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> { Ok(match op { BinOp::Add(_) | BinOp::AddAssign(_) => "+", BinOp::Sub(_) | BinOp::SubAssign(_) => "-", BinOp::Mul(_) | BinOp::MulAssign(_) => "*", BinOp::Div(_) | BinOp::DivAssign(_) => { // Nim spells integer division `div`. Both languages truncate // toward zero, so once the right operator is chosen the // semantics match, including for negative operands. let t = l.ty.clone().or(r.ty.clone()).ok_or( "cannot tell integer from float division here; annotate the operands", )?; if t.is_integer() { "div" } else { "/" } } BinOp::Rem(_) | BinOp::RemAssign(_) => { let t = l.ty.clone().or(r.ty.clone()).ok_or( "cannot tell integer from float remainder here; annotate the operands", )?; if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) } } BinOp::And(_) => "and", BinOp::Or(_) => "or", // Nim's `and`/`or`/`xor` are bitwise on integers and logical on // bools, exactly as Rust's `&`/`|`/`^` are. BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and", BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or", BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor", // Settled empirically: Nim's `shr` on a signed integer is // arithmetic, matching Rust. See DESIGN.md. BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl", BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr", BinOp::Eq(_) => "==", BinOp::Ne(_) => "!=", BinOp::Lt(_) => "<", BinOp::Le(_) => "<=", BinOp::Gt(_) => ">", BinOp::Ge(_) => ">=", other => return Err(format!("unsupported binary operator {other:?}")), }) } fn cast(&mut self, c: &syn::ExprCast) -> Result { let v = self.expr(&c.expr)?; let to = self.map_ty(&c.ty)?; let from = v.ty.clone().ok_or_else(|| { format!( "cannot lower `as {}`: the source type is unknown, and `as` \ truncates, so the source width decides the result", to.render() ) })?; let code = match (&from, &to) { (f, t) if f.is_integer() && t.is_integer() => { // Rust's `as` between integers is a pure bit-width truncation // or sign-extension — never a range check. Nim's `T(x)` *does* // range-check and would raise where Rust wraps, so `cast` is // the only faithful spelling. Probed against both compilers. format!("cast[{}]({})", t.render(), v.code) } (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => { format!("{}({})", p, v.code) } (Nim::Prim(b), t) if b == "bool" && t.is_integer() => { format!("{}(ord({}))", t.render(), v.code) } (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => { format!("cast[{}](int32({}))", t.render(), v.code) } (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => { format!("Rune(int32({}))", v.code) } (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(), (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => { // Rust saturates float->int casts; Nim rounds and range-errors. // Not the same operation, so it is refused rather than mapped. return Err(format!( "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \ no faithful mapping is implemented", t.render() )); } (f, t) => { return Err(format!( "unsupported cast from `{}` to `{}`", f.render(), t.render() )) } }; Ok(Val::new(code, Some(to))) } /// Rust's `?`: return early on the error branch, otherwise yield the value. /// /// The early return is statements, not an expression, so they are emitted /// ahead of the line being built. Every caller lowers its sub-expressions /// before emitting its own line, which is what makes that ordering hold. fn try_op(&mut self, t: &syn::ExprTry) -> Result { if self.in_loop_cond { return Err("`?` in a loop condition is not implemented yet: the \ early-return it expands to would be evaluated once, \ before the loop, rather than on each iteration" .into()); } let v = self.expr(&t.expr)?; let vt = v.ty.clone().ok_or( "`?` needs a known `Result`/`Option` type; annotate the expression it applies to", )?; let ret = self .ret .clone() .ok_or("`?` outside a function with a return type")?; let tmp = self.fresh("Try"); self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code)); match (&vt, &ret) { (Nim::Named(a, ai), Nim::Named(b, bi)) if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 => { // Rust inserts a `From::from` on the error here. We only accept // the case where the error types already agree, rather than // silently dropping a conversion that might not be the identity. if ai[1] != bi[1] { return Err(format!( "`?` would need `From<{}> for {}`: an error-type conversion \ is not implemented, and assuming it is the identity would \ be a guess", ai[1].render(), bi[1].render() )); } self.line(&format!("if not {}.ok:", tmp)); self.line(&format!( " return rsErr[{}, {}]({}.err)", bi[0].render(), bi[1].render(), tmp )); Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone()))) } (Nim::Named(a, ai), Nim::Named(b, bi)) if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 => { self.line(&format!("if not {}.has:", tmp)); self.line(&format!(" return rsNone[{}]()", bi[0].render())); Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone()))) } _ => Err(format!( "`?` on `{}` in a function returning `{}` is not a supported \ combination", vt.render(), ret.render() )), } } fn call(&mut self, c: &syn::ExprCall, expect: Option<&Nim>) -> Result { let Expr::Path(p) = &*c.func else { return Err("only calls to named functions are supported".into()); }; let name = path_name(&p.path); let ptys: Vec = self .fns .get(&name) .map(|s| s.params.clone()) .unwrap_or_default(); let mut args = Vec::new(); for (i, a) in c.args.iter().enumerate() { let want = ptys.get(i).cloned(); args.push(self.expr_at(a, want.as_ref())?); } let codes: Vec = args.iter().map(|a| a.code.clone()).collect(); // Constructors from the prelude. // `Ok`/`Err` must name the *whole* Result type, not just the half // being constructed: Nim cannot infer `E` from an `Ok(v)` alone. match name.as_str() { "Some" => { let inner = match expect { Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(), _ => { return Err("`Some(..)` needs a known `Option` type here; \ annotate the binding or the return type" .into()) } }; return Ok(Val::new( format!("rsSome[{}]({})", inner, codes.join(", ")), expect.cloned(), )); } "Ok" | "Err" => { let (t, e) = match expect { Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => { (a[0].render(), a[1].render()) } _ => { return Err(format!( "`{name}(..)` needs a known `Result` type here; \ annotate the binding or the return type" )) } }; let ctor = if name == "Ok" { "rsOk" } else { "rsErr" }; let arg = if codes.is_empty() { String::new() } else { codes.join(", ") }; return Ok(Val::new( format!("{}[{}, {}]({})", ctor, t, e, arg), expect.cloned(), )); } _ => {} } // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. if let Some((def, v)) = self.resolve_variant(&p.path) { return Ok(Val::new( format!("{}({})", def.ctor_ident(&v), codes.join(", ")), Some(Nim::Named(def.name.clone(), vec![])), )); } // A bare path that names a primitive type is Rust's tuple-struct-like // conversion, e.g. `String::from(..)`; handled by the method path. // Calling a proc-typed local, which is how an `impl Fn(..)` parameter // is invoked. if let Some(Nim::Proc(_, ret)) = self.lookup(&name) { return Ok(Val::new( format!("{}({})", ident(&name), codes.join(", ")), Some((*ret).clone()), )); } let ret = self.fns.get(&name).map(|s| s.ret.clone()); if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) { return Err(format!( "call to unknown function `{name}`; only functions defined in \ this file and the supported standard-library subset can be lowered" )); } Ok(Val::new( format!("{}({})", ident(&name), codes.join(", ")), ret, )) } fn method(&mut self, m: &syn::ExprMethodCall) -> Result { let recv = self.expr(&m.receiver)?; let name = m.method.to_string(); // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's // own type; `v.push(e)` takes the element type. let arg_want = match (name.as_str(), &recv.ty) { ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()), (_, t) => t.clone(), }; let mut args = Vec::new(); for a in &m.args { args.push(self.expr_at(a, arg_want.as_ref())?); } let a0 = args.first().map(|a| a.code.clone()); let rt = recv.ty.clone(); let (code, ty) = match name.as_str() { // Rust's `len()` is `usize`; Nim's is `int`. The conversion is // explicit so that a `usize` binding type-checks on the Nim side. "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))), "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))), "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)), "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter" | "into_iter" => (recv.code.clone(), rt.clone()), "unwrap" | "expect" => { let inner = match &rt { Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => { Some(a[0].clone()) } _ => None, }; (format!("unwrap({})", recv.code), inner) } "ok_or" => { let inner = match &rt { Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(), _ => return Err("`ok_or` needs a known `Option` receiver".into()), }; let e = args.first().ok_or("`ok_or` takes one argument")?; let ety = e .ty .clone() .ok_or("`ok_or` needs a known error type for its argument")?; ( format!( "rsOkOr[{}, {}]({}, {})", inner.render(), ety.render(), recv.code, e.code ), Some(Nim::Named("Result".into(), vec![inner, ety])), ) } "unwrap_or" => { let inner = match &rt { Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => { Some(a[0].clone()) } _ => None, }; ( format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()), inner, ) } "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))), "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))), "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))), "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))), // Settled empirically: Nim's fixed-width *unsigned* arithmetic // wraps silently, matching Rust's `wrapping_*`. For *signed* types // Nim raises OverflowDefect, so the operation is routed through // the unsigned view of the same width, which is what Rust's // wrapping_* is defined to compute. "wrapping_add" | "wrapping_sub" | "wrapping_mul" => { let op = match name.as_str() { "wrapping_add" => "+", "wrapping_sub" => "-", _ => "*", }; let t = rt.clone().ok_or_else(|| { format!("`{name}` needs a known receiver type to pick the wrapping width") })?; if !t.is_integer() { return Err(format!("`{name}` on a non-integer type")); } let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?; if t.is_unsigned() { (format!("({} {} {})", recv.code, op, arg), Some(t)) } else { let u = unsigned_peer(&t)?; ( format!( "cast[{}](cast[{}]({}) {} cast[{}]({}))", t.render(), u, recv.code, op, u, arg ), Some(t), ) } } "abs" => (format!("abs({})", recv.code), rt.clone()), "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()), "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))), "as_bytes" | "into_bytes" => ( format!("rsBytes({})", recv.code), Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))), ), _ => { // A method defined in this file via `impl`. Nim's UFCS makes // the call site spelling identical. if let Some(sig) = self.fns.get(&name) { let ret = sig.ret.clone(); let mut all = vec![recv.code.clone()]; all.extend(args.iter().map(|a| a.code.clone())); (format!("{}({})", ident(&name), all.join(", ")), Some(ret)) } else { return Err(format!( "unsupported method `.{name}()`; it is neither defined in \ this file nor part of the standard-library subset that \ has a verified Nim equivalent" )); } } }; Ok(Val::new(code, ty)) } // -------------------------------------------------------------- macros fn macro_call(&mut self, mac: &syn::Macro) -> Result { let name = path_name(&mac.path); match name.as_str() { "println" | "print" | "eprintln" | "eprint" => { let s = self.format_args(mac)?; let nl = name.ends_with("ln"); Ok(match (name.starts_with('e'), nl) { (false, true) => format!("echo {s}"), (false, false) => format!("stdout.write({s})"), (true, true) => format!("stderr.writeLine({s})"), (true, false) => format!("stderr.write({s})"), }) } "format" => self.format_args(mac), "panic" => { let s = self.format_args(mac)?; Ok(format!("rsPanic({s})")) } "assert" => { let e: Expr = mac.parse_body().map_err(|e| format!("assert!: {e}"))?; let v = self.expr(&e)?; Ok(format!( "(if not ({}): rsPanic(\"assertion failed\"))", v.code )) } "vec" => { let body = mac.tokens.to_string(); if body.trim().is_empty() { return Ok("@[]".into()); } // `vec![elem; n]` is the repeat form, not a list. The macro // body has no brackets, so it is parsed directly. if body.contains(';') { let (v, n) = mac .parse_body_with(|input: syn::parse::ParseStream| { let v: Expr = input.parse()?; input.parse::()?; let n: Expr = input.parse()?; Ok((v, n)) }) .map_err(|e| format!("vec![elem; n]: {e}"))?; let v = self.expr(&v)?; let n = self.expr(&n)?; return Ok(format!("newSeqWith(int({}), {})", n.code, v.code)); } let elems: syn::punctuated::Punctuated = mac .parse_body_with(syn::punctuated::Punctuated::parse_terminated) .map_err(|e| format!("vec!: {e}"))?; let mut parts = Vec::new(); for e in &elems { parts.push(self.expr(e)?.code); } Ok(format!("@[{}]", parts.join(", "))) } other => Err(format!( "unsupported macro `{other}!`; a macro whose expansion is not \ known cannot be lowered faithfully" )), } } /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression. fn format_args(&mut self, mac: &syn::Macro) -> Result { let args: syn::punctuated::Punctuated = mac .parse_body_with(syn::punctuated::Punctuated::parse_terminated) .map_err(|e| format!("format arguments: {e}"))?; let mut it = args.iter(); let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = it.next() else { if args.is_empty() { return Ok("\"\"".into()); } return Err("the first argument must be a literal format string".into()); }; let rest: Vec<&Expr> = it.collect(); let pieces = fmt::parse(&s.value())?; let mut parts: Vec = Vec::new(); let mut next = 0usize; let mut used = vec![false; rest.len()]; for p in &pieces { match p { fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)), fmt::Piece::Arg { r#ref, spec } => { let v = match r#ref { fmt::Ref::Next => { let e = rest.get(next).ok_or("too few arguments for format string")?; used[next] = true; next += 1; self.expr(e)? } fmt::Ref::Index(i) => { let e = rest.get(*i).ok_or("format index out of range")?; used[*i] = true; self.expr(e)? } fmt::Ref::Named(n) => { let t = self.lookup(n).ok_or_else(|| { format!("`{{{n}}}` captures `{n}`, which is not in scope") })?; Val::new(ident(n), Some(t)) } }; parts.push(fmt::render_arg(&v.code, spec)); } } } // Rust rejects an argument that no `{}` consumes; so do we, rather // than dropping it from the output. if let Some(i) = used.iter().position(|u| !u) { return Err(format!( "argument {} is never used by the format string", i + 1 )); } Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") }) } } /// Whether a pattern introduces a binding. fn binds(p: &Pat) -> bool { match p { Pat::Ident(_) => true, Pat::Guard(g) => binds(&g.pat), Pat::Paren(x) => binds(&x.pat), Pat::Reference(r) => binds(&r.pat), Pat::Or(o) => o.cases.iter().any(binds), Pat::TupleStruct(t) => t.elems.iter().any(|_| true), Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true, _ => false, } } /// Whether a pattern looks inside the value, which a Nim `case` cannot do. fn destructures(p: &Pat) -> bool { matches!( p, Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) ) || matches!(p, Pat::Guard(g) if destructures(&g.pat)) || matches!(p, Pat::Paren(x) if destructures(&x.pat)) || matches!(p, Pat::Reference(r) if destructures(&r.pat)) } /// Whether an expression has a direct Nim expression form. /// /// Nim's `if` is an expression only when every arm is a single expression, and /// its `case` is never one here. Anything else has to be lowered as statements /// that assign into a target. fn expressible(e: &Expr) -> bool { match e { Expr::If(i) => { let Some(then) = single_expr(&i.then_branch) else { return false }; if !expressible(then) { return false; } match &i.else_branch { None => false, Some((_, els)) => match &**els { Expr::Block(b) => single_expr(&b.block).is_some_and(expressible), other => expressible(other), }, } } Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false, _ => true, } } /// The single expression a block consists of, if that is all it is. An `if` /// can only be lowered as a Nim `if`-expression when both arms are this shape. fn single_expr(b: &syn::Block) -> Option<&Expr> { match (b.stmts.len(), b.stmts.first()) { (1, Some(Stmt::Expr(e, None))) => Some(e), _ => None, } } /// Substitute `params[i] -> args[i]` through a type. Enough of the type /// grammar is covered to expand the aliases we accept; anything else is left /// alone and will be reported by `ty::map` if it is unsupported. fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type { use syn::Type; match t { Type::Path(p) => { if p.qself.is_none() && p.path.segments.len() == 1 { let seg = &p.path.segments[0]; if seg.arguments.is_empty() { let name = seg.ident.to_string(); if let Some(i) = params.iter().position(|x| *x == name) { return args[i].clone(); } } } let mut p = p.clone(); for seg in &mut p.path.segments { if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments { for g in &mut a.args { if let syn::GenericArgument::Type(t) = g { *t = substitute(t, params, args); } } } } Type::Path(p) } Type::Reference(r) => { let mut r = r.clone(); r.elem = Box::new(substitute(&r.elem, params, args)); Type::Reference(r) } Type::Slice(sl) => { let mut sl = sl.clone(); sl.elem = Box::new(substitute(&sl.elem, params, args)); Type::Slice(sl) } Type::Array(a) => { let mut a = a.clone(); a.elem = Box::new(substitute(&a.elem, params, args)); Type::Array(a) } Type::Tuple(tp) => { let mut tp = tp.clone(); tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect(); Type::Tuple(tp) } Type::Paren(p) => substitute(&p.elem, params, args), Type::Group(g) => substitute(&g.elem, params, args), other => other.clone(), } } // --------------------------------------------------------------- utilities fn takes_self(sig: &syn::Signature) -> bool { matches!(sig.inputs.first(), Some(FnArg::Receiver(_))) } fn path_name(p: &syn::Path) -> String { p.segments .last() .map(|s| s.ident.to_string()) .unwrap_or_default() } fn is_compound(op: &BinOp) -> bool { matches!( op, BinOp::AddAssign(_) | BinOp::SubAssign(_) | BinOp::MulAssign(_) | BinOp::DivAssign(_) | BinOp::RemAssign(_) | BinOp::BitAndAssign(_) | BinOp::BitOrAssign(_) | BinOp::BitXorAssign(_) | BinOp::ShlAssign(_) | BinOp::ShrAssign(_) ) } /// The Nim literal suffix for an integer type (`5'i32`). fn nim_suffix(t: &Nim) -> Result<&'static str, String> { let Nim::Prim(p) = t else { return Err("not a primitive integer".into()); }; Ok(match p.as_str() { "int8" => "i8", "int16" => "i16", "int32" => "i32", "int64" => "i64", "int" => "i", "uint8" => "u8", "uint16" => "u16", "uint32" => "u32", "uint64" => "u64", "uint" => "u", other => return Err(format!("no Nim literal suffix for `{other}`")), }) } /// The unsigned integer type of the same width, used to spell `wrapping_*`. fn unsigned_peer(t: &Nim) -> Result<&'static str, String> { let Nim::Prim(p) = t else { return Err("not a primitive integer".into()); }; Ok(match p.as_str() { "int8" => "uint8", "int16" => "uint16", "int32" => "uint32", "int64" => "uint64", "int" => "uint", other => return Err(format!("`{other}` has no unsigned peer")), }) } fn quote_meta(m: &syn::Meta) -> String { match m { syn::Meta::Path(p) => path_name(p), syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)), syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)), } } fn item_attrs(i: &Item) -> &[syn::Attribute] { match i { Item::Fn(f) => &f.attrs, Item::Struct(s) => &s.attrs, Item::Enum(e) => &e.attrs, Item::Impl(x) => &x.attrs, Item::Const(c) => &c.attrs, Item::Type(t) => &t.attrs, Item::Mod(m) => &m.attrs, Item::Use(u) => &u.attrs, Item::ExternCrate(e) => &e.attrs, Item::Static(s) => &s.attrs, _ => &[], } } fn item_kind(i: &Item) -> &'static str { match i { Item::Trait(_) => "`trait`", Item::Static(_) => "`static`", Item::Macro(_) => "macro definition", Item::Union(_) => "`union`", Item::ForeignMod(_) => "`extern` block", _ => "item", } } fn expr_kind(e: &Expr) -> &'static str { match e { Expr::Closure(_) => "closure", Expr::Async(_) => "`async` block", Expr::Await(_) => "`.await`", Expr::Try(_) => "`?`", Expr::Range(_) => "range", Expr::Match(_) => "`match` (only statement position is implemented)", Expr::Let(_) => "`let` expression", Expr::Unsafe(_) => "`unsafe` block", Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)", _ => "expression", } }