//! 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) { return format!("{name}_r"); } // Nim identifiers may not begin with an underscore, and may not contain // two in a row. Rust uses both freely (`_unused`, `__private`). let mut out = String::new(); let mut last_us = false; for (i, c) in name.chars().enumerate() { if c == '_' { if i == 0 { out.push('u'); out.push('_'); last_us = true; continue; } if last_us { continue; } last_us = true; out.push('_'); } else { last_us = false; out.push(c); } } if out.ends_with('_') { out.push('x'); } out } /// A `for`-loop source, resolved from a chain of iterator adaptors. /// /// Rust's slice iterators are lazy and compose; Nim's `for` is over one /// sequence. So a chain is resolved into this shape and then emitted as a /// single index loop, with each binding becoming an *lvalue* into the original /// container. That is what makes `*dst = v` through `iter_mut()` write back to /// the caller's slice rather than to a copy. #[derive(Clone, Debug)] enum Iter { /// `a..b` / `a..=b`. Range { lo: String, hi: String, closed: bool, ty: Option }, /// `for x in a`, `a.iter()`, `a.iter_mut()`. `off`/`len` let the same /// shape cover a subslice view. `mutable` only affects whether the binding /// may be assigned through. Elems { code: String, off: String, len: String, elem: Option, mutable: bool }, /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of /// `k` elements starting at `k * i`. Chunks { code: String, base: String, len: String, k: String, elem: Option, mutable: bool }, /// `a.windows(k)`: like `Chunks` but advancing one element at a time. Windows { code: String, base: String, len: String, k: String, elem: Option }, /// `.enumerate()` — the index is the first half of the pair. Enumerate(Box), /// `.zip(other)` — stops at the shorter, as Rust's does. Zip(Box, Box), } impl Iter { /// The number of iterations, as a Nim expression in terms of the loop's /// own containers. fn len(&self) -> String { match self { Iter::Range { lo, hi, closed, .. } => { let n = format!("(int({hi}) - int({lo}))"); if *closed { format!("({n} + 1)") } else { n } } Iter::Elems { len, .. } => len.clone(), Iter::Chunks { k, len, .. } => format!("({} div int({}))", len, k), Iter::Windows { len, k, .. } => format!("(max(0, {} - int({}) + 1))", len, k), Iter::Enumerate(i) => i.len(), Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()), } } } /// How a `for`-loop pattern name refers back into the container it came from. #[derive(Clone, Debug)] enum Alias { /// The name stands for this Nim lvalue expression. Value { code: String, ty: Option }, /// The name stands for a window: `code[off .. off + len - 1]`. Window { code: String, off: String, len: String, elem: Option }, } /// 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, /// Set when the value *is* a slice view rather than a Nim value: binding /// it introduces an alias, not a copy. window: Option, /// For `get`/`get_mut`: the condition under which the `Option` is `Some`, /// carried until an `ok_or`/`?` or an `unwrap` consumes it. Nim's view /// types cannot live inside an object, so an `Option` of a view has no /// runtime representation -- it is tracked here instead. guard: Option, /// The error an `ok_or` attached to that guard. guard_err: Option, } impl Val { fn new(code: impl Into, ty: Option) -> Self { Val { code: code.into(), ty, window: None, guard: None, guard_err: None } } fn untyped(code: impl Into) -> Self { Val::new(code, 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>, /// Names introduced by a `for` pattern that stand for an lvalue or a /// window into a container, rather than for a variable of their own. alias_scopes: Vec>, /// `(module, name) -> signature`. Rust keeps `lower::decode` and /// `mixed::decode` apart by module; flattening into one Nim module would /// merge them, so the module is part of the key and of the emitted name. fns: HashMap<(String, String), Sig>, /// Module being lowered: the file stem, or empty for the crate root. cur_mod: String, /// `use` brings a name into scope from another module. Flattening loses /// the module structure, so the mapping is recorded and consulted when a /// bare call is resolved. use_map: 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>, /// `(receiver type, method) -> signature`. Keyed by type because two /// types may define the same method name, and Nim tells them apart by /// overload resolution on the first parameter. methods: HashMap<(String, String), Sig>, /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}` /// on a user type can be checked rather than assumed. fmt_impls: HashMap<(String, String), ()>, /// `(from, to)` conversions declared by `impl From for B`. from_impls: HashMap<(String, String), String>, /// Forward declarations, emitted between the type definitions and the /// bodies. Rust has no declaration-before-use rule and Nim does, so every /// proc is declared up front rather than the input being reordered -- /// which would not work for mutual recursion anyway. forwards: Vec, /// Element type a `vec![..]` should build, from the binding's annotation. vec_expect: Option, /// While lowering a formatting impl: the `Formatter` parameter's name. /// Writes through it produce the proc's string result. fmt_param: Option, /// `type X = ...`, expanded before any type is mapped. aliases: HashMap, syn::Type)>, /// Module names supplied as separate input files. A `mod x;` naming one /// of these is satisfied by that file having been passed in. pub modules: Vec, /// 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()], alias_scopes: vec![HashMap::new()], fns: HashMap::new(), cur_mod: String::new(), use_map: HashMap::new(), structs: HashMap::new(), enums: HashMap::new(), variant_owner: HashMap::new(), methods: HashMap::new(), fmt_impls: HashMap::new(), from_impls: HashMap::new(), fmt_param: None, vec_expect: None, forwards: Vec::new(), aliases: HashMap::new(), modules: Vec::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()); self.alias_scopes.push(HashMap::new()); } fn pop_scope(&mut self) { self.scopes.pop(); self.alias_scopes.pop(); } fn bind_alias(&mut self, name: &str, a: Alias) { self.alias_scopes .last_mut() .unwrap() .insert(name.to_string(), a); } fn lookup_alias(&self, name: &str) -> Option { self.alias_scopes .iter() .rev() .find_map(|s| s.get(name).cloned()) } 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, files: &[(String, syn::File)]) -> Result { self.out.push_str(include_str!("prelude.nim")); self.blank(); // Pass 0: type aliases. A signature in one file may use an alias // declared in another, and inputs are given in whatever order suits // the caller, so aliases are registered before anything is mapped. for (m, f) in files { self.cur_mod = m.clone(); for item in &f.items { self.collect_aliases(item)?; } } // Pass 1: signatures and struct shapes, so that a call can be typed // regardless of declaration order (Rust has no forward declarations). for (m, f) in files { self.cur_mod = m.clone(); for item in &f.items { self.collect(item)?; } } // Pass 2: type definitions, which every signature may mention. for (m, f) in files { self.cur_mod = m.clone(); for item in &f.items { self.item_types(item)?; } } // Pass 3: forward declarations. Rust imposes no declaration order and // Nim does, so everything is declared before any body is emitted; // reordering the input would not handle mutual recursion anyway. if !self.forwards.is_empty() { for f in self.forwards.clone() { self.line(&f); } self.blank(); } // Pass 4: bodies. for (m, f) in files { self.cur_mod = m.clone(); for item in &f.items { self.item(item)?; } } if self.fns.contains_key(&(String::new(), "main".to_string())) { 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_aliases(&mut self, item: &Item) -> Result<(), String> { if !self.cfg_keeps(item_attrs(item))? { return Ok(()); } match item { Item::Use(u) => self.collect_use(&u.tree, &[]), 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::Mod(m) if m.content.is_some() => { let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); for i in &items { self.collect_aliases(i)?; } } _ => {} } Ok(()) } /// Record what a `use` brings into scope, as `name -> module`. fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) { use syn::UseTree; match t { UseTree::Path(p) => { let mut pre = prefix.to_vec(); pre.push(p.ident.to_string()); self.collect_use(&p.tree, &pre); } UseTree::Group(g) => { for t in &g.items { self.collect_use(t, prefix); } } UseTree::Name(n) => { let m = module_of(prefix); self.use_map.insert(n.ident.to_string(), m); } UseTree::Rename(r) => { let m = module_of(prefix); self.use_map.insert(r.rename.to_string(), m); } // A glob brings in an unknown set of names; resolution falls back // to the current module and the root, as it would without it. UseTree::Glob(_) => {} } } 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)?; let name = f.sig.ident.to_string(); let nim = self.fn_name(&self.cur_mod, &name); self.forwards.push(self.head_of(&nim, &f.sig, None)?); self.fns .insert((self.cur_mod.clone(), name), 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::Mod(m) if m.content.is_some() => { let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); for i in &items { self.collect(i)?; } } 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)?; let tyname = type_name(&self_ty); if let Some((path, _)) = &im.trait_ { let tr = path_name(path); if im.items.is_empty() { // A marker trait with no items. We do not model trait // resolution at all, so it generates nothing; any use // that actually needed the trait (a `dyn`, a bound) is // rejected where it appears. return Ok(()); } if is_fmt_trait(&tr) { self.forwards.push(format!( "proc {}*(self: {}): string", fmt_proc(&tr), self_ty.render() )); self.fmt_impls.insert((tyname, tr), ()); return Ok(()); } if tr == "From" { let syn::ImplItem::Fn(m) = &im.items[0] else { return Err("`impl From` must contain `fn from`".into()); }; let (params, _) = self.signature(&m.sig)?; let src = params .first() .ok_or("`fn from` takes one argument")? .clone(); let name = format!("rsFrom{}{}", tyname, type_name(&src)); self.forwards.push(self.head_of(&name, &m.sig, None)?); self.from_impls .insert((type_name(&src), tyname), name); return Ok(()); } return Err(format!( "`impl {tr} for {tyname}`: only formatting traits \ (Display, Debug, LowerHex, UpperHex, Binary, Octal), \ `From`, and marker traits with no items are implemented" )); } 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()); } let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; let head = self.head_of(&m.sig.ident.to_string(), &m.sig, recv.as_ref())?; self.forwards.push(head); self.methods .insert((tyname.clone(), 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) } /// The Nim name for a function, qualified by its module. fn fn_name(&self, module: &str, name: &str) -> String { if module.is_empty() { ident(name) } else { format!("{}_{}", module, ident(name)) } } /// Resolve a call path to the module and name it refers to: an explicit /// `mixed::decode`, then the current module, then the crate root. fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> { let segs: Vec = path.segments.iter().map(|s| s.ident.to_string()).collect(); let last = segs.last()?.clone(); if segs.len() >= 2 { let q = &segs[segs.len() - 2]; if self.fns.contains_key(&(q.clone(), last.clone())) { return Some((q.clone(), last)); } } let imported = self.use_map.get(&last).cloned(); for m in [Some(self.cur_mod.clone()), imported, Some(String::new())] .into_iter() .flatten() { if self.fns.contains_key(&(m.clone(), last.clone())) { return Some((m, last)); } } None } /// The Nim `proc` head for a Rust signature, used both for the forward /// declaration and for the definition, so the two cannot drift apart. fn head_of( &self, name: &str, sig: &syn::Signature, recv: Option<&Nim>, ) -> Result { let (ptys, ret) = self.signature(sig)?; let mut parts = Vec::new(); if let Some(self_ty) = recv { 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() }; parts.push(format!("self: {}", t.render())); } let typed: Vec<&syn::PatType> = sig .inputs .iter() .filter_map(|a| match a { FnArg::Typed(t) => Some(t), _ => None, }) .collect(); for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() { let pname = match &*p.pat { Pat::Ident(id) => id.ident.to_string(), Pat::Wild(_) => format!("unused{}", parts.len()), _ => return Err("only plain identifier parameters are supported".into()), }; let _ = i; parts.push(format!("{}: {}", ident(&pname), t.render())); } Ok(if ret == Nim::Unit { format!("proc {}*({})", ident(name), parts.join(", ")) } else { format!("proc {}*({}): {}", ident(name), parts.join(", "), ret.render()) }) } fn signature(&self, sig: &syn::Signature) -> Result<(Vec, Nim), String> { // `unsafe fn` marks a contract for callers; it does not change what // the body means, so it lowers like any other proc. 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, // A returned `&[T]` is a borrow of the caller's buffer, so it // stays an `openArray` view. Only an owned type (`Vec`) becomes // a `seq`, which `owned()` would do to both. ReturnType::Type(_, t) => { let n = self.map_ty(t)?; if returns_borrow(t) { n } else { n.owned() } } }; Ok((params, ret)) } // --------------------------------------------------------------- items /// Emit the type definitions only: they must precede every signature. fn item_types(&mut self, item: &Item) -> Result<(), String> { if !self.cfg_keeps(item_attrs(item))? { return Ok(()); } match item { Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item), Item::Mod(m) if m.content.is_some() => { let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default(); for i in &items { self.item_types(i)?; } Ok(()) } _ => Ok(()), } } fn item(&mut self, item: &Item) -> Result<(), String> { if !self.cfg_keeps(item_attrs(item))? { return Ok(()); } // Types were emitted in their own pass. if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) { return Ok(()); } self.item_inner(item) } fn item_inner(&mut self, item: &Item) -> Result<(), String> { match item { Item::Fn(f) => { let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string()); self.func_named(&nim, &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 let Some((path, _)) = &im.trait_ { let tr = path_name(path); if im.items.is_empty() { return Ok(()); } let syn::ImplItem::Fn(m) = &im.items[0] else { return Err(format!("unsupported item in `impl {tr}`")); }; if is_fmt_trait(&tr) { return self.fmt_impl(&tr, &self_ty, &m.sig, &m.block); } if tr == "From" { let name = { let (params, _) = self.signature(&m.sig)?; let src = params.first().cloned().ok_or("`fn from` takes one argument")?; self.from_impls[&(type_name(&src), type_name(&self_ty))].clone() }; return self.func_named(&name, &m.sig, &m.block, None); } return Err(format!("`impl {tr}` is not implemented")); } 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.item(i)?; } Ok(()) } Item::Mod(m) => { // Satisfied if that file was passed in too; everything is one // Nim module, so the declaration itself emits nothing. if self.modules.iter().any(|x| *x == m.ident.to_string()) { return Ok(()); } Err(format!( "`mod {};` refers to another file that was not passed to \ rustnim; add it to the input list", 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, } } /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string. /// /// Rust's `Formatter` is a sink that a `fmt` method writes into; the /// observable result of `{}` is exactly the bytes written. So the method /// becomes `proc rsDisplay(self: T): string` and every write through the /// formatter produces that string. A `fmt` body that does anything else /// with the formatter -- padding, precision, `debug_struct` -- is rejected, /// because those affect the output and this model does not carry them. /// The window an expression names, if it names one. fn window_of(&self, e: &Expr) -> Option { match e { Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) { Some(a @ Alias::Window { .. }) => Some(a), _ => None, }, Expr::Reference(r) => self.window_of(&r.expr), Expr::Paren(p) => self.window_of(&p.expr), Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr), _ => None, } } /// Whether an expression is the `Formatter` parameter of the formatting /// impl currently being lowered. fn is_fmt_param(&self, e: &Expr) -> bool { let Some(f) = &self.fmt_param else { return false }; match e { Expr::Path(p) => path_name(&p.path) == *f, Expr::Reference(r) => self.is_fmt_param(&r.expr), Expr::Paren(p) => self.is_fmt_param(&p.expr), _ => false, } } fn fmt_impl( &mut self, tr: &str, self_ty: &Nim, sig: &syn::Signature, body: &syn::Block, ) -> Result<(), String> { let proc_name = fmt_proc(tr); // The formatter is the parameter after `self`. let f = sig .inputs .iter() .filter_map(|a| match a { FnArg::Typed(t) => match &*t.pat { Pat::Ident(i) => Some(i.ident.to_string()), _ => None, }, _ => None, }) .next() .ok_or("`fn fmt` needs a `Formatter` parameter")?; self.push_scope(); self.bind("self", self_ty.clone()); let saved = self.fmt_param.replace(f); let outer_ret = self.ret.replace(Nim::Prim("string".into())); let outer_target = self .target .replace(("result".to_string(), Some(Nim::Prim("string".into())))); self.line(&format!( "proc {}*(self: {}): string =", proc_name, self_ty.render() )); self.indent += 1; let before = self.out.len(); let want = Nim::Prim("string".into()); let tail = self.block_body_at(body, Some(&want))?; self.emit_tail(tail); if self.out.len() == before { self.line("discard"); } self.indent -= 1; self.target = outer_target; self.ret = outer_ret; self.fmt_param = saved; self.pop_scope(); self.blank(); Ok(()) } fn func( &mut self, sig: &syn::Signature, body: &syn::Block, recv: Option, ) -> Result<(), String> { let name = sig.ident.to_string(); self.func_named(&name.clone(), sig, body, recv) } fn func_named( &mut self, name: &str, sig: &syn::Signature, body: &syn::Block, recv: Option, ) -> Result<(), 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(), // `fn from(_: Error) -> ..` — the parameter is unused, but Nim // still needs a name for it. Pat::Wild(_) => format!("unused{}", rendered.len()), _ => 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())?; if let Some(w) = v.window.clone() { // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a // view into the caller's buffer. Copying it into a `seq` would // still print the right bytes but would stop writes reaching the // caller, so it is bound as an alias. if v.guard.is_some() && v.guard_err.is_some() { return Err(format!( "`let {name} = ...get(..)` keeps an `Option` of a slice view, \ which Nim cannot represent; apply `?` or `unwrap()` to it \ in the same expression" )); } self.bind_alias(&name, w); return Ok(()); } 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::Unsafe(u) => { // Transparent in statement position too, for the same reason. self.nested_block_flat(&u.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 it = self.resolve_iter(&f.expr)?; // One index loop drives the whole chain. Rust's adaptors are lazy and // compose; resolving them to an index and binding each name to an // lvalue reproduces that without materialising anything. let i = self.fresh("Idx"); self.line(&format!("for {} in 0 ..< int({}):", i, it.len())); self.indent += 1; self.push_scope(); let before = self.out.len(); self.bind_pattern(&f.pat, &it, &i)?; 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.pop_scope(); self.indent -= 1; Ok(()) } /// Resolve a chain of iterator adaptors into a single `Iter`. /// /// Only adaptors with an exact index-loop equivalent are accepted. `map`, /// `filter`, `take_while` and friends are rejected rather than partially /// honoured: silently dropping an adaptor would change which elements the /// loop visits. fn resolve_iter(&mut self, e: &Expr) -> Result { match e { Expr::Reference(r) => self.resolve_iter(&r.expr), Expr::Paren(p) => self.resolve_iter(&p.expr), 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 ty = lo.ty.clone().or(hi.ty.clone()); Ok(Iter::Range { lo: lo.code, hi: hi.code, closed: matches!(r.limits, syn::RangeLimits::Closed(_)), ty, }) } Expr::MethodCall(m) => { let name = m.method.to_string(); match name.as_str() { "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => { let mut it = self.resolve_iter(&m.receiver)?; if name == "iter_mut" { if let Iter::Elems { mutable, .. } = &mut it { *mutable = true; } } Ok(it) } "enumerate" if m.args.is_empty() => { Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?))) } "zip" if m.args.len() == 1 => { let a = self.resolve_iter(&m.receiver)?; let b = self.resolve_iter(&m.args[0])?; Ok(Iter::Zip(Box::new(a), Box::new(b))) } "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => { let (code, base, len, elem) = self.slice_parts(&m.receiver)?; let k = self.expr(&m.args[0])?; Ok(Iter::Chunks { code, base, len, k: k.code, elem, mutable: name.ends_with("_mut"), }) } "windows" if m.args.len() == 1 => { let (code, base, len, elem) = self.slice_parts(&m.receiver)?; let k = self.expr(&m.args[0])?; Ok(Iter::Windows { code, base, len, k: k.code, elem }) } other => Err(format!( "iterator adaptor `.{other}()` is not implemented; it has \ no index-loop equivalent here, and dropping it would \ change which elements the loop visits" )), } } other => { // A `for` binding that is itself a window iterates that window, // not the whole container it points into. if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) { return Ok(Iter::Elems { code, off, len, elem, mutable: false }); } let v = self.expr(other)?; Ok(Iter::Elems { len: format!("{}.len", v.code), elem: elem_of(&v.ty), code: v.code, off: "0".into(), mutable: false, }) } } } /// Bind a `for` pattern against a resolved iterator at index `i`. fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> { match (p, it) { (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => { self.bind_pattern(&t.elems[0], a, i)?; self.bind_pattern(&t.elems[1], b, i) } (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => { if let Pat::Ident(id) = &t.elems[0] { let n = id.ident.to_string(); // Rust's `enumerate` counts in `usize`. self.line(&format!("let {}: uint = uint({})", ident(&n), i)); self.bind(&n, Nim::Prim("uint".into())); } self.bind_pattern(&t.elems[1], inner, i) } (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err( "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(), ), (Pat::Wild(_), _) => Ok(()), // `for &byte in xs` — the `&` destructures the reference, which in // Nim is already the value. (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i), (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i), (Pat::Ident(id), _) => { let name = id.ident.to_string(); match it { Iter::Range { lo, ty, .. } => { let t = ty.clone().unwrap_or(Nim::Prim("int".into())); // The loop counts from zero; the range's own start is // added back so the binding has Rust's value and type. self.line(&format!( "let {}: {} = {}({}) + {}", ident(&name), t.render(), t.render(), i, lo )); self.bind(&name, t); Ok(()) } Iter::Elems { code, off, elem, mutable, .. } => { let access = if off == "0" { format!("{}[{}]", code, i) } else { format!("{}[{} + {}]", code, off, i) }; if *mutable { // An alias, not a copy: assigning through the // binding must reach the original element. self.bind_alias( &name, Alias::Value { code: access, ty: elem.clone() }, ); } else { let t = elem .clone() .ok_or("cannot infer the element type of this `for`")?; self.line(&format!( "let {}: {} = {}", ident(&name), t.render(), access )); self.bind(&name, t); } Ok(()) } Iter::Chunks { code, base, k, elem, .. } => { self.bind_alias( &name, Alias::Window { code: code.clone(), off: format!("({} + {} * int({}))", base, i, k), len: format!("int({})", k), elem: elem.clone(), }, ); Ok(()) } Iter::Windows { code, base, k, elem, .. } => { self.bind_alias( &name, Alias::Window { code: code.clone(), off: format!("({} + {})", base, i), len: format!("int({})", k), elem: elem.clone(), }, ); Ok(()) } // Handled above: a zip or enumerate needs a tuple pattern, // and binding one name to the pair is not supported. Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(), } } _ => Err("unsupported `for` pattern".into()), } } 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 struct used as a value: `fmt::Error`, or a `struct S;` // declared here. In Nim that is a constructor call. if p.path.segments.len() > 1 { let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() }); if let Ok(Nim::Prim(n)) = ty::map(&ty) { if n == "FmtError" { return Ok(Val::new("FmtError()", Some(Nim::Prim(n)))); } } } if self.structs.get(&name).is_some_and(|f| f.is_empty()) { return Ok(Val::new( format!("{}()", ident(&name)), Some(Nim::Named(name.clone(), vec![])), )); } // 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) }); } // A `for` binding that stands for an element of the container // it came from: using it must read (and assigning through it // must write) that element, not a copy. if let Some(a) = self.lookup_alias(&name) { return Ok(match a { Alias::Value { code, ty } => Val::new(code, ty), // A window *is* a slice; as a value it is the view it // denotes, which is what Rust's `&[T]` means too. Alias::Window { code, off, len, elem } => Val::new( format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len), elem.map(|e| Nim::OpenArray(Box::new(e))), ), }); } 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(k) = self.resolve_fn(&p.path) { let sig = &self.fns[&k]; let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone())); return Ok(Val::new(self.fn_name(&k.0, &k.1), 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) if matches!(&*i.index, Expr::Range(_)) => { let Expr::Range(r) = &*i.index else { unreachable!() }; let base = self.expr(&i.expr)?; let lo = match &r.start { Some(e) => format!("int({})", self.expr(e)?.code), None => "0".into(), }; // Nim's `toOpenArray` takes an inclusive upper bound. let hi = match (&r.end, r.limits) { (Some(e), syn::RangeLimits::HalfOpen(_)) => { format!("int({}) - 1", self.expr(e)?.code) } (Some(e), syn::RangeLimits::Closed(_)) => { format!("int({})", self.expr(e)?.code) } (None, _) => format!("{}.len - 1", base.code), }; let elem = elem_of(&base.ty) .ok_or("cannot infer the element type of this slice")?; Ok(Val::new( format!("{}.toOpenArray({}, {})", base.code, lo, hi), Some(Nim::OpenArray(Box::new(elem))), )) } Expr::Index(i) => { if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) { let idx = self.expr(&i.index)?; return Ok(Val::new( format!("{}[{} + int({})]", code, off, idx.code), elem, )); } 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)) } // `unsafe` is a permission marker, not a semantic change: it does // not alter what the enclosed operations mean. So the block is // transparent here, and each operation inside still goes through // the ordinary lowering -- and is still rejected if it has no // faithful mapping. Expr::Unsafe(u) => match single_expr(&u.block) { Some(e) => self.expr_at(e, expect), None => Err("an `unsafe` block used as a value must be a single \ expression" .into()), }, Expr::Closure(c) => self.closure(c, expect), Expr::Try(t) => self.try_op(t), Expr::Call(c) => self.call(c, expect), Expr::MethodCall(m) => self.method(m, expect), Expr::Macro(m) if path_name(&m.mac.path) == "vec" => { // `vec![..]`'s elements take their type from the annotation on // the binding, exactly as Rust's would. let want = match expect { Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()), _ => None, }; let saved = std::mem::replace(&mut self.vec_expect, want.clone()); let code = self.macro_call(&m.mac); self.vec_expect = saved; let code = code?; let ty = match want { Some(e) => Some(Nim::Seq(Box::new(e))), None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))), }; Ok(Val::new(code, ty)) } 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. /// The container, start offset, length and element type an expression /// denotes as a slice. A window alias contributes its own offset, so /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight /// into the original buffer rather than through a rebuilt view. fn slice_parts( &mut self, e: &Expr, ) -> Result<(String, String, String, Option), String> { if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) { return Ok((code, off, len, elem)); } let v = self.expr(e)?; let len = format!("{}.len", v.code); Ok((v.code, "0".to_string(), len, elem_of(&v.ty))) } /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline. fn map_closure( &mut self, what: &str, recv: &Val, kind: &str, targs: &[Nim], c: &syn::ExprClosure, ) -> Result { if c.capture.is_some() { return Err("a `move` closure captures by value; Nim's closures \ capture by reference, and the two are not the same" .into()); } if c.inputs.len() != 1 { return Err(format!("`.{what}()` takes a one-argument closure")); } let pname = match &c.inputs[0] { Pat::Ident(i) => i.ident.to_string(), Pat::Wild(_) => "unused0".into(), _ => return Err("only plain identifier closure parameters are supported".into()), }; let is_opt = kind == "Option"; let tmp = self.fresh("Map"); let recv_ty = Nim::Named(kind.to_string(), targs.to_vec()); self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code)); let body = match &*c.body { Expr::Block(b) => single_expr(&b.block) .ok_or("a closure body with statements is not implemented yet")?, other => other, }; self.push_scope(); // The parameter names the payload itself, so a view stays a view. self.bind_alias( &pname, Alias::Value { code: format!("{}.val", tmp), ty: Some(targs[0].clone()), }, ); let v = self.expr(body)?; self.pop_scope(); let inner = v .ty .clone() .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?; // `and_then`'s closure already returns the wrapped type; `map`'s does // not and has to be re-wrapped. let (test, some_branch, none_branch, out_ty) = if is_opt { let out = if what == "map" { Nim::Named("Option".into(), vec![inner.clone()]) } else { inner.clone() }; let body_code = if what == "map" { format!("rsSome[{}]({})", inner.render(), v.code) } else { v.code.clone() }; ( format!("{}.has", tmp), body_code, format!("rsNone[{}]()", elem_arg(&out).render()), out, ) } else { let e = targs[1].clone(); let out = if what == "map" { Nim::Named("Result".into(), vec![inner.clone(), e.clone()]) } else { inner.clone() }; let ok_ty = elem_arg(&out); let body_code = if what == "map" { format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code) } else { v.code.clone() }; ( format!("{}.ok", tmp), body_code, format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp), out, ) }; Ok(Val::new( format!("(if {}: {} else: {})", test, some_branch, none_branch), Some(out_ty), )) } /// `|x| x + 1` -> a Nim anonymous proc. /// /// Nim's closures capture by reference, as Rust's non-`move` closures do. /// A `move` closure captures by value, which is a different thing, so it /// is rejected rather than lowered to the same construct. fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result { if c.capture.is_some() { return Err("a `move` closure captures by value; Nim's closures \ capture by reference, and the two are not the same" .into()); } let want: Option<&Vec> = match expect { Some(Nim::Proc(a, _)) => Some(a), _ => None, }; self.push_scope(); let mut parts = Vec::new(); let mut ptys = Vec::new(); for (i, p) in c.inputs.iter().enumerate() { let (name, ann) = match p { Pat::Ident(id) => (id.ident.to_string(), None), Pat::Type(t) => match &*t.pat { Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)), _ => return Err("only plain identifier closure parameters are supported".into()), }, Pat::Wild(_) => (format!("unused{i}"), None), _ => return Err("only plain identifier closure parameters are supported".into()), }; let t = ann .or_else(|| want.and_then(|w| w.get(i).cloned())) .ok_or_else(|| { format!( "cannot infer the type of closure parameter `{name}`; \ annotate it" ) })?; parts.push(format!("{}: {}", ident(&name), t.render())); self.bind(&name, t.clone()); ptys.push(t); } let ret_ann = match &c.output { ReturnType::Default => None, ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()), }; let body = match &*c.body { Expr::Block(b) => single_expr(&b.block) .ok_or("a closure body with statements is not implemented yet")?, other => other, }; let v = self.expr_at(body, ret_ann.as_ref())?; self.pop_scope(); let ret = ret_ann .or_else(|| v.ty.clone()) .ok_or("cannot infer a closure's return type; annotate it")?; Ok(Val::new( format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code), Some(Nim::Proc(ptys, Box::new(ret))), )) } /// Lower a block's statements at the current indentation, without opening /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope /// of its own in the generated code. fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> { self.push_scope(); let tail = self.block_body(b)?; self.emit_tail(tail); self.pop_scope(); Ok(()) } 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)?; if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) { // An `Option`/`Result` of a view: the check is emitted here and the // view itself survives as an alias, since it has no value form. let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?; let err = v.guard_err.clone().ok_or( "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is", )?; let Nim::Named(n, ra) = &ret else { return Err(format!("`?` in a function returning `{}`", ret.render())); }; if n != "Result" || ra.len() != 2 { return Err(format!("`?` in a function returning `{}`", ret.render())); } self.line(&format!("if not {}:", guard)); self.line(&format!( " return rsErr[{}, {}]({})", ra[0].render(), ra[1].render(), err )); let mut out = Val::new(String::new(), None); out.window = Some(w); return Ok(out); } 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. Where the // types differ we call the crate's own `impl From`; we never // assume the conversion is the identity. let err = if ai[1] == bi[1] { format!("{}.err", tmp) } else { let key = (type_name(&ai[1]), type_name(&bi[1])); let f = self.from_impls.get(&key).cloned().ok_or_else(|| { format!( "`?` needs `From<{}> for {}` to convert the error, and \ no such `impl` is in scope; assuming the conversion is \ the identity would be a guess", key.0, key.1 ) })?; format!("{}({}.err)", f, tmp) }; self.line(&format!("if not {}.ok:", tmp)); self.line(&format!( " return rsErr[{}, {}]({})", bi[0].render(), bi[1].render(), err )); 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 target = self.resolve_fn(&p.path); let ptys: Vec = target .as_ref() .and_then(|k| self.fns.get(k)) .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(), )); } _ => {} } // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a // string view; no copy, no validation, same memory. if name == "from_utf8_unchecked" && codes.len() == 1 { return Ok(Val::new( format!("rsStrView({})", codes[0]), Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))), )); } // 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 = target.as_ref().and_then(|k| self.fns.get(k)).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" )); } let nim = match &target { Some((m, n)) => self.fn_name(m, n), None => ident(&name), }; Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret)) } fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result { let name = m.method.to_string(); if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) { match name.as_str() { "len" => { return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into())))) } "is_empty" => { return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into())))) } other => { return Err(format!( "`.{other}()` on a slice window from `chunks_exact`/\ `windows` is not implemented; only indexing and \ `len()` are" )) } } } let recv = self.expr(&m.receiver)?; let rt0 = recv.ty.clone(); // `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no // way to put a view in an object, so instead of materialising an // Option the view and its validity condition travel together until // an `ok_or`/`?`/`unwrap` resolves them. if matches!(name.as_str(), "get" | "get_mut") && matches!(m.args.first(), Some(Expr::Range(_))) { let Some(Expr::Range(r)) = m.args.first() else { unreachable!() }; let (code, base, blen, belem) = self.slice_parts(&m.receiver)?; let lo = match &r.start { Some(e) => format!("int({})", self.expr(e)?.code), None => "0".into(), }; let len = match (&r.end, r.limits) { (Some(e), syn::RangeLimits::HalfOpen(_)) => { format!("(int({}) - {})", self.expr(e)?.code, lo) } (Some(e), syn::RangeLimits::Closed(_)) => { format!("(int({}) - {} + 1)", self.expr(e)?.code, lo) } (None, _) => format!("({} - {})", blen, lo), }; // Hoisted, so the bounds are computed once -- as Rust computes // them once -- and cannot be re-evaluated later in a scope where // the names they mention have been shadowed by a loop pattern. let off_t = self.fresh("Off"); let len_t = self.fresh("Len"); self.line(&format!("let {}: int = {} + {}", off_t, base, lo)); self.line(&format!("let {}: int = {}", len_t, len)); let elem = belem .or_else(|| elem_of(&rt0)) .ok_or("cannot infer the element type of this slice")?; let mut v = Val::new( String::new(), Some(Nim::Named( "Option".into(), vec![Nim::OpenArray(Box::new(elem.clone()))], )), ); v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen)); v.window = Some(Alias::Window { code, off: off_t, len: len_t, elem: Some(elem), }); return Ok(v); } // `.map`/`.and_then` over an `Option`/`Result` take a closure whose // parameter type comes from the receiver, so they are handled before // the arguments are lowered. The closure is expanded inline, with its // parameter aliased to the payload: that keeps the whole thing an // expression and avoids handing a view to a generic proc. if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 { if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) = (recv.ty.clone(), &m.args[0]) { if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2) { return self.map_closure(&name, &recv, &kind, &targs, c); } } } // `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" if recv.guard.is_some() => { let e = args.first().ok_or("`ok_or` takes one argument")?; let ety = e.ty.clone(); let mut v = recv.clone(); v.guard_err = Some(e.code.clone()); v.ty = match (&recv.ty, ety) { (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => { Some(Nim::Named("Result".into(), vec![a[0].clone(), et])) } _ => None, }; return Ok(v); } "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), ) } } // Inside a formatting impl, a write through the `Formatter` *is* // the value the proc returns, so it lowers to the string written. "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => ( a0.ok_or("`write_str` takes one argument")?, Some(Nim::Prim("string".into())), ), "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())))), ), "into" => { // `.into()` resolves through the `impl From` declarations, and // needs the target type to pick one. let from = rt .clone() .ok_or("`.into()` needs a known receiver type")?; let to = expect .ok_or("`.into()` needs a known target type; annotate the binding")?; let key = (type_name(&from), type_name(to)); let f = self.from_impls.get(&key).cloned().ok_or_else(|| { format!( "no `impl From<{}> for {}` in this file, so `.into()` has \ no conversion to call", key.0, key.1 ) })?; (format!("{}({})", f, recv.code), Some(to.clone())) } _ => { // A method defined in this file via `impl`, found by the // receiver's type rather than by name alone. let key = rt.as_ref().map(|t| (type_name(t), name.clone())); let sig = key.and_then(|k| self.methods.get(&k)).map(|s| s.ret.clone()); if let Some(ret) = sig { 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 /// The element type of a `vec![..]`, from its first element. fn vec_elem(&mut self, mac: &syn::Macro) -> Result, String> { let body = mac.tokens.to_string(); if body.trim().is_empty() { return Ok(None); } let first: Option = if body.contains(';') { // The whole body must be consumed or the parse fails, so the // length is parsed too even though only the element is wanted. mac.parse_body_with(|input: syn::parse::ParseStream| { let v: Expr = input.parse()?; input.parse::()?; let _len: Expr = input.parse()?; Ok(v) }) .ok() } else { mac.parse_body_with( syn::punctuated::Punctuated::::parse_terminated, ) .ok() .and_then(|p| p.into_iter().next()) }; match first { Some(e) => Ok(self.expr(&e)?.ty), None => Ok(None), } } 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), "write" | "writeln" => { // `write!(f, "..", ..)` inside a formatting impl: the first // argument is the sink, the rest is an ordinary format call. let args: Vec = mac .parse_body_with(syn::punctuated::Punctuated::::parse_terminated) .map_err(|e| format!("write!: {e}"))? .into_iter() .collect(); let sink = args.first().ok_or("`write!` needs a sink")?; if !self.is_fmt_param(sink) { return Err("`write!` to anything but the `Formatter` of the \ enclosing formatting impl is not implemented" .into()); } let s = self.format_pieces(&args[1..])?; Ok(if name == "writeln" { format!("({} & \"\\n\")", s) } else { s }) } "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 want = self.vec_expect.clone(); let v = self.expr_at(&v, want.as_ref())?; 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 want = self.vec_expect.clone(); let mut parts = Vec::new(); for e in &elems { parts.push(self.expr_at(e, want.as_ref())?.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: Vec = mac .parse_body_with(syn::punctuated::Punctuated::::parse_terminated) .map_err(|e| format!("format arguments: {e}"))? .into_iter() .collect(); self.format_pieces(&args) } /// `["{} {}", a, b]` -> a Nim string-concatenation expression. fn format_pieces(&mut self, args: &[Expr]) -> Result { let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = args.first() else { if args.is_empty() { return Ok("\"\"".into()); } return Err("the first argument must be a literal format string".into()); }; let rest: Vec<&Expr> = args[1..].iter().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 /// Whether a return type is a borrow of one of the arguments, which Nim /// models with a view rather than with an owned copy. fn returns_borrow(t: &syn::Type) -> bool { match t { syn::Type::Reference(r) => match &*r.elem { syn::Type::Slice(_) => true, // `&str` is a borrow of someone else's bytes too, and returning it // means returning a view, not an owned string. syn::Type::Path(p) => p.path.is_ident("str"), _ => false, }, syn::Type::Paren(p) => returns_borrow(&p.elem), syn::Type::Group(g) => returns_borrow(&g.elem), _ => false, } } /// The module a `use` prefix names. `crate`, `self` and `super` all resolve /// to the crate root, which is where a flattened module's items live unless /// they came from one of the extra input files. fn module_of(prefix: &[String]) -> String { match prefix.last() { Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(), _ => String::new(), } } /// The first type argument of an `Option[T]` / `Result[T, E]`. fn elem_arg(t: &Nim) -> Nim { match t { Nim::Named(_, a) if !a.is_empty() => a[0].clone(), other => other.clone(), } } /// The element type of a sequence-like Nim type. fn elem_of(t: &Option) -> Option { match t { Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()), Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())), _ => None, } } /// The short name a Nim type is known by, for keying method tables. fn type_name(t: &Nim) -> String { match t { Nim::Named(n, _) => n.clone(), Nim::Prim(p) => p.clone(), other => other.render(), } } fn is_fmt_trait(t: &str) -> bool { matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal") } /// The prelude proc a formatting trait's output is produced by. fn fmt_proc(t: &str) -> &'static str { match t { "Display" => "rsDisplay", "Debug" => "rsDebug", "LowerHex" => "rsLowerHex", "UpperHex" => "rsUpperHex", "Binary" => "rsBinary", _ => "rsOctal", } } 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::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", } }