//! 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 }, /// The name stands for an iterator that has not been consumed yet, as in /// `let it = xs.chunks_exact(k);`. Rust's iterators are values; ours are /// resolved chains, so the chain is carried until a `for` consumes it. Iterator(Box), } /// 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, /// Type parameters this signature is generic in, so a call site can bind /// them from its argument types. generics: Vec, } /// One variant of a Rust enum. #[derive(Clone)] struct Variant { name: String, /// `Error = 1` — Nim enums take explicit ordinals too, so the value is /// preserved rather than the variant being renumbered. discriminant: Option, /// `(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, /// The type of the `impl` block being lowered, which `Self` names. self_ty: Option, /// Type parameters of the enclosing `impl`, which its methods share. impl_generics: Vec, /// Type parameters of the proc being lowered, impl's included. fn_generics: Vec, /// Type parameters declared by each generic struct or enum. type_generics: HashMap>, /// `(type, name) -> type` for `type Item = ..;` inside an `impl`. Rust /// writes those as `Self::Item`, which has to resolve before any /// signature mentioning it is mapped. assoc: HashMap<(String, String), Nim>, /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`. assoc_consts: HashMap<(String, String), (String, Nim)>, /// Symbols declared by an `extern "C"` block. foreign: std::collections::HashSet, /// Const-qualified C pointer aliases already declared. const_ptrs: std::collections::HashSet, /// `macro_rules!` definitions we can expand, and the reasons for those we /// cannot, so a call site can say *why* rather than "unknown macro". mrules: HashMap, mrules_bad: HashMap, macro_depth: usize, /// Types declared by a `bitflags!` invocation. bitflags: std::collections::HashSet, /// `(type, flag) -> nim const name`. flag_consts: HashMap<(String, String), 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>, /// Operator traits implemented for a type, so `a += b` on a user type can /// be dispatched to the impl rather than to Nim's built-in operator. op_impls: HashMap<(String, String), ()>, /// `(type, method) -> nim name`, for calls written as `Type::method(..)`. statics: 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, /// How many items were actually translated. If this is zero the input /// produced nothing but the prelude, and reporting success for that is /// the precise failure this project exists to avoid -- see `findings/`. emitted: usize, /// 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(), self_ty: None, impl_generics: Vec::new(), fn_generics: Vec::new(), type_generics: HashMap::new(), assoc: HashMap::new(), assoc_consts: HashMap::new(), foreign: std::collections::HashSet::new(), const_ptrs: std::collections::HashSet::new(), mrules: HashMap::new(), mrules_bad: HashMap::new(), macro_depth: 0, bitflags: std::collections::HashSet::new(), flag_consts: HashMap::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(), op_impls: HashMap::new(), statics: HashMap::new(), fmt_param: None, vec_expect: None, forwards: Vec::new(), aliases: HashMap::new(), modules: Vec::new(), emitted: 0, 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)?; } } // An input that translates to nothing is a failure, however plausible // the output file looks. The prelude alone is not a translation. if self.emitted == 0 { return Err(format!( "nothing was translated: the input has no items this lowering \ emits{}. Writing a file containing only the prelude would \ report success for work that was not done", if self.dropped_by_cfg > 0 { format!( " ({} item(s) were dropped by `#[cfg]`; enable them with \ `--cfg feature=`)", self.dropped_by_cfg ) } else { String::new() } )); } 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 gen_names = Self::generics_of(&f.sig.generics); 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, generics: gen_names.clone() }); } Item::Struct(s) => { if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) { return Err(format!( "`struct {}` has a const generic parameter, which Nim has \ no equivalent for", s.ident )); } let g = Self::generics_of(&s.generics); // The parameters must be in scope while the field types are // mapped, so that `T` resolves to itself rather than to an // unknown named type. self.type_generics.insert(s.ident.to_string(), g); 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 }; // A field of `&[T]` / `&str` type is a borrow, and Nim's // view types allow it as an object field, so it stays a // view rather than being copied into a `seq`. let t = self.map_ty(&f.ty)?; let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() }; fields.push((name, t)); } self.structs.insert(s.ident.to_string(), fields); } Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => { self.collect_bitflags(&m.mac)?; } // A `macro_rules!` definition is recorded, not emitted: it is // expanded at each call site, where the types its body needs are // known. See `src/mrules.rs` for why that beats translating it // into a Nim template. Item::Macro(m) if path_name(&m.mac.path) == "macro_rules" => { if let Some(name) = &m.ident { match crate::mrules::parse(m.mac.tokens.clone()) { Ok(def) => { self.mrules.insert(name.to_string(), def); } Err(why) => { // Recorded as unusable rather than silently absent: // a call site gets this reason instead of "unknown // macro". self.mrules_bad.insert(name.to_string(), why); } } } } Item::ForeignMod(f) => { for it in &f.items { if let syn::ForeignItem::Fn(fi) = it { let (params, ret) = self.signature(&fi.sig)?; self.fns.insert( (self.cur_mod.clone(), fi.sig.ident.to_string()), Sig { params, ret, generics: Vec::new() }, ); } } } 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::Const(_))) { return Err(format!( "`enum {name}` has a const generic parameter, which Nim \ has no equivalent for" )); } self.type_generics .insert(name.clone(), Self::generics_of(&e.generics)); let mut variants = Vec::new(); for v in &e.variants { let vname = v.ident.to_string(); let discriminant = match &v.discriminant { Some((_, e)) => Some(self.expr(e)?.code), None => None, }; 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}"), }; let t = self.map_ty(&f.ty)?; let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() }; fields.push((fname, t)); } variants.push(Variant { name: vname, discriminant, 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 outer_g = std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics)); let self_ty = self.map_ty(&im.self_ty)?; let outer_self = self.self_ty.replace(self_ty.clone()); let r = self.collect_impl(im, &self_ty); self.self_ty = outer_self; self.impl_generics = outer_g; return r; } _ => {} } Ok(()) } fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> { { let self_ty = self_ty.clone(); let tyname = type_name(&self_ty); // Associated types first: a signature in the same block may name // one, and it has to resolve by the time that signature is mapped. for it in &im.items { if let syn::ImplItem::Type(t) = it { let v = self.map_ty(&t.ty)?; self.assoc.insert((tyname.clone(), t.ident.to_string()), v); } } 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(()); } // Any other trait: its methods are emitted as procs on // the type, named after the trait so two traits declaring // the same method name do not collide. The *trait* is not // modelled -- no dynamic dispatch, no bounds -- and a use // that needs it is rejected where it appears. if let Some(op) = operator_trait(&tr) { self.op_impls.insert((tyname.clone(), op.to_string()), ()); } for it in &im.items { // Already recorded above; a const is emitted with the // bodies. if matches!(it, syn::ImplItem::Type(_) | syn::ImplItem::Const(_)) { continue; } let syn::ImplItem::Fn(m) = it else { return Err(format!( "unsupported item in `impl {tr}`: only `fn`, \ `type` and `const` are implemented" )); }; let mname = m.sig.ident.to_string(); let (mut params, ret) = self.signature(&m.sig)?; let mut gen_names = self.impl_generics.clone(); gen_names.extend(Self::generics_of(&m.sig.generics)); let recv = if takes_self(&m.sig) { params.insert(0, self_ty.clone()); Some(self_ty.clone()) } else { None }; let nim = trait_method_name(&tyname, &tr, &mname); self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?); self.methods .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() }); self.statics.insert((tyname.clone(), mname), nim); } return Ok(()); } for it in &im.items { if let syn::ImplItem::Fn(m) = it { let (mut params, ret) = self.signature(&m.sig)?; let mut gen_names = self.impl_generics.clone(); gen_names.extend(Self::generics_of(&m.sig.generics)); 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 nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); let head = self.head_of(&nim, &m.sig, recv.as_ref())?; self.forwards.push(head); self.methods .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret, generics: gen_names.clone() }); self.statics .insert((tyname.clone(), m.sig.ident.to_string()), nim); } } } 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 { // Bare flags whose value is determined by the profile this project // models: a normal (non-`--test`) debug build, not a docs build. // Anything platform-specific stays rejected, since we would be // picking a target on the user's behalf. syn::Meta::Path(p) if p.is_ident("test") => Ok(false), syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true), syn::Meta::Path(p) if p.is_ident("docsrs") || p.is_ident("doc") => Ok(false), syn::Meta::Path(p) if p.is_ident("doctest") || p.is_ident("miri") => Ok(false), // Host facts. The generated Nim is compiled for this machine, so // these are known rather than chosen. See DESIGN.md item 10: it // does make the output host-shaped. syn::Meta::Path(p) if p.is_ident("unix") => Ok(cfg!(unix)), syn::Meta::Path(p) if p.is_ident("windows") => Ok(cfg!(windows)), syn::Meta::NameValue(nv) if nv.path.is_ident("target_os") || nv.path.is_ident("target_arch") || nv.path.is_ident("target_family") || nv.path.is_ident("target_vendor") => { let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { return Err("this `cfg` key expects a string".into()); }; let key = nv.path.get_ident().map(|i| i.to_string()).unwrap_or_default(); Ok(s.value() == match key.as_str() { "target_os" => std::env::consts::OS, "target_arch" => std::env::consts::ARCH, "target_family" => std::env::consts::FAMILY, _ => "unknown", }) } // The generated Nim is compiled for the same machine, so the // target's word size and endianness are known rather than // guessed. This does mean the output is host-shaped: a crate that // branches on pointer width has had that branch decided here. syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => { let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { return Err("`target_pointer_width = ..` expects a string".into()); }; Ok(s.value() == (usize::BITS).to_string()) } // Every integer width and pointer-sized atomic exists on the // targets Nim builds for here; like the other host facts this is // read off the machine rather than chosen. syn::Meta::NameValue(nv) if nv.path.is_ident("target_has_atomic") => { let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { return Err("`target_has_atomic = ..` expects a string".into()); }; Ok(matches!( s.value().as_str(), "8" | "16" | "32" | "64" | "ptr" )) } syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => { let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else { return Err("`target_endian = ..` expects a string".into()); }; Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" }) } 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. \ Features (`--cfg feature=..`), host facts (`unix`, `windows`, \ `target_os`, `target_arch`, `target_family`, \ `target_pointer_width`, `target_endian`), `doc`/`doctest`/\ `miri`, and `not`/`all`/`any` over those are. A custom or \ build-script `cfg` has no value we could know", quote_meta(other) )), } } /// Map a Rust type, resolving `Self` and expanding any `type` alias. 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 { // `Self::Item` names an associated type of the enclosing `impl`. if let syn::Type::Path(p) = t { let segs: Vec = p.path.segments.iter().map(|s| s.ident.to_string()).collect(); if segs.len() == 2 { let owner = if segs[0] == "Self" { self.self_ty.as_ref().map(type_name) } else { Some(segs[0].clone()) }; if let Some(o) = owner { if let Some(a) = self.assoc.get(&(o, segs[1].clone())) { return Ok(a.clone()); } } } } let n = ty::map(&self.expand(t, 0)?)?; Ok(self.subst_self(n)) } /// Substitute a generic type's parameters with the arguments the use site /// supplies: a field of `Holder` read through a `Holder` is `i32`. fn subst_type_args(&self, name: &str, used_as: &Nim, field: Nim) -> Nim { let Some(params) = self.type_generics.get(name) else { return field }; if params.is_empty() { return field; } let Nim::Named(n, args) = used_as else { return field }; if n != name || args.len() != params.len() { return field; } let map: HashMap = params.iter().cloned().zip(args.iter().cloned()).collect(); Self::subst(&field, &map) } /// `Self` inside an `impl` block names the type being implemented. fn subst_self(&self, t: Nim) -> Nim { let Some(me) = &self.self_ty else { return t }; match t { Nim::Named(n, _) if n == "Self" => me.clone(), Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))), Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))), Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))), Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))), Nim::Named(n, a) => { Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect()) } other => other, } } 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 type parameters a generic item declares. /// /// Trait bounds and `where` clauses are dropped. Nim instantiates a /// generic structurally: an operation the bound would have permitted /// either exists for the instantiated type or is a compile error at the /// instantiation site. So dropping a bound cannot make an accepted /// program mean something different — it only makes rustnim accept some /// programs rustc would have rejected, which does not matter when the /// input is known-good Rust. fn generics_of(g: &syn::Generics) -> Vec { g.params .iter() .filter_map(|p| match p { syn::GenericParam::Type(t) => Some(t.ident.to_string()), _ => None, }) .collect() } /// Bind a signature's type parameters by matching its declared parameter /// types against the actual argument types, then substitute into `ret`. /// /// This is the small amount of inference a call site needs: Nim will /// resolve the instantiation itself, but the *binding* still has to be /// annotated with a concrete type, and `T` is not one. fn instantiate(sig: &Sig, args: &[Option]) -> Nim { if sig.generics.is_empty() { return sig.ret.clone(); } let mut bound: HashMap = HashMap::new(); for (decl, actual) in sig.params.iter().zip(args) { if let Some(a) = actual { Self::unify(decl, a, &sig.generics, &mut bound); } } Self::subst(&sig.ret, &bound) } fn unify(decl: &Nim, actual: &Nim, params: &[String], out: &mut HashMap) { match (decl, actual) { (Nim::Named(n, da), _) if params.iter().any(|p| p == n) && da.is_empty() => { out.entry(n.clone()).or_insert_with(|| actual.clone()); } (Nim::Named(_, da), Nim::Named(_, aa)) if da.len() == aa.len() => { for (d, a) in da.iter().zip(aa) { Self::unify(d, a, params, out); } } (Nim::Seq(d), Nim::Seq(a)) | (Nim::OpenArray(d), Nim::OpenArray(a)) | (Nim::Seq(d), Nim::OpenArray(a)) | (Nim::OpenArray(d), Nim::Seq(a)) | (Nim::Var(d), Nim::Var(a)) | (Nim::Array(_, d), Nim::Array(_, a)) => Self::unify(d, a, params, out), (Nim::Var(d), a) => Self::unify(d, a, params, out), (d, Nim::Var(a)) => Self::unify(d, a, params, out), (Nim::Tuple(d), Nim::Tuple(a)) if d.len() == a.len() => { for (d, a) in d.iter().zip(a) { Self::unify(d, a, params, out); } } _ => {} } } fn subst(t: &Nim, m: &HashMap) -> Nim { match t { Nim::Named(n, a) if a.is_empty() => m.get(n).cloned().unwrap_or_else(|| t.clone()), Nim::Named(n, a) => { Nim::Named(n.clone(), a.iter().map(|x| Self::subst(x, m)).collect()) } Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))), Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))), Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))), Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))), Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()), other => other.clone(), } } /// Whether a type mentions a type parameter that is in scope here. Such a /// type cannot be used as a Nim annotation at an instantiation site: Nim /// infers it, and writing `T` would name something that is not bound. fn mentions_type_param(&self, t: &Nim) -> bool { match t { Nim::Named(n, a) => { self.fn_generics.iter().any(|g| g == n) || a.iter().any(|x| self.mentions_type_param(x)) } Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) => { self.mentions_type_param(e) } Nim::Tuple(ts) => ts.iter().any(|x| self.mentions_type_param(x)), Nim::Proc(a, r) => { a.iter().any(|x| self.mentions_type_param(x)) || self.mentions_type_param(r) } _ => false, } } /// The Nim name of a `log::Level` written as a path. fn log_level_of(&mut self, e: &Expr) -> Result { let Expr::Path(p) = e else { return Err("a log level must be written as `Level::Info`".into()); }; let last = path_name(&p.path); Ok(match last.as_str() { "Error" => "rsLvlError", "Warn" => "rsLvlWarn", "Info" => "rsLvlInfo", "Debug" => "rsLvlDebug", "Trace" => "rsLvlTrace", other => return Err(format!("`Level::{other}` is not a log level")), } .to_string()) } /// `[T, U]`, or empty. fn gen_list(params: &[String]) -> String { if params.is_empty() { String::new() } else { format!("[{}]", params.join(", ")) } } /// 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)?; // A method inside `impl Foo` is generic in the impl's // parameters as well as its own. let mut params = self.impl_generics.clone(); for g in Self::generics_of(&sig.generics) { if !params.contains(&g) { params.push(g); } } let gens = Self::gen_list(¶ms); 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), gens, parts.join(", ")) } else { format!( "proc {}*{}({}): {}", ident(name), gens, 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 // they disappear. Type parameters become Nim generic parameters. // Const parameters have no Nim equivalent and are still rejected. if sig.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) { return Err(format!( "`fn {}` has a const generic parameter, which Nim has no \ equivalent for", 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::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item), Item::ForeignMod(_) => 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(()); } if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") || matches!(item, Item::ForeignMod(_)) { return Ok(()); // emitted with the types } self.item_inner(item) } fn item_inner(&mut self, item: &Item) -> Result<(), String> { if !matches!(item, Item::Use(_) | Item::ExternCrate(_) | Item::Mod(_) | Item::Type(_)) { self.emitted += 1; } 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(); let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[])); self.line(&format!("type {}*{} = object", ident(&name), g)); 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::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.emit_bitflags(&m.mac), Item::Macro(m) if path_name(&m.mac.path) == "macro_rules" => Ok(()), Item::Type(_) => Ok(()), // expanded at every use site Item::Trait(t) => { // We do not model trait resolution, so a declaration generates // nothing and a use that needed it is rejected where it // appears. A *default body*, though, is code: dropping it // would silently remove a method the impls inherit. for it in &t.items { if let syn::TraitItem::Fn(f) = it { if f.default.is_some() { return Err(format!( "`trait {}` gives `{}` a default body; trait \ resolution is not modelled, so that body has no \ impl to be emitted into and dropping it would \ remove code", t.ident, f.sig.ident )); } } } Ok(()) } 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(); // The annotation types the initialiser, exactly as it does for // a `let`: `const MOD: u32 = 65521` is a u32 literal. let v = self.expr_at(&c.expr, Some(&t))?; self.bind(&c.ident.to_string(), t.clone()); // Only a top-level const is exported; `*` on a local is not // Nim syntax. let star = if self.indent == 0 { "*" } else { "" }; let line = format!( "const {}{}: {} = {}", ident(&c.ident.to_string()), star, t.render(), v.code ); self.line(&line); if self.indent == 0 { self.blank(); } Ok(()) } Item::Impl(im) => { let outer_g = std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics)); let self_ty = self.map_ty(&im.self_ty)?; let outer = self.self_ty.replace(self_ty.clone()); let r = self.impl_body(im, &self_ty); self.self_ty = outer; self.impl_generics = outer_g; r } // `use` and `extern crate` are resolution directives with no Nim // analogue once everything is one module. Item::Use(_) | Item::ExternCrate(_) => Ok(()), Item::ForeignMod(f) => self.foreign_mod(f), 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 impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> { if let Some((path, _)) = &im.trait_ { let tr = path_name(path); if im.items.is_empty() { return Ok(()); } if is_fmt_trait(&tr) { let syn::ImplItem::Fn(m) = &im.items[0] else { return Err(format!("unsupported item in `impl {tr}`")); }; return self.fmt_impl(&tr, self_ty, &m.sig, &m.block); } if tr == "From" { let syn::ImplItem::Fn(m) = &im.items[0] else { return Err("`impl From` must contain `fn from`".into()); }; 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); } let tyname = type_name(self_ty); for it in &im.items { if let syn::ImplItem::Const(c) = it { self.assoc_const(&tyname, c)?; continue; } if matches!(it, syn::ImplItem::Type(_)) { continue; // a type binding emits nothing } let syn::ImplItem::Fn(m) = it else { return Err(format!( "unsupported item in `impl {tr}`: only `fn`, `type` and \ `const` are implemented" )); }; let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string()); self.func_named(&nim, &m.sig, &m.block, recv)?; } return Ok(()); } let tyname = type_name(self_ty); for it in &im.items { match it { syn::ImplItem::Fn(m) => { let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); self.func_named(&nim, &m.sig, &m.block, recv)?; } syn::ImplItem::Type(_) => {} syn::ImplItem::Const(c) => self.assoc_const(&tyname, c)?, _ => { return Err("only `fn`, `type` and `const` items are supported \ inside `impl`" .into()) } } } Ok(()) } /// An `extern "C" { .. }` block: declarations of symbols someone else /// defines. Nim's `importc` is the same statement, and both are bound by /// the C ABI, so the two declarations describe one symbol rather than one /// being a translation of the other. fn foreign_mod(&mut self, f: &syn::ItemForeignMod) -> Result<(), String> { let abi = f .abi .name .as_ref() .map(|s| s.value()) .unwrap_or_else(|| "C".into()); if abi != "C" { return Err(format!( "`extern \"{abi}\"` is not the C ABI; only that one has a Nim \ equivalent" )); } // A const-qualified C pointer needs a type whose C spelling carries // the `const`; Nim has no such type built in, so one is declared per // element type actually used. let mut needed: Vec = Vec::new(); for it in &f.items { if let syn::ForeignItem::Fn(fi) = it { let (params, ret) = self.signature(&fi.sig)?; for t in params.iter().chain(std::iter::once(&ret)) { if let Nim::ConstPtr(inner) = t { if !matches!(&**inner, Nim::Prim(p) if p == "void") && !needed.contains(t) { needed.push(t.clone()); } } } } } for t in &needed { let Nim::ConstPtr(inner) = t else { continue }; let alias = ty::const_ptr_alias(inner); if !self.const_ptrs.insert(alias.clone()) { continue; } let c = ty::c_spelling(inner).ok_or_else(|| { format!( "`*const {}` has no C spelling we know, so a const-qualified \ declaration cannot be emitted for it", inner.render() ) })?; self.line(&format!( "type {}* {{.importc: \"const {} *\", nodecl.}} = distinct pointer", alias, c )); } for it in &f.items { match it { syn::ForeignItem::Fn(fi) => { if fi.sig.variadic.is_some() { return Err(format!( "`{}` is variadic; Nim needs `varargs` with a fixed \ calling shape, which this does not give us", fi.sig.ident )); } let name = fi.sig.ident.to_string(); let head = self.head_of(&name, &fi.sig, None)?; // `importc` names the C symbol, so the Nim name may differ // from it without changing what is linked. self.line(&format!( "{} {{.importc: \"{}\", cdecl.}}", head, name )); let (params, ret) = self.signature(&fi.sig)?; self.fns.insert( (self.cur_mod.clone(), name.clone()), Sig { params, ret, generics: Vec::new() }, ); self.foreign.insert(name); } syn::ForeignItem::Static(st) => { let t = self.map_ty(&st.ty)?; let name = st.ident.to_string(); self.line(&format!( "var {}* {{.importc: \"{}\".}}: {}", ident(&name), name, t.render() )); self.bind(&name, t); } syn::ForeignItem::Type(_) => { // An opaque C type: Nim spells it as a distinct object. continue; } _ => return Err("unsupported item in an `extern` block".into()), } } self.blank(); Ok(()) } /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant /// namespace, so it becomes a module-level const named for both. fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> { let t = self.map_ty(&c.ty)?.owned(); let v = self.expr_at(&c.expr, Some(&t))?; let name = format!("{}_{}", tyname, c.ident); self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code)); self.blank(); self.assoc_consts .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t)); Ok(()) } /// The type an operator impl declares for its right-hand operand. fn op_param(&self, t: &Option, op: &str) -> Option { let n = type_name(t.as_ref()?); let sig = self.methods.get(&(n, op_method(op).to_string()))?; sig.params.get(1).cloned().map(|t| t.unvar()) } /// The proc implementing `op` for a user type, if there is one. fn op_proc(&self, t: &Option, op: &str) -> Option { let n = type_name(t.as_ref()?); let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0; if self.op_impls.contains_key(&(n.clone(), op.to_string())) { Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1)) } else { None } } /// Register a `bitflags!` type's operations so call sites resolve. fn collect_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> { let input: crate::macros::BitflagsInput = mac .parse_body() .map_err(|e| format!("`bitflags!`: {e}"))?; for def in &input.0 { let name = def.name.to_string(); let repr = self.map_ty(&def.repr)?; if !repr.is_integer() { return Err(format!("`bitflags! {name}` needs an integer representation")); } let me = Nim::Named(name.clone(), vec![]); let b = Nim::Prim("bool".into()); self.structs .insert(name.clone(), vec![("bitsField".into(), repr.clone())]); self.type_generics.insert(name.clone(), Vec::new()); let mut m = |n: &str, params: Vec, ret: Nim, nim: String| { self.methods.insert( (name.clone(), n.to_string()), Sig { params, ret, generics: Vec::new() }, ); self.statics.insert((name.clone(), n.to_string()), nim); }; let s1 = vec![me.clone()]; let s2 = vec![me.clone(), me.clone()]; let vs2 = vec![Nim::Var(Box::new(me.clone())), me.clone()]; m("bits", s1.clone(), repr.clone(), format!("{name}_bits")); m("is_empty", s1.clone(), b.clone(), format!("{name}_is_empty")); m("is_all", s1.clone(), b.clone(), format!("{name}_is_all")); m("contains", s2.clone(), b.clone(), format!("{name}_contains")); m("intersects", s2.clone(), b.clone(), format!("{name}_intersects")); for (rust, nim) in [ ("union", "union"), ("intersection", "intersection"), ("difference", "difference"), ("symmetric_difference", "symmetric_difference"), ] { m(rust, s2.clone(), me.clone(), format!("{name}_{nim}")); } for n in ["insert", "remove", "toggle"] { m(n, vs2.clone(), Nim::Unit, format!("{name}_{n}")); } m( "set", vec![Nim::Var(Box::new(me.clone())), me.clone(), b.clone()], Nim::Unit, format!("{name}_set"), ); m("empty", vec![], me.clone(), format!("{name}_empty")); m("all", vec![], me.clone(), format!("{name}_all")); m( "from_bits", vec![repr.clone()], Nim::Named("Option".into(), vec![me.clone()]), format!("{name}_from_bits"), ); m( "from_bits_truncate", vec![repr.clone()], me.clone(), format!("{name}_from_bits_truncate"), ); m("complement", s1.clone(), me.clone(), format!("{name}_complement")); // The operator forms, routed through the same dispatch that a // hand-written `impl BitOr` would use. for (op, trait_name, method) in [ ("|", "BitOr", "bitor"), ("&", "BitAnd", "bitand"), ("^", "BitXor", "bitxor"), ("-", "Sub", "sub"), ("not", "Not", "not"), ] { self.op_impls.insert((name.clone(), op.to_string()), ()); let params = if op == "not" { s1.clone() } else { s2.clone() }; self.methods.insert( (name.clone(), method.to_string()), Sig { params, ret: me.clone(), generics: Vec::new() }, ); let _ = trait_name; } self.bitflags.insert(name); } Ok(()) } /// Emit the Nim for a `bitflags!` type. See `src/macros.rs` for why this /// is lowered directly rather than by expanding the macro. fn emit_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> { let input: crate::macros::BitflagsInput = mac .parse_body() .map_err(|e| format!("`bitflags!`: {e}"))?; for def in &input.0 { let name = def.name.to_string(); let repr = self.map_ty(&def.repr)?; let r = repr.render(); self.line(&format!("type {name}* = object")); self.line(&format!(" bitsField*: {r}")); self.blank(); self.line(&format!("proc {name}_bits*(x: {name}): {r} = x.bitsField")); // The constants. A flag's value may name earlier flags, as // `const ALL = Self::READ.bits() | ..` does, so they are emitted // in order and each is in scope for the next. self.push_scope(); self.bind_static_type(&name, &repr); for (fname, value) in &def.flags { let v = self.expr_at(value, Some(&repr))?; self.line(&format!( "const {}{}* = {}(bitsField: {})", name, fname, name, v.code )); self.flag_consts .insert((name.clone(), fname.to_string()), format!("{name}{fname}")); } self.pop_scope(); let all: Vec = def .flags .iter() .map(|(f, _)| format!("{name}{f}.bitsField")) .collect(); let all_bits = if all.is_empty() { format!("{}(0)", r) } else { all.join(" or ") }; self.blank(); self.line(&format!("const {name}AllBits: {r} = {all_bits}")); self.blank(); for l in [ format!("proc {name}_empty*(): {name} = {name}(bitsField: {r}(0))"), format!("proc {name}_all*(): {name} = {name}(bitsField: {name}AllBits)"), format!("proc {name}_is_empty*(x: {name}): bool = x.bitsField == {r}(0)"), format!("proc {name}_is_all*(x: {name}): bool = (x.bitsField and {name}AllBits) == {name}AllBits"), format!("proc {name}_contains*(a, b: {name}): bool = (a.bitsField and b.bitsField) == b.bitsField"), format!("proc {name}_intersects*(a, b: {name}): bool = (a.bitsField and b.bitsField) != {r}(0)"), format!("proc {name}_union*(a, b: {name}): {name} = {name}(bitsField: a.bitsField or b.bitsField)"), format!("proc {name}_intersection*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and b.bitsField)"), format!("proc {name}_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and (not b.bitsField))"), format!("proc {name}_symmetric_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField xor b.bitsField)"), // `!x` complements and then masks to `all()`, which is what // bitflags does and not what a plain `not` would give. format!("proc {name}_complement*(x: {name}): {name} = {name}(bitsField: (not x.bitsField) and {name}AllBits)"), format!("proc {name}_from_bits_truncate*(b: {r}): {name} = {name}(bitsField: b and {name}AllBits)"), format!("proc {name}_from_bits*(b: {r}): Option[{name}] ="), format!(" if (b and (not {name}AllBits)) != {r}(0): rsNone[{name}]() else: rsSome({name}(bitsField: b))"), format!("proc {name}_insert*(x: var {name}, o: {name}) = x.bitsField = x.bitsField or o.bitsField"), format!("proc {name}_remove*(x: var {name}, o: {name}) = x.bitsField = x.bitsField and (not o.bitsField)"), format!("proc {name}_toggle*(x: var {name}, o: {name}) = x.bitsField = x.bitsField xor o.bitsField"), format!("proc {name}_set*(x: var {name}, o: {name}, on: bool) ="), format!(" if on: {name}_insert(x, o) else: {name}_remove(x, o)"), format!("proc rsBitOr_{name}_bitor*(a, b: {name}): {name} = {name}_union(a, b)"), format!("proc rsBitAnd_{name}_bitand*(a, b: {name}): {name} = {name}_intersection(a, b)"), format!("proc rsBitXor_{name}_bitxor*(a, b: {name}): {name} = {name}_symmetric_difference(a, b)"), format!("proc rsSub_{name}_sub*(a, b: {name}): {name} = {name}_difference(a, b)"), format!("proc rsNot_{name}_not*(a: {name}): {name} = {name}_complement(a)"), ] { self.line(&l); } // Debug prints the set flag names, or `0x0` when empty -- again // matching the crate rather than a guess. self.line(&format!("proc rsDebug*(x: {name}): string =")); self.line(&format!(" result = \"{name}(\"")); self.line(" var first = true"); for (fname, _) in &def.flags { self.line(&format!( " if (x.bitsField and {name}{f}.bitsField) == {name}{f}.bitsField and {name}{f}.bitsField != {r}(0):", f = fname )); self.line(" if not first: result.add(\" | \")"); self.line(&format!(" result.add(\"{fname}\")")); self.line(" first = false"); } self.line(" if first: result.add(\"0x0\")"); self.line(" result.add(\")\")"); self.blank(); } Ok(()) } fn bind_static_type(&mut self, _name: &str, _repr: &Nim) {} fn emit_enum(&mut self, def: &EnumDef) { let name = ident(&def.name); let g = Self::gen_list( self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]), ); if def.simple && !g.is_empty() { // A Nim `enum` cannot take parameters; an all-unit generic enum // has no payload to be generic in anyway, so this would be a // parameter that never appears. // Fall through to the object-variant form instead. } if def.simple && g.is_empty() { // 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 { match &v.discriminant { Some(d) => self.line(&format!("{} = {}", ident(&v.name), d)), None => self.line(&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, g)); 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); let ret = format!("{}{}", name, g); self.line(&format!( "proc {}*{}({}): {} = {}({})", def.ctor_ident(&v.name), g, args.join(", "), ret, ret, all.join(", ") )); } self.blank(); self.line(&format!("proc rsDebug*{}(x: {}{}): string =", g, name, g)); 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(); } /// The concrete type an enum variant constructs, and the `[T]` list to /// spell at the constructor when the enum is generic. fn variant_type( &self, def: &EnumDef, expect: Option<&Nim>, ) -> Result<(Nim, String), String> { let params = self.type_generics.get(&def.name).cloned().unwrap_or_default(); if params.is_empty() { return Ok((Nim::Named(def.name.clone(), vec![]), String::new())); } match expect { Some(Nim::Named(n, a)) if *n == def.name && a.len() == params.len() => Ok(( Nim::Named(def.name.clone(), a.clone()), format!("[{}]", a.iter().map(|t| t.render()).collect::>().join(", ")), )), _ => Err(format!( "`{}` is a variant of a generic enum, and its type parameters \ cannot be inferred here; annotate the binding or the return type", def.name )), } } /// 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())); // No assignment target: a formatter write *appends*, because a `fmt` // body may write repeatedly -- `UpperHex` writes once per byte in a // loop -- and assigning would keep only the last one. let outer_target = self.target.take(); self.line(&format!( "proc {}*(self: {}): string =", proc_name, self_ty.render() )); self.indent += 1; let before = self.out.len(); let tail = self.block_body(body)?; 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 mut gparams = self.impl_generics.clone(); for g in Self::generics_of(&sig.generics) { if !gparams.contains(&g) { gparams.push(g); } } let gens = Self::gen_list(&gparams); let outer_fg = std::mem::replace(&mut self.fn_generics, gparams.clone()); let head = if ret == Nim::Unit { format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", ")) } else { format!( "proc {}*{}({}): {} =", ident(name), gens, 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.fn_generics = outer_fg; 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(()) } // A `const` declared inside a function body is local to it, and // must be emitted here rather than skipped as an already-emitted // top-level type. Stmt::Item(i) => self.item_inner(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), Pat::Tuple(t) => return self.local_tuple(l, t), _ => 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 it = xs.chunks_exact(k)` binds an iterator, not a value. if is_iterator_expr(&init.expr) { let it = self.resolve_iter(&init.expr)?; self.bind_alias(&name, Alias::Iterator(Box::new(it))); return Ok(()); } let v = self.expr_at(&init.expr, ann.as_ref())?; // `let s = &buf[..n]` binds a view of a place that is already in // scope. Nim's borrow checker will not let a `let` borrow out of a // local, and there is nothing to materialise anyway -- a view is a // reference. Binding it as an alias substitutes the same expression at // each use, which re-evaluates nothing because the initialiser is a // place expression with no side effects. if v.window.is_none() && matches!(v.ty, Some(Nim::OpenArray(_))) && is_pure_place(&init.expr) { let t = v.ty.clone().unwrap(); let elem = match &t { Nim::OpenArray(e) => Some((**e).clone()), _ => None, }; self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) }); let _ = elem; return Ok(()); } 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(()); } // A `let` binding a borrow keeps the view: `let res = encode(..)?` // names the caller's buffer, and copying it into a `seq` would still // print the right bytes while silently breaking the aliasing. let t = match (ann, &v.ty) { (Some(a), _) => a.unvar(), (None, Some(t)) => t.clone().unvar(), (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 x = &mut y[..n]` is an immutable binding of a *mutable* borrow: // Rust may write through it, and Nim only accepts a `var` where a // `var` parameter is wanted, so the binding has to be one. let mutable = mutable || is_mut_borrow(&init.expr); let kw = if mutable { "var" } else { "let" }; // Inside a generic proc the binding's type may mention a parameter Nim // will infer; naming it in an annotation would not resolve. let line = if self.mentions_type_param(&t) { format!("{} {} = {}", kw, ident(&name), v.code) } else { format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code) }; self.line(&line); self.bind(&name, t); Ok(()) } /// `let (a, b) = ..` — tuple destructuring. fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> { let Some(init) = &l.init else { return Err("a destructuring `let` needs an initialiser".into()); }; let names: Vec<(String, bool)> = t .elems .iter() .map(|p| match p { Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())), Pat::Wild(_) => Ok(("_".to_string(), false)), _ => Err("only plain identifiers are supported in a destructuring `let`"), }) .collect::>()?; // `split_at` hands back two *views* of the same slice. Nim has no // tuple of views, and there is nothing to materialise anyway, so each // name becomes a window into the original. if let Expr::MethodCall(m) = &*init.expr { let mname = m.method.to_string(); if (mname == "split_at" || mname == "split_at_mut") && m.args.len() == 1 && names.len() == 2 { let (code, base, len, elem) = self.slice_parts(&m.receiver)?; let at = self.expr(&m.args[0])?; let cut = self.fresh("Cut"); self.line(&format!("let {}: int = int({})", cut, at.code)); self.bind_alias( &names[0].0, Alias::Window { code: code.clone(), off: base.clone(), len: cut.clone(), elem: elem.clone(), }, ); self.bind_alias( &names[1].0, Alias::Window { code, off: format!("({} + {})", base, cut), len: format!("({} - {})", len, cut), elem, }, ); return Ok(()); } } let v = self.expr(&init.expr)?; let tys = match &v.ty { Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(), _ => { return Err(format!( "cannot destructure this into {} bindings: its type is not a \ tuple of that many elements", names.len() )) } }; let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" }; let lhs: Vec = names.iter().map(|(n, _)| ident(n)).collect(); self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code)); for ((n, _), t) in names.iter().zip(tys) { self.bind(n, 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)?; // A compound assignment on a user type goes to that type's own // `impl OpAssign`, not to Nim's built-in operator. if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) { // The impl's own parameter type types the right operand, // so `b_vec *= 4` takes 4 at the width the impl declares. let want = self.op_param(&lhs.ty, compound_symbol(&b.op)); let rhs = self.expr_at(&b.right, want.as_ref())?; self.line(&format!("{}({}, {})", f, lhs.code, rhs.code)); return Ok(None); } // `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" )), } } Expr::Path(p) => { let n = path_name(&p.path); if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) { return Ok((*it).clone()); } if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) { return Ok(Iter::Elems { code, off, len, elem, mutable: false }); } let v = self.expr(e)?; Ok(Iter::Elems { len: format!("{}.len", v.code), elem: elem_of(&v.ty), code: v.code, off: "0".into(), mutable: false, }) } 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")); }; let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default(); // The variant's payload is declared in the enum's own parameters; the // scrutinee says what they are here. Ok(fields .into_iter() .map(|(n, ft)| (n, self.subst_type_args(&def.name, t, ft))) .collect()) } 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())); } // `log::Level` and `log::LevelFilter` come from the facade // shim, under names no crate can collide with. if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) { if (q == "Level" || q == "LevelFilter") && !self.enums.contains_key(&q) { let pre = if q == "Level" { "rsLvl" } else { "rsFlt" }; let t = if q == "Level" { "RsLogLevel" } else { "RsLogFilter" }; if q == "LevelFilter" && name == "Off" { return Ok(Val::new("rsFltOff", Some(Nim::Prim(t.into())))); } if matches!(name.as_str(), "Error" | "Warn" | "Info" | "Debug" | "Trace") { return Ok(Val::new( format!("{pre}{name}"), Some(Nim::Prim(t.into())), )); } } } // `Perms::READ`: a constant of a `bitflags!` type. if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) { let q = if q == "Self" { self.self_ty.as_ref().map(type_name).unwrap_or(q) } else { q }; if let Some(c) = self.flag_consts.get(&(q.clone(), name.clone())) { return Ok(Val::new(c.clone(), Some(Nim::Named(q, vec![])))); } } // `Grid::BORDER`: a `const` declared inside an `impl`. if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) { let q = if q == "Self" { self.self_ty.as_ref().map(type_name).unwrap_or(q) } else { q }; if let Some((nim, t)) = self.assoc_consts.get(&(q, name.clone())) { return Ok(Val::new(nim.clone(), Some(t.clone()))); } } // `i32::MAX` and friends: an associated const on a primitive. if matches!(name.as_str(), "MAX" | "MIN") { if let Some(q) = p.path.segments.iter().rev().nth(1) { if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) { if t.is_integer() { let f = if name == "MAX" { "high" } else { "low" }; return Ok(Val::new( format!("{}({})", f, t.render()), Some(t), )); } } } } // 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, targs) = self.variant_type(&def, expect)?; return Ok(if def.simple && targs.is_empty() { Val::new(format!("{}.{}", ident(&def.name), ident(&v)), Some(ty)) } else { // A unit variant of a generic enum has no argument to // infer the parameters from, so they are written out. Val::new( format!("{}{}()", def.ctor_ident(&v), targs), Some(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))), ), // An iterator is not a value here: it is consumed by a // `for`, or asked for its `.remainder()`. Alias::Iterator(_) => { return Err(format!( "`{name}` is an iterator; it can be iterated or asked \ for its `remainder()`, but not used as a value" )) } }); } 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(bt @ Nim::Named(s, _)) => self .structs .get(s) .and_then(|fs| fs.iter().find(|(f, _)| *f == name)) .map(|(_, t)| self.subst_type_args(s, bt, 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 is_write = matches!(path_name(&m.mac.path).as_str(), "write" | "writeln"); let code = self.macro_call(&m.mac)?; // A formatter write is a statement that appends, not a value. let ty = if is_write { Some(Nim::Unit) } else { None }; Ok(Val::new(code, ty)) } 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![])), )); } // `Self { .. }` inside an `impl` names the type being // implemented, and its fields are that type's fields. let name = match path_name(&s.path).as_str() { "Self" => self .self_ty .as_ref() .map(type_name) .ok_or("`Self` outside an `impl` block")?, other => other.to_string(), }; 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)); } // Nim cannot infer an object's generic parameters from a // constructor's field values, so they are written out. let gp = self.type_generics.get(&name).cloned().unwrap_or_default(); let ty = if gp.is_empty() { Nim::Named(name.clone(), vec![]) } else { match expect { Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => { Nim::Named(name.clone(), a.clone()) } _ => { return Err(format!( "`{name} {{ .. }}` is generic, and Nim cannot infer \ its parameters from the field values; annotate the \ binding or the return type" )) } } }; Ok(Val::new( format!("{}({})", ty.render(), parts.join(", ")), Some(ty), )) } 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) => { // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size // array from a `seq`, so the expected type decides which, and // an array needs its elements written out. let want_elem = match expect { Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => { Some((**e).clone()) } _ => None, }; let v = self.expr_at(&r.expr, want_elem.as_ref())?; if let Some(Nim::Array(n, _)) = expect { let elems: Vec = (0..*n).map(|_| v.code.clone()).collect(); let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t))); return Ok(Val::new(format!("[{}]", elems.join(", ")), t)); } 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), // A block with statements used as a value: the statements are // emitted ahead of the line being built and the block's trailing // expression becomes the value. Every caller lowers its // sub-expressions before emitting its own line, which is what // makes that ordering hold. Expr::Block(b) if b.label.is_none() && b.block.stmts.len() > 1 && matches!(b.block.stmts.last(), Some(Stmt::Expr(_, None))) => { if self.in_loop_cond { return Err("a block expression in a loop condition is not \ implemented yet: its statements would run once, \ before the loop" .into()); } // Nim's `block:` is an expression too, so the statements get // their own scope rather than being hoisted into the enclosing // one -- which would collide if the same block is written // twice, as a macro expanded at two call sites is. let tmp = self.fresh("Blk"); self.line(&format!("let {} = block:", tmp)); self.indent += 1; self.push_scope(); let saved = self.target.take(); let before = self.out.len(); let tail = self.block_body_at(&b.block, expect)?; let ty = tail.as_ref().and_then(|v| v.ty.clone()); match tail { Some(v) => { let code = v.code.clone(); self.line(&code); } None => { return Err( "a block used as a value needs a trailing expression".into() ) } } let _ = before; self.target = saved; self.pop_scope(); self.indent -= 1; Ok(Val::new(tmp, ty)) } 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(_) => { if let Some(f) = self.op_proc(&v.ty, "not") { return Ok(Val::new(format!("{}({})", f, v.code), v.ty)); } 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("")); // A binary operator on a user type goes to that type's own impl. if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) { let want = self.op_param(&l.ty, binary_symbol(&b.op)); let r = self.expr_at(&b.right, want.as_ref())?; let ret = self .methods .get(&( type_name(l.ty.as_ref().unwrap()), op_method(binary_symbol(&b.op)).to_string(), )) .map(|s| s.ret.clone()); return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret)); } 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. `cast` says exactly // that. (Nim's `T(x)` turns out to truncate here as well -- // see DESIGN.md item 5 -- but `cast` is the spelling that // means it rather than the one that happens to agree.) format!("cast[{}]({})", t.render(), v.code) } (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => { format!("{}({})", p, v.code) } // The facade's level enums carry their Rust discriminants, so // `Level::Info as usize` is the ordinal. (Nim::Prim(p), t) if t.is_integer() && (p == "RsLogLevel" || p == "RsLogFilter") => { format!("{}(ord({}))", t.render(), v.code) } // A C-like enum's `as` yields its discriminant, which is its // ordinal in Nim. (Nim::Named(n, _), t) if t.is_integer() && self.enums.get(n).is_some_and(|d| d.simple) => { format!("{}(ord({}))", t.render(), 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) } // Pointer-to-pointer, and integer-to-pointer, are reinterpretations // in both languages. (Nim::Ptr(_) | Nim::ConstPtr(_), Nim::Ptr(_) | Nim::ConstPtr(_)) => { format!("cast[{}]({})", to.render(), v.code) } (f, Nim::Ptr(_) | Nim::ConstPtr(_)) if f.is_integer() => { format!("cast[{}]({})", to.render(), v.code) } (Nim::Ptr(_) | Nim::ConstPtr(_), t) if t.is_integer() => { format!("cast[{}]({})", t.render(), 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 self.fmt_param.is_some() { // Writing into a string cannot fail, so `?` on a formatter write // is a no-op. `?` on anything else can fail, and `format!` panics // when a formatting impl returns an error -- so that is what the // error branch does here, with std's own message. if v.ty.as_ref() == Some(&Nim::Unit) { return Ok(v); } if let Some(Nim::Named(n, a)) = v.ty.clone() { if n == "Result" && a.len() == 2 { let tmp = self.fresh("Fmt"); self.line(&format!( "let {}: {} = {}", tmp, Nim::Named(n, a.clone()).render(), v.code )); self.line(&format!("if not {}.ok:", tmp)); self.line( " rsPanic(\"a formatting trait implementation returned an error\")", ); return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone()))); } } } 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" if self.fmt_param.is_some() && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) => { // `Ok(())` ends a `fmt` body: nothing more is written. return Ok(Val::new(String::new(), Some(Nim::Unit))); } "Ok" | "Err" => { let (t, e) = match expect { Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => { (a[0].render(), a[1].render()) } _ => { return Err(format!( "`{name}(..)` needs a known `Result` type here; \ annotate the binding or the return type" )) } }; let ctor = if name == "Ok" { "rsOk" } else { "rsErr" }; let arg = if codes.is_empty() { String::new() } else { codes.join(", ") }; return Ok(Val::new( format!("{}[{}, {}]({})", ctor, t, e, arg), expect.cloned(), )); } _ => {} } // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's // object constructor names its fields even when Rust's does not. if let Some(fields) = self.structs.get(&name).cloned() { if fields.len() == c.args.len() { let mut parts = Vec::new(); for (i, a) in c.args.iter().enumerate() { let v = self.expr_at(a, Some(&fields[i].1))?; parts.push(format!("{}: {}", ident(&fields[i].0), v.code)); } return Ok(Val::new( format!("{}({})", ident(&name), parts.join(", ")), Some(Nim::Named(name.clone(), vec![])), )); } } // `log::set_max_level` / `log::max_level`. if name == "set_max_level" && codes.len() == 1 { return Ok(Val::new( format!("rsLogMaxLevel = int({})", codes[0]), Some(Nim::Unit), )); } if name == "max_level" && codes.is_empty() { return Ok(Val::new( "RsLogFilter(rsLogMaxLevel)", Some(Nim::Prim("RsLogFilter".into())), )); } // `Spacing::from(d)`: a `From` impl called through its target type. // Rust picks the impl by the argument's type, and so do we -- Nim // cannot overload on return type, so each impl has its own proc name. if name == "from" && codes.len() == 1 { if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) { let q = if q == "Self" { self.self_ty.as_ref().map(type_name).unwrap_or(q) } else { q }; if let Some(src) = args[0].ty.as_ref().map(type_name) { if let Some(f) = self.from_impls.get(&(src, q.clone())).cloned() { return Ok(Val::new( format!("{}({})", f, codes[0]), Some(Nim::Named(q, vec![])), )); } } } } // `u32::from(b)`: `From` between primitives is lossless by definition // -- it is the widening direction only -- so a plain Nim conversion is // exact. (The truncating direction is `as`, which is `cast`.) if name == "from" && codes.len() == 1 { if let Some(q) = p.path.segments.iter().rev().nth(1) { if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) { return Ok(Val::new( format!("{}({})", t, codes[0]), Some(Nim::Prim(t)), )); } } } // `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 { // `String::from_utf8_unchecked(v)` takes ownership and yields an // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields // a view. Same name, different operations -- the qualifier says // which, and an unqualified call is ambiguous. let q = p .path .segments .iter() .rev() .nth(1) .map(|s| s.ident.to_string()); return match q.as_deref() { Some("String") => Ok(Val::new( format!("rsStringOf({})", codes[0]), Some(Nim::Prim("string".into())), )), Some("str") => Ok(Val::new( format!("rsStrView({})", codes[0]), Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))), )), _ => Err( "`from_utf8_unchecked` must be written as `str::..` (a \ borrowed view) or `String::..` (an owned string); the two \ are different operations" .into(), ), }; } // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. if let Some((def, v)) = self.resolve_variant(&p.path) { let (ty, _) = self.variant_type(&def, expect)?; return Ok(Val::new( format!("{}({})", def.ctor_ident(&v), codes.join(", ")), Some(ty), )); } // 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()), )); } // `Adler32::new()` / `Adler32::default()`: a method called through // its type rather than through a receiver. if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) { // `Self::new()` inside an `impl` names the type being implemented. let q = if q == "Self" { self.self_ty.as_ref().map(type_name).unwrap_or(q) } else { q }; if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) { // Re-lower the arguments with the declared parameter types, so // a literal takes the width the signature asks for. let declared = sig.params.clone(); let mut args = args.clone(); let mut codes = codes.clone(); for (i, a) in c.args.iter().enumerate() { if let Some(want) = declared.get(i) { let want = want.clone().unvar(); args[i] = self.expr_at(a, Some(&want))?; codes[i] = args[i].code.clone(); } } let sig = &self.methods[&(q.clone(), name.clone())]; let arg_tys: Vec> = args.iter().map(|a| a.ty.clone()).collect(); let ret = Self::instantiate(sig, &arg_tys); let nim = self .statics .get(&(q.clone(), name.clone())) .cloned() .unwrap_or_else(|| ident(&name)); return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret))); } } let arg_tys: Vec> = args.iter().map(|a| a.ty.clone()).collect(); let ret = target .as_ref() .and_then(|k| self.fns.get(k)) .map(|sig| Self::instantiate(sig, &arg_tys)); 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(); // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield. if name == "remainder" && m.args.is_empty() { if let Expr::Path(p) = &*m.receiver { if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) { if let Iter::Chunks { code, base, len, k, elem, .. } = &*it { let kept = format!("(({} div int({})) * int({}))", len, k, k); let mut v = Val::new( String::new(), elem.clone().map(|e| Nim::OpenArray(Box::new(e))), ); v.window = Some(Alias::Window { code: code.clone(), off: format!("({} + {})", base, kept), len: format!("({} - {})", len, kept), elem: elem.clone(), }); return Ok(v); } return Err( "`.remainder()` is only defined for a `chunks_exact` iterator".into(), ); } } return Err("`.remainder()` needs an iterator bound by `let`".into()); } 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(); // A method the input defines wins over our model of the standard // library: `is_empty` on a `bitflags!` type is that type's, not the // sequence one. Rust resolves inherent methods the same way. if let Some(t) = &rt { let key = (type_name(t), name.clone()); if self.methods.contains_key(&key) { let declared = self.methods[&key].params.clone(); let skip = usize::from(declared.len() == m.args.len() + 1); for (i, a) in m.args.iter().enumerate() { if let Some(want) = declared.get(i + skip) { let want = want.clone().unvar(); args[i] = self.expr_at(a, Some(&want))?; } } let mut arg_tys: Vec> = vec![rt.clone()]; arg_tys.extend(args.iter().map(|a| a.ty.clone())); let ret = Self::instantiate(&self.methods[&key], &arg_tys); let nim = self .statics .get(&key) .cloned() .unwrap_or_else(|| ident(&name)); let mut all = vec![recv.code.clone()]; all.extend(args.iter().map(|a| a.code.clone())); return Ok(Val::new(format!("{}({})", nim, all.join(", ")), Some(ret))); } } 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" => { // Expanded inline rather than called as a generic proc: when // the payload is a view, Nim can only borrow from a path // expression, which a proc body containing the panic is not. let (kind, inner) = match &rt { Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => { ("Option", a[0].clone()) } Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => { ("Result", a[0].clone()) } _ => { return Err(format!( "`.{name}()` needs a known `Option`/`Result` receiver type" )) } }; if self.in_loop_cond { return Err(format!( "`.{name}()` in a loop condition is not implemented yet: the \ check it expands to would run once, before the loop" )); } let tmp = self.fresh("Unwrap"); let rty = rt.clone().unwrap(); self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code)); let (test, msg) = if kind == "Option" { (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value") } else { (format!("{}.ok", tmp), "called `Result::unwrap()` on an `Err` value") }; let msg = if name == "expect" { args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg)) } else { fmt::nim_str(msg) }; self.line(&format!("if not {}:", test)); self.line(&format!(" rsPanic({})", msg)); // If the payload is a view, hand back an alias rather than a // value: Nim will not let a `let` borrow out of a local, and a // view is a reference anyway, so there is nothing to bind. // `{tmp}.val` is a plain field access, so substituting it at // each use re-evaluates nothing. if matches!(inner, Nim::OpenArray(_)) { let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone())); v.window = Some(Alias::Value { code: format!("{}.val", tmp), ty: Some(inner), }); return Ok(v); } (format!("{}.val", tmp), Some(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) => { let a = args.first().ok_or("`write_str` takes one argument")?; // A `&str` argument is a character view, not a Nim string. let text = match &a.ty { Some(Nim::Prim(p)) if p == "string" => a.code.clone(), _ => format!("rsDisplay({})", a.code), }; (format!("result.add({})", text), Some(Nim::Unit)) } "saturating_add" | "saturating_sub" | "saturating_mul" | "checked_add" | "checked_sub" | "checked_mul" => { let t = rt .clone() .filter(|t| t.is_integer()) .ok_or_else(|| format!("`{name}` needs a known integer receiver"))?; let arg = args .first() .ok_or_else(|| format!("`{name}` takes one argument"))?; let f = match name.as_str() { "saturating_add" => "rsSatAdd", "saturating_sub" => "rsSatSub", "saturating_mul" => "rsSatMul", "checked_add" => "rsChkAdd", "checked_sub" => "rsChkSub", _ => "rsChkMul", }; let out = if name.starts_with("checked") { Nim::Named("Option".into(), vec![t]) } else { t }; (format!("{}({}, {})", f, recv.code, arg.code), Some(out)) } // `as_ptr` hands a C function the address of the first element, // which is what Rust's does. An empty slice has no first element // in either language, and reading through the pointer would be // undefined in both. "as_ptr" | "as_mut_ptr" => { let elem = elem_of(&rt) .ok_or("`as_ptr` needs a known element type")?; ( format!( "(if {r}.len == 0: nil else: cast[ptr {e}](addr {r}[0]))", r = recv.code, e = elem.render() ), Some(Nim::Ptr(Box::new(elem))), ) } "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())); // Re-lower the arguments with the declared parameter types: // a method's own signature says what width its literals are, // which the receiver's type does not. let declared: Option> = key .as_ref() .and_then(|k| self.methods.get(k)) .map(|s| s.params.clone()); if let Some(d) = &declared { // params[0] is the receiver for a method with `self`. let skip = usize::from(d.len() == m.args.len() + 1); for (i, a) in m.args.iter().enumerate() { if let Some(want) = d.get(i + skip) { let want = want.clone().unvar(); args[i] = self.expr_at(a, Some(&want))?; } } } let mut arg_tys: Vec> = vec![recv.ty.clone()]; arg_tys.extend(args.iter().map(|a| a.ty.clone())); let sig = key .as_ref() .and_then(|k| self.methods.get(k)) .map(|s| Self::instantiate(s, &arg_tys)); if let Some(ret) = sig { // Use the name the proc was actually emitted under: an // inherent method is qualified by its module, a trait // method by its trait. let nim = key .and_then(|k| self.statics.get(&k).cloned()) .unwrap_or_else(|| ident(&name)); let mut all = vec![recv.code.clone()]; all.extend(args.iter().map(|a| a.code.clone())); (format!("{}({})", nim, 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..])?; let s = if name == "writeln" { format!("({} & \"\\n\")", s) } else { s }; Ok(format!("result.add({})", s)) } "panic" => { let s = self.format_args(mac)?; Ok(format!("rsPanic({s})")) } // `debug_assert*` fires in debug builds, which is the profile // this project models, so it lowers the same as `assert*`. "assert" | "debug_assert" => { let args: Vec = mac .parse_body_with( syn::punctuated::Punctuated::::parse_terminated, ) .map_err(|e| format!("{name}!: {e}"))? .into_iter() .collect(); let cond = args.first().ok_or("`assert!` needs a condition")?; let v = self.expr(cond)?; let msg = if args.len() > 1 { self.format_pieces(&args[1..])? } else { fmt::nim_str("assertion failed") }; Ok(format!("(if not ({}): rsPanic({}))", v.code, msg)) } "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => { let args: Vec = mac .parse_body_with( syn::punctuated::Punctuated::::parse_terminated, ) .map_err(|e| format!("{name}!: {e}"))? .into_iter() .collect(); if args.len() < 2 { return Err(format!("`{name}!` takes two operands")); } let a = self.expr(&args[0])?; let b = self.expr_at(&args[1], a.ty.as_ref())?; let ne = name.ends_with("_ne"); let op = if ne { "!=" } else { "==" }; // Rust's message shows both sides; reproducing it keeps a // failing assertion as informative as the original. let label = if ne { "assertion failed: `(left != right)`" } else { "assertion failed: `(left == right)`" }; Ok(format!( "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))", a.code, op, b.code, fmt::nim_str(label), a.code, b.code )) } // The `log` facade. See `src/prelude.nim` for why these are // lowered directly rather than expanded. The enabled check wraps // the whole thing because Rust does not evaluate a log record's // arguments when the level is disabled. "error" | "warn" | "info" | "debug" | "trace" | "log" => { let args: Vec = mac .parse_body_with( syn::punctuated::Punctuated::::parse_terminated, ) .map_err(|e| format!("`{name}!`: {e}"))? .into_iter() .collect(); let (level, rest) = if name == "log" { let first = args.first().ok_or("`log!` needs a level")?; (self.log_level_of(first)?, &args[1..]) } else { ( match name.as_str() { "error" => "rsLvlError", "warn" => "rsLvlWarn", "info" => "rsLvlInfo", "debug" => "rsLvlDebug", _ => "rsLvlTrace", } .to_string(), &args[..], ) }; let msg = self.format_pieces(rest)?; let target = fmt::nim_str(&self.cur_mod.clone()); Ok(format!( "(if rsLogEnabled({lvl}): rsLog({lvl}, {target}, {msg}))", lvl = level )) } "log_enabled" => { let e: Expr = mac .parse_body() .map_err(|e| format!("`log_enabled!`: {e}"))?; let l = self.log_level_of(&e)?; Ok(format!("rsLogEnabled({l})")) } "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 => { if let Some(why) = self.mrules_bad.get(other) { return Err(format!("`{other}!` cannot be expanded: {why}")); } if let Some(def) = self.mrules.get(other).cloned() { // A multi-rule macro commonly recurses, shedding one // argument per step. The depth limit turns a macro that // does not terminate into an error rather than a hang. if self.macro_depth > 64 { return Err(format!( "`{other}!` expanded more than 64 levels deep; it \ does not appear to terminate" )); } let expanded = def .expand(mac.tokens.clone()) .map_err(|e| format!("expanding `{other}!`: {e}"))?; // The expansion is ordinary Rust, lowered in a context // where its types are known. let e: Expr = syn::parse2(expanded.clone()).map_err(|_| { format!( "`{other}!` expands to something that is not an \ expression: `{}`", expanded ) })?; self.macro_depth += 1; let r = self.expr(&e); self.macro_depth -= 1; return Ok(r?.code); } 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)) } }; let integer = v.ty.as_ref().is_some_and(|t| t.is_integer()); if spec.radix.is_some() && !integer && v.ty.is_none() { return Err( "a radix format (`{:x}`, `{:b}`, ...) needs a known \ argument type: on an integer it formats the bit \ pattern, on anything else it calls that type's own \ impl" .into(), ); } parts.push(fmt::render_arg(&v.code, spec, integer)); } } } // 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), }, } } // `unsafe { .. }` is transparent, so it is an expression exactly when // its block is one. Expr::Unsafe(u) => single_expr(&u.block).is_some_and(expressible), Expr::Block(b) => { b.label.is_none() && matches!(b.block.stmts.last(), Some(Stmt::Expr(_, None))) } Expr::Match(_) | 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(), } } /// `(trait, operator)` for every operator trait we dispatch. const OPERATOR_TRAITS: &[(&str, &str)] = &[ ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"), ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"), ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="), ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="), ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="), ("Neg", "neg"), ("Not", "not"), ]; /// `(operator, trait method name)`. const OP_METHOD: &[(&str, &str)] = &[ ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"), ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"), ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"), ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"), ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"), (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"), ]; fn op_method(op: &str) -> &'static str { OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("") } /// The operator symbol a compound assignment applies. fn compound_symbol(op: &BinOp) -> &'static str { match op { BinOp::AddAssign(_) => "+=", BinOp::SubAssign(_) => "-=", BinOp::MulAssign(_) => "*=", BinOp::DivAssign(_) => "/=", BinOp::RemAssign(_) => "%=", BinOp::BitAndAssign(_) => "&=", BinOp::BitOrAssign(_) => "|=", BinOp::BitXorAssign(_) => "^=", BinOp::ShlAssign(_) => "<<=", BinOp::ShrAssign(_) => ">>=", _ => "", } } fn binary_symbol(op: &BinOp) -> &'static str { match op { BinOp::Add(_) => "+", BinOp::Sub(_) => "-", BinOp::Mul(_) => "*", BinOp::Div(_) => "/", BinOp::Rem(_) => "%", BinOp::BitAnd(_) => "&", BinOp::BitOr(_) => "|", BinOp::BitXor(_) => "^", BinOp::Shl(_) => "<<", BinOp::Shr(_) => ">>", _ => "", } } /// The operator a trait overloads, if it is one of the operator traits. fn operator_trait(t: &str) -> Option<&'static str> { Some(match t { "Add" => "+", "Sub" => "-", "Mul" => "*", "Div" => "/", "Rem" => "%", "BitAnd" => "&", "BitOr" => "|", "BitXor" => "^", "Shl" => "<<", "Shr" => ">>", "AddAssign" => "+=", "SubAssign" => "-=", "MulAssign" => "*=", "DivAssign" => "/=", "RemAssign" => "%=", "BitAndAssign" => "&=", "BitOrAssign" => "|=", "BitXorAssign" => "^=", "ShlAssign" => "<<=", "ShrAssign" => ">>=", "Neg" => "neg", "Not" => "not", _ => return None, }) } /// The Nim proc name for a trait method, qualified by trait and type so that /// two traits declaring the same method name cannot collide. fn trait_method_name(ty: &str, tr: &str, m: &str) -> String { format!("rs{}_{}_{}", tr, ty, m) } 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", } } /// Whether an expression is an iterator-producing chain rather than a value. fn is_iterator_expr(e: &Expr) -> bool { match e { Expr::MethodCall(m) => matches!( m.method.to_string().as_str(), "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact" | "chunks_exact_mut" | "windows" ), Expr::Paren(p) => is_iterator_expr(&p.expr), _ => false, } } /// Whether an expression denotes a place -- a variable, a field, or an index /// or slice of one -- and so may be re-evaluated with no side effect. fn is_pure_place(e: &Expr) -> bool { match e { Expr::Path(_) => true, Expr::Field(f) => is_pure_place(&f.base), Expr::Index(i) => { is_pure_place(&i.expr) && match &*i.index { Expr::Range(r) => { r.start.as_deref().map_or(true, is_pure_place) && r.end.as_deref().map_or(true, is_pure_place) } other => is_pure_place(other), } } Expr::Lit(_) => true, Expr::Reference(r) => is_pure_place(&r.expr), Expr::Paren(p) => is_pure_place(&p.expr), Expr::Group(g) => is_pure_place(&g.expr), // Arithmetic on places is still side-effect free, so a bound like // `..want - 1` does not stop the binding being an alias. Expr::Binary(b) if !is_compound(&b.op) => { is_pure_place(&b.left) && is_pure_place(&b.right) } Expr::Unary(u) => is_pure_place(&u.expr), Expr::Cast(c) => is_pure_place(&c.expr), _ => false, } } /// Whether an expression is a `&mut` borrow, directly or through parens. fn is_mut_borrow(e: &Expr) -> bool { match e { Expr::Reference(r) => r.mutability.is_some(), Expr::Paren(p) => is_mut_borrow(&p.expr), Expr::Group(g) => is_mut_borrow(&g.expr), _ => false, } } 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", } } 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", } }