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

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

lower.rs · 4033 lines · 166.4 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1//! Rust AST -> Nim source.
2//!
3//! The governing rule is in DESIGN.md and it shapes every function here:
4//! anything whose Rust semantics cannot be reproduced exactly in Nim returns
5//! `Err` with a reason. Nothing is emitted on a guess. Where a construct maps
6//! one-to-one (signed `shr`, unsigned wrapping, truncating `div`/`mod`) the
7//! mapping is direct and there is a comment saying why that is safe.
8
9use crate::fmt;
10use crate::ty::{self, Nim};
11use std::collections::HashMap;
12use syn::{
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago13 BinOp, Expr, FnArg, GenericArgument, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago14};
15
16// --------------------------------------------------------------- vocabulary
17
18/// Nim keywords. Rust code may legally use any of these as an identifier.
19const NIM_KEYWORDS: &[&str] = &[
20 "addr", "and", "as", "asm", "bind", "block", "break", "case", "cast",
21 "concept", "const", "continue", "converter", "defer", "discard", "distinct",
22 "div", "do", "elif", "else", "end", "enum", "except", "export", "finally",
23 "for", "from", "func", "if", "import", "in", "include", "interface", "is",
24 "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not",
25 "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref",
26 "return", "shl", "shr", "static", "template", "try", "tuple", "type",
27 "using", "var", "when", "while", "xor", "result", "echo",
28];
29
30fn ident(name: &str) -> String {
31 if NIM_KEYWORDS.contains(&name) {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago32 return format!("{name}_r");
33 }
34 // Nim identifiers may not begin with an underscore, and may not contain
35 // two in a row. Rust uses both freely (`_unused`, `__private`).
36 let mut out = String::new();
37 let mut last_us = false;
38 for (i, c) in name.chars().enumerate() {
39 if c == '_' {
40 if i == 0 {
41 out.push('u');
42 out.push('_');
43 last_us = true;
44 continue;
45 }
46 if last_us {
47 continue;
48 }
49 last_us = true;
50 out.push('_');
51 } else {
52 last_us = false;
53 out.push(c);
54 }
55 }
56 if out.ends_with('_') {
57 out.push('x');
58 }
59 out
60}
61
62/// A `for`-loop source, resolved from a chain of iterator adaptors.
63///
64/// Rust's slice iterators are lazy and compose; Nim's `for` is over one
65/// sequence. So a chain is resolved into this shape and then emitted as a
66/// single index loop, with each binding becoming an *lvalue* into the original
67/// container. That is what makes `*dst = v` through `iter_mut()` write back to
68/// the caller's slice rather than to a copy.
69#[derive(Clone, Debug)]
70enum Iter {
71 /// `a..b` / `a..=b`.
72 Range { lo: String, hi: String, closed: bool, ty: Option<Nim> },
73 /// `for x in a`, `a.iter()`, `a.iter_mut()`. `off`/`len` let the same
74 /// shape cover a subslice view. `mutable` only affects whether the binding
75 /// may be assigned through.
76 Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool },
77 /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of
78 /// `k` elements starting at `k * i`.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago79 Chunks { code: String, base: String, len: String, k: String, elem: Option<Nim>, mutable: bool },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago80 /// `a.windows(k)`: like `Chunks` but advancing one element at a time.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago81 Windows { code: String, base: String, len: String, k: String, elem: Option<Nim> },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago82 /// `.enumerate()` — the index is the first half of the pair.
83 Enumerate(Box<Iter>),
84 /// `.zip(other)` — stops at the shorter, as Rust's does.
85 Zip(Box<Iter>, Box<Iter>),
86}
87
88impl Iter {
89 /// The number of iterations, as a Nim expression in terms of the loop's
90 /// own containers.
91 fn len(&self) -> String {
92 match self {
93 Iter::Range { lo, hi, closed, .. } => {
94 let n = format!("(int({hi}) - int({lo}))");
95 if *closed { format!("({n} + 1)") } else { n }
96 }
97 Iter::Elems { len, .. } => len.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago98 Iter::Chunks { k, len, .. } => format!("({} div int({}))", len, k),
99 Iter::Windows { len, k, .. } => format!("(max(0, {} - int({}) + 1))", len, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago100 Iter::Enumerate(i) => i.len(),
101 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
102 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago103 }
104}
105
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago106/// How a `for`-loop pattern name refers back into the container it came from.
107#[derive(Clone, Debug)]
108enum Alias {
109 /// The name stands for this Nim lvalue expression.
110 Value { code: String, ty: Option<Nim> },
111 /// The name stands for a window: `code[off .. off + len - 1]`.
112 Window { code: String, off: String, len: String, elem: Option<Nim> },
113}
114
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago115/// A lowered expression: its Nim text, and its type where we know it.
116///
117/// The type is not decoration. Nim needs it to pick `div` over `/`, to size a
118/// `cast`, and to annotate every binding so that Nim's own type checker
119/// catches a mistake in this file rather than letting it through as output
120/// that runs and is wrong.
121#[derive(Clone, Debug)]
122struct Val {
123 code: String,
124 ty: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago125 /// Set when the value *is* a slice view rather than a Nim value: binding
126 /// it introduces an alias, not a copy.
127 window: Option<Alias>,
128 /// For `get`/`get_mut`: the condition under which the `Option` is `Some`,
129 /// carried until an `ok_or`/`?` or an `unwrap` consumes it. Nim's view
130 /// types cannot live inside an object, so an `Option` of a view has no
131 /// runtime representation -- it is tracked here instead.
132 guard: Option<String>,
133 /// The error an `ok_or` attached to that guard.
134 guard_err: Option<String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago135}
136
137impl Val {
138 fn new(code: impl Into<String>, ty: Option<Nim>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago139 Val { code: code.into(), ty, window: None, guard: None, guard_err: None }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago140 }
141 fn untyped(code: impl Into<String>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago142 Val::new(code, None)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago143 }
144}
145
146struct Sig {
147 params: Vec<Nim>,
148 ret: Nim,
149}
150
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago151/// One variant of a Rust enum.
152#[derive(Clone)]
153struct Variant {
154 name: String,
155 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
156 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
157 /// Nim requires the branches of a variant object to have distinct fields.
158 fields: Vec<(String, Nim)>,
159}
160
161#[derive(Clone)]
162struct EnumDef {
163 name: String,
164 /// True when every variant is a unit variant, which Nim represents as a
165 /// plain `enum` rather than an object variant.
166 simple: bool,
167 variants: Vec<Variant>,
168}
169
170impl EnumDef {
171 fn kind_ident(&self, v: &str) -> String {
172 format!("k{}{}", self.name, v)
173 }
174 fn ctor_ident(&self, v: &str) -> String {
175 format!("{}{}", self.name, v)
176 }
177 fn get(&self, v: &str) -> Option<&Variant> {
178 self.variants.iter().find(|x| x.name == v)
179 }
180}
181
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago182pub struct Lowerer {
183 out: String,
184 indent: usize,
185 scopes: Vec<HashMap<String, Nim>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago186 /// Names introduced by a `for` pattern that stand for an lvalue or a
187 /// window into a container, rather than for a variable of their own.
188 alias_scopes: Vec<HashMap<String, Alias>>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago189 /// `(module, name) -> signature`. Rust keeps `lower::decode` and
190 /// `mixed::decode` apart by module; flattening into one Nim module would
191 /// merge them, so the module is part of the key and of the emitted name.
192 fns: HashMap<(String, String), Sig>,
193 /// Module being lowered: the file stem, or empty for the crate root.
194 cur_mod: String,
195 /// `use` brings a name into scope from another module. Flattening loses
196 /// the module structure, so the mapping is recorded and consulted when a
197 /// bare call is resolved.
198 use_map: HashMap<String, String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago199 /// struct name -> (field, type)
200 structs: HashMap<String, Vec<(String, Nim)>>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago201 enums: HashMap<String, EnumDef>,
202 /// variant name -> enums declaring it. A variant named by more than one
203 /// enum must be written qualified, or it is rejected as ambiguous.
204 variant_owner: HashMap<String, Vec<String>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago205 /// `(receiver type, method) -> signature`. Keyed by type because two
206 /// types may define the same method name, and Nim tells them apart by
207 /// overload resolution on the first parameter.
208 methods: HashMap<(String, String), Sig>,
209 /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
210 /// on a user type can be checked rather than assumed.
211 fmt_impls: HashMap<(String, String), ()>,
212 /// `(from, to)` conversions declared by `impl From<A> for B`.
213 from_impls: HashMap<(String, String), String>,
214 /// Forward declarations, emitted between the type definitions and the
215 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
216 /// proc is declared up front rather than the input being reordered --
217 /// which would not work for mutual recursion anyway.
218 forwards: Vec<String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago219 /// Element type a `vec![..]` should build, from the binding's annotation.
220 vec_expect: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago221 /// While lowering a formatting impl: the `Formatter` parameter's name.
222 /// Writes through it produce the proc's string result.
223 fmt_param: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago224 /// `type X<T> = ...`, expanded before any type is mapped.
225 aliases: HashMap<String, (Vec<String>, syn::Type)>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago226 /// Module names supplied as separate input files. A `mod x;` naming one
227 /// of these is satisfied by that file having been passed in.
228 pub modules: Vec<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago229 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
230 /// evaluated against these exactly as rustc would, so an item that is
231 /// dropped here is genuinely not part of the program being compiled.
232 pub features: Vec<String>,
233 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago234 /// Return type of the proc being lowered, so `return e` and a trailing
235 /// expression can type their literals the way Rust's inference would.
236 ret: Option<Nim>,
237 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
238 /// statement must assign their value to.
239 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago240 /// Set while lowering a `while` condition, which Nim re-evaluates each
241 /// iteration and so cannot have statements hoisted out of it.
242 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago243 tmp: usize,
244}
245
246impl Lowerer {
247 pub fn new() -> Self {
248 Lowerer {
249 out: String::new(),
250 indent: 0,
251 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago252 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago253 fns: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago254 cur_mod: String::new(),
255 use_map: HashMap::new(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago256 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago257 enums: HashMap::new(),
258 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago259 methods: HashMap::new(),
260 fmt_impls: HashMap::new(),
261 from_impls: HashMap::new(),
262 fmt_param: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago263 vec_expect: None,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago264 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago265 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago266 modules: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago267 features: Vec::new(),
268 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago269 ret: None,
270 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago271 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago272 tmp: 0,
273 }
274 }
275
276 // ------------------------------------------------------------ emission
277
278 fn line(&mut self, s: &str) {
279 for _ in 0..self.indent {
280 self.out.push_str(" ");
281 }
282 self.out.push_str(s);
283 self.out.push('\n');
284 }
285
286 fn blank(&mut self) {
287 self.out.push('\n');
288 }
289
290 fn fresh(&mut self, hint: &str) -> String {
291 self.tmp += 1;
292 format!("rsTmp{}{}", hint, self.tmp)
293 }
294
295 // --------------------------------------------------------------- scope
296
297 fn push_scope(&mut self) {
298 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago299 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago300 }
301 fn pop_scope(&mut self) {
302 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago303 self.alias_scopes.pop();
304 }
305 fn bind_alias(&mut self, name: &str, a: Alias) {
306 self.alias_scopes
307 .last_mut()
308 .unwrap()
309 .insert(name.to_string(), a);
310 }
311 fn lookup_alias(&self, name: &str) -> Option<Alias> {
312 self.alias_scopes
313 .iter()
314 .rev()
315 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago316 }
317 fn bind(&mut self, name: &str, t: Nim) {
318 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
319 }
320 fn lookup(&self, name: &str) -> Option<Nim> {
321 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
322 }
323
324 // ---------------------------------------------------------------- file
325
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago326 pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago327 self.out.push_str(include_str!("prelude.nim"));
328 self.blank();
329
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago330 // Pass 0: type aliases. A signature in one file may use an alias
331 // declared in another, and inputs are given in whatever order suits
332 // the caller, so aliases are registered before anything is mapped.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago333 for (m, f) in files {
334 self.cur_mod = m.clone();
335 for item in &f.items {
336 self.collect_aliases(item)?;
337 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago338 }
339
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago340 // Pass 1: signatures and struct shapes, so that a call can be typed
341 // regardless of declaration order (Rust has no forward declarations).
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago342 for (m, f) in files {
343 self.cur_mod = m.clone();
344 for item in &f.items {
345 self.collect(item)?;
346 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago347 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago348 // Pass 2: type definitions, which every signature may mention.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago349 for (m, f) in files {
350 self.cur_mod = m.clone();
351 for item in &f.items {
352 self.item_types(item)?;
353 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago354 }
355
356 // Pass 3: forward declarations. Rust imposes no declaration order and
357 // Nim does, so everything is declared before any body is emitted;
358 // reordering the input would not handle mutual recursion anyway.
359 if !self.forwards.is_empty() {
360 for f in self.forwards.clone() {
361 self.line(&f);
362 }
363 self.blank();
364 }
365
366 // Pass 4: bodies.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago367 for (m, f) in files {
368 self.cur_mod = m.clone();
369 for item in &f.items {
370 self.item(item)?;
371 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago372 }
373
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago374 if self.fns.contains_key(&(String::new(), "main".to_string())) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago375 self.blank();
376 self.line("when isMainModule:");
377 self.indent += 1;
378 self.line("try:");
379 self.line(" main()");
380 // Rust's panic exits 101 with a message on stderr. Nim's Defects
381 // exit 1. Mapping them here is what keeps the differential runner's
382 // exit-status comparison meaningful for panicking programs.
383 self.line("except RustPanic as e:");
384 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
385 self.line(" quit(101)");
386 self.line("except Defect as e:");
387 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
388 self.line(" quit(101)");
389 self.indent -= 1;
390 }
391 Ok(std::mem::take(&mut self.out))
392 }
393
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago394 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
395 if !self.cfg_keeps(item_attrs(item))? {
396 return Ok(());
397 }
398 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago399 Item::Use(u) => self.collect_use(&u.tree, &[]),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago400 Item::Type(t) => {
401 let params: Vec<String> = t
402 .generics
403 .params
404 .iter()
405 .filter_map(|g| match g {
406 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
407 _ => None,
408 })
409 .collect();
410 self.aliases
411 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
412 }
413 Item::Mod(m) if m.content.is_some() => {
414 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
415 for i in &items {
416 self.collect_aliases(i)?;
417 }
418 }
419 _ => {}
420 }
421 Ok(())
422 }
423
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago424 /// Record what a `use` brings into scope, as `name -> module`.
425 fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
426 use syn::UseTree;
427 match t {
428 UseTree::Path(p) => {
429 let mut pre = prefix.to_vec();
430 pre.push(p.ident.to_string());
431 self.collect_use(&p.tree, &pre);
432 }
433 UseTree::Group(g) => {
434 for t in &g.items {
435 self.collect_use(t, prefix);
436 }
437 }
438 UseTree::Name(n) => {
439 let m = module_of(prefix);
440 self.use_map.insert(n.ident.to_string(), m);
441 }
442 UseTree::Rename(r) => {
443 let m = module_of(prefix);
444 self.use_map.insert(r.rename.to_string(), m);
445 }
446 // A glob brings in an unknown set of names; resolution falls back
447 // to the current module and the root, as it would without it.
448 UseTree::Glob(_) => {}
449 }
450 }
451
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago452 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago453 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
454 // silently would change what the program does; picking a feature set
455 // on the user's behalf would be a guess. So it is reported, except on
456 // items that carry no runtime meaning here anyway.
457 if !self.cfg_keeps(item_attrs(item))? {
458 self.dropped_by_cfg += 1;
459 return Ok(());
460 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago461 match item {
462 Item::Fn(f) => {
463 let (params, ret) = self.signature(&f.sig)?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago464 let name = f.sig.ident.to_string();
465 let nim = self.fn_name(&self.cur_mod, &name);
466 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
467 self.fns
468 .insert((self.cur_mod.clone(), name), Sig { params, ret });
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago469 }
470 Item::Struct(s) => {
471 let mut fields = Vec::new();
472 for (i, f) in s.fields.iter().enumerate() {
473 let name = match &f.ident {
474 Some(id) => id.to_string(),
475 None => format!("f{i}"), // tuple struct
476 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago477 // A field of `&[T]` / `&str` type is a borrow, and Nim's
478 // view types allow it as an object field, so it stays a
479 // view rather than being copied into a `seq`.
480 let t = self.map_ty(&f.ty)?;
481 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
482 fields.push((name, t));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago483 }
484 self.structs.insert(s.ident.to_string(), fields);
485 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago486 Item::Mod(m) if m.content.is_some() => {
487 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
488 for i in &items {
489 self.collect(i)?;
490 }
491 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago492 Item::Type(t) => {
493 let params: Vec<String> = t
494 .generics
495 .params
496 .iter()
497 .filter_map(|g| match g {
498 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
499 _ => None,
500 })
501 .collect();
502 self.aliases
503 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
504 }
505 Item::Enum(e) => {
506 let name = e.ident.to_string();
507 if e.generics.params.iter().any(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
508 return Err(format!("`enum {name}` is generic: not implemented yet"));
509 }
510 let mut variants = Vec::new();
511 for v in &e.variants {
512 let vname = v.ident.to_string();
513 if v.discriminant.is_some() {
514 return Err(format!(
515 "`{name}::{vname}` has an explicit discriminant; Rust's \
516 `as` on such an enum has a value this lowering does not \
517 yet preserve"
518 ));
519 }
520 let mut fields = Vec::new();
521 for (i, f) in v.fields.iter().enumerate() {
522 // Nim requires the branches of a variant object to have
523 // distinct field names, so each is prefixed.
524 let fname = match &f.ident {
525 Some(id) => format!("{vname}_{id}"),
526 None => format!("{vname}_f{i}"),
527 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago528 let t = self.map_ty(&f.ty)?;
529 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
530 fields.push((fname, t));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago531 }
532 variants.push(Variant { name: vname, fields });
533 }
534 let simple = variants.iter().all(|v| v.fields.is_empty());
535 for v in &variants {
536 self.variant_owner
537 .entry(v.name.clone())
538 .or_default()
539 .push(name.clone());
540 }
541 self.enums.insert(
542 name.clone(),
543 EnumDef { name, simple, variants },
544 );
545 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago546 Item::Impl(im) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago547 let self_ty = self.map_ty(&im.self_ty)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago548 let tyname = type_name(&self_ty);
549 if let Some((path, _)) = &im.trait_ {
550 let tr = path_name(path);
551 if im.items.is_empty() {
552 // A marker trait with no items. We do not model trait
553 // resolution at all, so it generates nothing; any use
554 // that actually needed the trait (a `dyn`, a bound) is
555 // rejected where it appears.
556 return Ok(());
557 }
558 if is_fmt_trait(&tr) {
559 self.forwards.push(format!(
560 "proc {}*(self: {}): string",
561 fmt_proc(&tr),
562 self_ty.render()
563 ));
564 self.fmt_impls.insert((tyname, tr), ());
565 return Ok(());
566 }
567 if tr == "From" {
568 let syn::ImplItem::Fn(m) = &im.items[0] else {
569 return Err("`impl From` must contain `fn from`".into());
570 };
571 let (params, _) = self.signature(&m.sig)?;
572 let src = params
573 .first()
574 .ok_or("`fn from` takes one argument")?
575 .clone();
576 let name = format!("rsFrom{}{}", tyname, type_name(&src));
577 self.forwards.push(self.head_of(&name, &m.sig, None)?);
578 self.from_impls
579 .insert((type_name(&src), tyname), name);
580 return Ok(());
581 }
582 return Err(format!(
583 "`impl {tr} for {tyname}`: only formatting traits \
584 (Display, Debug, LowerHex, UpperHex, Binary, Octal), \
585 `From`, and marker traits with no items are implemented"
586 ));
587 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago588 for it in &im.items {
589 if let syn::ImplItem::Fn(m) = it {
590 let (mut params, ret) = self.signature(&m.sig)?;
591 if takes_self(&m.sig) {
592 params.insert(0, self_ty.clone());
593 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago594 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
595 let head = self.head_of(&m.sig.ident.to_string(), &m.sig, recv.as_ref())?;
596 self.forwards.push(head);
597 self.methods
598 .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret });
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago599 }
600 }
601 }
602 _ => {}
603 }
604 Ok(())
605 }
606
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago607 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
608 ///
609 /// This is evaluation, not approximation: rustc does the same thing, and
610 /// an item whose predicate is false is not part of the compiled program.
611 /// A predicate that cannot be evaluated is reported rather than assumed.
612 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
613 for a in attrs {
614 if a.path().is_ident("cfg") {
615 let pred: syn::Meta = a
616 .parse_args()
617 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
618 if !self.cfg_eval(&pred)? {
619 return Ok(false);
620 }
621 }
622 }
623 Ok(true)
624 }
625
626 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
627 match m {
628 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
629 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
630 return Err("`feature = ..` expects a string".into());
631 };
632 Ok(self.features.iter().any(|f| *f == s.value()))
633 }
634 syn::Meta::List(l) if l.path.is_ident("not") => {
635 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
636 Ok(!self.cfg_eval(&inner)?)
637 }
638 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
639 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
640 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
641 .map_err(|e| e.to_string())?;
642 let all = l.path.is_ident("all");
643 let mut acc = all;
644 for i in &items {
645 let v = self.cfg_eval(i)?;
646 acc = if all { acc && v } else { acc || v };
647 }
648 Ok(acc)
649 }
650 other => Err(format!(
651 "`#[cfg({})]` is not a predicate rustnim can evaluate; only \
652 `feature = \"..\"`, `not`, `all` and `any` are implemented",
653 quote_meta(other)
654 )),
655 }
656 }
657
658 /// Map a Rust type, expanding any `type` alias first. Every type in the
659 /// lowering goes through here rather than calling `ty::map` directly, so
660 /// an alias cannot be missed in one position and honoured in another.
661 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
662 ty::map(&self.expand(t, 0)?)
663 }
664
665 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
666 if depth > 16 {
667 return Err("type alias expansion did not terminate; is it cyclic?".into());
668 }
669 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
670 // Only an unqualified name can be one of this file's aliases.
671 // `fmt::Result` and `core::result::Result` are different types that
672 // merely end in the same segment.
673 if p.path.segments.len() != 1 {
674 return Ok(t.clone());
675 }
676 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
677 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
678 return Ok(t.clone());
679 };
680 let args: Vec<syn::Type> = match &seg.arguments {
681 syn::PathArguments::AngleBracketed(a) => a
682 .args
683 .iter()
684 .filter_map(|g| match g {
685 GenericArgument::Type(t) => Some(t.clone()),
686 _ => None,
687 })
688 .collect(),
689 _ => vec![],
690 };
691 if args.len() != params.len() {
692 // Flattening several files into one module can bring a crate's own
693 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
694 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
695 // module; here they are told apart by arity, and a use that fits
696 // neither is left for `ty::map` to report.
697 return Ok(t.clone());
698 }
699 self.expand(&substitute(target, params, &args), depth + 1)
700 }
701
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago702 /// The Nim name for a function, qualified by its module.
703 fn fn_name(&self, module: &str, name: &str) -> String {
704 if module.is_empty() {
705 ident(name)
706 } else {
707 format!("{}_{}", module, ident(name))
708 }
709 }
710
711 /// Resolve a call path to the module and name it refers to: an explicit
712 /// `mixed::decode`, then the current module, then the crate root.
713 fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
714 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
715 let last = segs.last()?.clone();
716 if segs.len() >= 2 {
717 let q = &segs[segs.len() - 2];
718 if self.fns.contains_key(&(q.clone(), last.clone())) {
719 return Some((q.clone(), last));
720 }
721 }
722 let imported = self.use_map.get(&last).cloned();
723 for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
724 .into_iter()
725 .flatten()
726 {
727 if self.fns.contains_key(&(m.clone(), last.clone())) {
728 return Some((m, last));
729 }
730 }
731 None
732 }
733
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago734 /// The Nim `proc` head for a Rust signature, used both for the forward
735 /// declaration and for the definition, so the two cannot drift apart.
736 fn head_of(
737 &self,
738 name: &str,
739 sig: &syn::Signature,
740 recv: Option<&Nim>,
741 ) -> Result<String, String> {
742 let (ptys, ret) = self.signature(sig)?;
743 let mut parts = Vec::new();
744 if let Some(self_ty) = recv {
745 let mutable = matches!(
746 sig.inputs.first(),
747 Some(FnArg::Receiver(r))
748 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
749 );
750 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
751 parts.push(format!("self: {}", t.render()));
752 }
753 let typed: Vec<&syn::PatType> = sig
754 .inputs
755 .iter()
756 .filter_map(|a| match a {
757 FnArg::Typed(t) => Some(t),
758 _ => None,
759 })
760 .collect();
761 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
762 let pname = match &*p.pat {
763 Pat::Ident(id) => id.ident.to_string(),
764 Pat::Wild(_) => format!("unused{}", parts.len()),
765 _ => return Err("only plain identifier parameters are supported".into()),
766 };
767 let _ = i;
768 parts.push(format!("{}: {}", ident(&pname), t.render()));
769 }
770 Ok(if ret == Nim::Unit {
771 format!("proc {}*({})", ident(name), parts.join(", "))
772 } else {
773 format!("proc {}*({}): {}", ident(name), parts.join(", "), ret.render())
774 })
775 }
776
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago777 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago778 // `unsafe fn` marks a contract for callers; it does not change what
779 // the body means, so it lowers like any other proc.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago780 if sig.asyncness.is_some() {
781 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
782 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago783 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
784 // `fn encode<'a>(..)` is not generic for our purposes. Type and const
785 // parameters genuinely are, and are rejected.
786 if let Some(p) = sig.generics.params.iter().find(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
787 let what = match p {
788 syn::GenericParam::Const(_) => "const",
789 _ => "type",
790 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago791 return Err(format!(
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago792 "`fn {}` has a {what} parameter: generics are not implemented yet",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago793 sig.ident
794 ));
795 }
796 let mut params = Vec::new();
797 for a in &sig.inputs {
798 if let FnArg::Typed(t) = a {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago799 params.push(self.map_ty(&t.ty)?);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago800 }
801 }
802 let ret = match &sig.output {
803 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago804 // A returned `&[T]` is a borrow of the caller's buffer, so it
805 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
806 // a `seq`, which `owned()` would do to both.
807 ReturnType::Type(_, t) => {
808 let n = self.map_ty(t)?;
809 if returns_borrow(t) { n } else { n.owned() }
810 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago811 };
812 Ok((params, ret))
813 }
814
815 // --------------------------------------------------------------- items
816
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago817 /// Emit the type definitions only: they must precede every signature.
818 fn item_types(&mut self, item: &Item) -> Result<(), String> {
819 if !self.cfg_keeps(item_attrs(item))? {
820 return Ok(());
821 }
822 match item {
823 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
824 Item::Mod(m) if m.content.is_some() => {
825 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
826 for i in &items {
827 self.item_types(i)?;
828 }
829 Ok(())
830 }
831 _ => Ok(()),
832 }
833 }
834
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago835 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago836 if !self.cfg_keeps(item_attrs(item))? {
837 return Ok(());
838 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago839 // Types were emitted in their own pass.
840 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
841 return Ok(());
842 }
843 self.item_inner(item)
844 }
845
846 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago847 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago848 Item::Fn(f) => {
849 let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
850 self.func_named(&nim, &f.sig, &f.block, None)
851 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago852 Item::Struct(s) => {
853 let name = s.ident.to_string();
854 let fields = self.structs[&name].clone();
855 self.line(&format!("type {}* = object", ident(&name)));
856 self.indent += 1;
857 if fields.is_empty() {
858 self.line("discard");
859 }
860 for (fname, fty) in &fields {
861 self.line(&format!("{}*: {}", ident(fname), fty.render()));
862 }
863 self.indent -= 1;
864 self.blank();
865 Ok(())
866 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago867 Item::Type(_) => Ok(()), // expanded at every use site
868 Item::Enum(e) => {
869 let def = self.enums[&e.ident.to_string()].clone();
870 self.emit_enum(&def);
871 Ok(())
872 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago873 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago874 let t = self.map_ty(&c.ty)?.owned();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago875 let v = self.expr(&c.expr)?;
876 self.bind(&c.ident.to_string(), t.clone());
877 let line = format!("const {}*: {} = {}", ident(&c.ident.to_string()), t.render(), v.code);
878 self.line(&line);
879 self.blank();
880 Ok(())
881 }
882 Item::Impl(im) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago883 let self_ty = self.map_ty(&im.self_ty)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago884 if let Some((path, _)) = &im.trait_ {
885 let tr = path_name(path);
886 if im.items.is_empty() {
887 return Ok(());
888 }
889 let syn::ImplItem::Fn(m) = &im.items[0] else {
890 return Err(format!("unsupported item in `impl {tr}`"));
891 };
892 if is_fmt_trait(&tr) {
893 return self.fmt_impl(&tr, &self_ty, &m.sig, &m.block);
894 }
895 if tr == "From" {
896 let name = {
897 let (params, _) = self.signature(&m.sig)?;
898 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
899 self.from_impls[&(type_name(&src), type_name(&self_ty))].clone()
900 };
901 return self.func_named(&name, &m.sig, &m.block, None);
902 }
903 return Err(format!("`impl {tr}` is not implemented"));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago904 }
905 for it in &im.items {
906 match it {
907 syn::ImplItem::Fn(m) => {
908 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
909 self.func(&m.sig, &m.block, recv)?;
910 }
911 _ => return Err("only `fn` items are supported inside `impl`".into()),
912 }
913 }
914 Ok(())
915 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago916 // `use` and `extern crate` are resolution directives with no Nim
917 // analogue once everything is one module.
918 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
919 Item::Mod(m) if m.content.is_some() => {
920 // An inline `mod` is flattened; Nim has no nested modules in a
921 // single file.
922 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
923 for i in &items {
924 self.item(i)?;
925 }
926 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago927 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago928 Item::Mod(m) => {
929 // Satisfied if that file was passed in too; everything is one
930 // Nim module, so the declaration itself emits nothing.
931 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
932 return Ok(());
933 }
934 Err(format!(
935 "`mod {};` refers to another file that was not passed to \
936 rustnim; add it to the input list",
937 m.ident
938 ))
939 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago940 other => Err(format!("unsupported item: {}", item_kind(other))),
941 }
942 }
943
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago944 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
945 fn none_of(&self, expect: Option<&Nim>) -> String {
946 match expect {
947 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
948 format!("rsNone[{}]()", a[0].render())
949 }
950 _ => "rsNone()".to_string(),
951 }
952 }
953
954 fn emit_enum(&mut self, def: &EnumDef) {
955 let name = ident(&def.name);
956 if def.simple {
957 // Every variant is a unit variant, so a plain Nim enum is an exact
958 // fit: it compares, orders and `case`-checks like Rust's.
959 self.line(&format!("type {name}* = enum"));
960 self.indent += 1;
961 for v in &def.variants {
962 self.line(&format!("{}", ident(&v.name)));
963 }
964 self.indent -= 1;
965 self.blank();
966 self.line(&format!("proc rsDebug*(x: {name}): string ="));
967 self.indent += 1;
968 self.line("case x");
969 for v in &def.variants {
970 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
971 }
972 self.indent -= 1;
973 self.blank();
974 return;
975 }
976
977 // A data-carrying enum is a Nim object variant: one discriminant enum
978 // plus a branch per variant. This is the same shape the prelude uses
979 // for `Option` and `Result`.
980 self.line("type");
981 self.indent += 1;
982 self.line(&format!("{}Kind* = enum", name));
983 self.indent += 1;
984 for v in &def.variants {
985 self.line(&def.kind_ident(&v.name));
986 }
987 self.indent -= 1;
988 self.blank();
989 self.line(&format!("{}* = object", name));
990 self.indent += 1;
991 self.line(&format!("case kind*: {}Kind", name));
992 for v in &def.variants {
993 if v.fields.is_empty() {
994 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
995 } else {
996 self.line(&format!("of {}:", def.kind_ident(&v.name)));
997 self.indent += 1;
998 for (f, t) in &v.fields {
999 self.line(&format!("{}*: {}", ident(f), t.render()));
1000 }
1001 self.indent -= 1;
1002 }
1003 }
1004 self.indent -= 2;
1005 self.blank();
1006
1007 for v in &def.variants {
1008 let args: Vec<String> = v
1009 .fields
1010 .iter()
1011 .enumerate()
1012 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
1013 .collect();
1014 let inits: Vec<String> = v
1015 .fields
1016 .iter()
1017 .enumerate()
1018 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
1019 .collect();
1020 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1021 all.extend(inits);
1022 self.line(&format!(
1023 "proc {}*({}): {} = {}({})",
1024 def.ctor_ident(&v.name),
1025 args.join(", "),
1026 name,
1027 name,
1028 all.join(", ")
1029 ));
1030 }
1031 self.blank();
1032
1033 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1034 self.indent += 1;
1035 self.line("case x.kind");
1036 for v in &def.variants {
1037 if v.fields.is_empty() {
1038 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
1039 } else {
1040 let parts: Vec<String> = v
1041 .fields
1042 .iter()
1043 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
1044 .collect();
1045 self.line(&format!(
1046 "of {}: \"{}(\" & {} & \")\"",
1047 def.kind_ident(&v.name),
1048 v.name,
1049 parts.join(" & \", \" & ")
1050 ));
1051 }
1052 }
1053 self.indent -= 1;
1054 self.blank();
1055 }
1056
1057 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1058 /// to the enum that declares it.
1059 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
1060 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1061 let last = segs.last()?.clone();
1062 if segs.len() >= 2 {
1063 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
1064 if def.get(&last).is_some() {
1065 return Some((def.clone(), last));
1066 }
1067 }
1068 }
1069 // Unqualified: only unambiguous if exactly one enum declares it.
1070 match self.variant_owner.get(&last) {
1071 Some(owners) if owners.len() == 1 => {
1072 let def = self.enums.get(&owners[0])?;
1073 Some((def.clone(), last))
1074 }
1075 _ => None,
1076 }
1077 }
1078
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1079 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
1080 ///
1081 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
1082 /// observable result of `{}` is exactly the bytes written. So the method
1083 /// becomes `proc rsDisplay(self: T): string` and every write through the
1084 /// formatter produces that string. A `fmt` body that does anything else
1085 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
1086 /// because those affect the output and this model does not carry them.
1087 /// The window an expression names, if it names one.
1088 fn window_of(&self, e: &Expr) -> Option<Alias> {
1089 match e {
1090 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
1091 Some(a @ Alias::Window { .. }) => Some(a),
1092 _ => None,
1093 },
1094 Expr::Reference(r) => self.window_of(&r.expr),
1095 Expr::Paren(p) => self.window_of(&p.expr),
1096 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
1097 _ => None,
1098 }
1099 }
1100
1101 /// Whether an expression is the `Formatter` parameter of the formatting
1102 /// impl currently being lowered.
1103 fn is_fmt_param(&self, e: &Expr) -> bool {
1104 let Some(f) = &self.fmt_param else { return false };
1105 match e {
1106 Expr::Path(p) => path_name(&p.path) == *f,
1107 Expr::Reference(r) => self.is_fmt_param(&r.expr),
1108 Expr::Paren(p) => self.is_fmt_param(&p.expr),
1109 _ => false,
1110 }
1111 }
1112
1113 fn fmt_impl(
1114 &mut self,
1115 tr: &str,
1116 self_ty: &Nim,
1117 sig: &syn::Signature,
1118 body: &syn::Block,
1119 ) -> Result<(), String> {
1120 let proc_name = fmt_proc(tr);
1121 // The formatter is the parameter after `self`.
1122 let f = sig
1123 .inputs
1124 .iter()
1125 .filter_map(|a| match a {
1126 FnArg::Typed(t) => match &*t.pat {
1127 Pat::Ident(i) => Some(i.ident.to_string()),
1128 _ => None,
1129 },
1130 _ => None,
1131 })
1132 .next()
1133 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1134
1135 self.push_scope();
1136 self.bind("self", self_ty.clone());
1137 let saved = self.fmt_param.replace(f);
1138 let outer_ret = self.ret.replace(Nim::Prim("string".into()));
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago1139 // No assignment target: a formatter write *appends*, because a `fmt`
1140 // body may write repeatedly -- `UpperHex` writes once per byte in a
1141 // loop -- and assigning would keep only the last one.
1142 let outer_target = self.target.take();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1143
1144 self.line(&format!(
1145 "proc {}*(self: {}): string =",
1146 proc_name,
1147 self_ty.render()
1148 ));
1149 self.indent += 1;
1150 let before = self.out.len();
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago1151 let tail = self.block_body(body)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1152 self.emit_tail(tail);
1153 if self.out.len() == before {
1154 self.line("discard");
1155 }
1156 self.indent -= 1;
1157
1158 self.target = outer_target;
1159 self.ret = outer_ret;
1160 self.fmt_param = saved;
1161 self.pop_scope();
1162 self.blank();
1163 Ok(())
1164 }
1165
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1166 fn func(
1167 &mut self,
1168 sig: &syn::Signature,
1169 body: &syn::Block,
1170 recv: Option<Nim>,
1171 ) -> Result<(), String> {
1172 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1173 self.func_named(&name.clone(), sig, body, recv)
1174 }
1175
1176 fn func_named(
1177 &mut self,
1178 name: &str,
1179 sig: &syn::Signature,
1180 body: &syn::Block,
1181 recv: Option<Nim>,
1182 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1183 let (ptys, ret) = self.signature(sig)?;
1184
1185 self.push_scope();
1186 let mut rendered: Vec<String> = Vec::new();
1187
1188 if let Some(self_ty) = recv {
1189 // `&mut self` and `mut self` both mean the body may mutate the
1190 // receiver; only the former is observable by the caller, and a Nim
1191 // `var` parameter is the faithful spelling of that.
1192 let mutable = matches!(
1193 sig.inputs.first(),
1194 Some(FnArg::Receiver(r))
1195 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1196 );
1197 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1198 rendered.push(format!("self: {}", t.render()));
1199 self.bind("self", self_ty);
1200 }
1201
1202 let typed: Vec<&syn::PatType> = sig
1203 .inputs
1204 .iter()
1205 .filter_map(|a| match a {
1206 FnArg::Typed(t) => Some(t),
1207 _ => None,
1208 })
1209 .collect();
1210 for (p, t) in typed.iter().zip(ptys.iter()) {
1211 let pname = match &*p.pat {
1212 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1213 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
1214 // still needs a name for it.
1215 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1216 _ => return Err("only plain identifier parameters are supported".into()),
1217 };
1218 rendered.push(format!("{}: {}", ident(&pname), t.render()));
1219 // Inside the body a `var T` parameter is used exactly like a `T`.
1220 self.bind(&pname, t.clone().owned());
1221 }
1222
1223 let head = if ret == Nim::Unit {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1224 format!("proc {}*({}) =", ident(name), rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1225 } else {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1226 format!("proc {}*({}): {} =", ident(name), rendered.join(", "), ret.render())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1227 };
1228 self.line(&head);
1229 self.indent += 1;
1230 let outer_ret = self.ret.replace(ret.clone());
1231
1232 // A Rust fn's trailing expression is its return value. Naming Nim's
1233 // implicit `result` as the target makes that true whether the tail is
1234 // a plain expression or an `if`/`match` with statement arms.
1235 let outer_target = if ret == Nim::Unit {
1236 self.target.take()
1237 } else {
1238 self.target.replace(("result".to_string(), Some(ret.clone())))
1239 };
1240 let before = self.out.len();
1241 let tail = self.block_body_at(body, Some(&ret))?;
1242 self.target = outer_target;
1243 match tail {
1244 Some(v) if ret != Nim::Unit => {
1245 let code = v.code.clone();
1246 self.line(&format!("result = {code}"));
1247 }
1248 Some(v) => {
1249 // A trailing expression in a `()`-returning fn is evaluated for
1250 // its effect; Nim requires an explicit discard.
1251 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1252 if needs_discard && !v.code.is_empty() {
1253 let code = v.code.clone();
1254 self.line(&format!("discard {code}"));
1255 }
1256 }
1257 None => {}
1258 }
1259 if self.out.len() == before {
1260 self.line("discard");
1261 }
1262
1263 self.indent -= 1;
1264 self.ret = outer_ret;
1265 self.pop_scope();
1266 self.blank();
1267 Ok(())
1268 }
1269
1270 // ---------------------------------------------------------- statements
1271
1272 /// Lower a block's statements. Returns the block's trailing expression,
1273 /// if it has one, *without* emitting it — the caller decides whether that
1274 /// value is a return value, a binding, or discarded.
1275 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
1276 self.block_body_at(b, None)
1277 }
1278
1279 fn block_body_at(
1280 &mut self,
1281 b: &syn::Block,
1282 expect: Option<&Nim>,
1283 ) -> Result<Option<Val>, String> {
1284 // An assignment target belongs to *this* block's trailing expression
1285 // only. A non-final `if` is a statement and must not assign anything.
1286 let target = self.target.take();
1287 let n = b.stmts.len();
1288 let mut tail = None;
1289 for (i, st) in b.stmts.iter().enumerate() {
1290 let last = i + 1 == n;
1291 match st {
1292 Stmt::Expr(e, None) if last && expressible(e) => {
1293 tail = Some(self.expr_at(e, expect)?)
1294 }
1295 Stmt::Expr(e, None) if last => {
1296 // A trailing `if`/`match` with statement arms, or a loop.
1297 // Lower it as statements; if this block's value is wanted,
1298 // each arm assigns it.
1299 match &target {
1300 Some((t, ty)) => {
1301 let (t, ty) = (t.clone(), ty.clone());
1302 self.assign_from(e, &t, ty.as_ref())?;
1303 }
1304 None => self.stmt(st)?,
1305 }
1306 }
1307 _ => self.stmt(st)?,
1308 }
1309 }
1310 self.target = target;
1311 Ok(tail)
1312 }
1313
1314 /// Lower a block in statement position (loop bodies, `if` arms).
1315 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
1316 self.push_scope();
1317 self.indent += 1;
1318 let before = self.out.len();
1319 let want = self.target.clone().and_then(|(_, t)| t);
1320 let tail = self.block_body_at(b, want.as_ref())?;
1321 self.emit_tail(tail);
1322 if self.out.len() == before {
1323 self.line("discard");
1324 }
1325 self.indent -= 1;
1326 self.pop_scope();
1327 Ok(())
1328 }
1329
1330 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
1331 match s {
1332 Stmt::Local(l) => self.local(l),
1333 Stmt::Expr(e, _) => {
1334 let v = self.expr_stmt(e)?;
1335 if let Some(v) = v {
1336 // A bare expression with a value must be discarded in Nim.
1337 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1338 let code = v.code.clone();
1339 if needs {
1340 self.line(&format!("discard {code}"));
1341 } else if !code.is_empty() {
1342 self.line(&code);
1343 }
1344 }
1345 Ok(())
1346 }
1347 Stmt::Item(i) => self.item(i),
1348 Stmt::Macro(m) => {
1349 let line = self.macro_call(&m.mac)?;
1350 self.line(&line);
1351 Ok(())
1352 }
1353 }
1354 }
1355
1356 fn local(&mut self, l: &Local) -> Result<(), String> {
1357 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
1358 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
1359 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1360 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), Some(self.map_ty(&t.ty)?)),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1361 _ => return Err("only `let <ident>` bindings are supported".into()),
1362 },
1363 Pat::Wild(_) => ("_".into(), false, None),
1364 _ => return Err("destructuring `let` is not implemented yet".into()),
1365 };
1366
1367 let Some(init) = &l.init else {
1368 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
1369 // not. Rust's own rules make reading it before assignment illegal,
1370 // so the two agree on every program rustc accepts.
1371 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
1372 let t = t.owned();
1373 self.line(&format!("var {}: {}", ident(&name), t.render()));
1374 self.bind(&name, t);
1375 return Ok(());
1376 };
1377 if init.diverge.is_some() {
1378 return Err("`let ... else` is not implemented yet".into());
1379 }
1380
1381 if !expressible(&init.expr) && name != "_" {
1382 // The initialiser is an `if`/`match` whose arms are statements.
1383 // Declare first, then let each arm assign into the binding.
1384 let t = ann
1385 .clone()
1386 .ok_or_else(|| {
1387 format!(
1388 "`let {name} = match/if ...` needs a type annotation: \
1389 its arms are statements, so the binding must be \
1390 declared before they run"
1391 )
1392 })?
1393 .owned();
1394 self.line(&format!("var {}: {}", ident(&name), t.render()));
1395 self.bind(&name, t.clone());
1396 let target = ident(&name);
1397 return self.assign_from(&init.expr, &target, Some(&t));
1398 }
1399
1400 let v = self.expr_at(&init.expr, ann.as_ref())?;
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 18h ago1401
1402 // `let s = &buf[..n]` binds a view of a place that is already in
1403 // scope. Nim's borrow checker will not let a `let` borrow out of a
1404 // local, and there is nothing to materialise anyway -- a view is a
1405 // reference. Binding it as an alias substitutes the same expression at
1406 // each use, which re-evaluates nothing because the initialiser is a
1407 // place expression with no side effects.
1408 if v.window.is_none()
1409 && matches!(v.ty, Some(Nim::OpenArray(_)))
1410 && is_pure_place(&init.expr)
1411 {
1412 let t = v.ty.clone().unwrap();
1413 let elem = match &t {
1414 Nim::OpenArray(e) => Some((**e).clone()),
1415 _ => None,
1416 };
1417 self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) });
1418 let _ = elem;
1419 return Ok(());
1420 }
1421
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1422 if let Some(w) = v.window.clone() {
1423 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
1424 // view into the caller's buffer. Copying it into a `seq` would
1425 // still print the right bytes but would stop writes reaching the
1426 // caller, so it is bound as an alias.
1427 if v.guard.is_some() && v.guard_err.is_some() {
1428 return Err(format!(
1429 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
1430 which Nim cannot represent; apply `?` or `unwrap()` to it \
1431 in the same expression"
1432 ));
1433 }
1434 self.bind_alias(&name, w);
1435 return Ok(());
1436 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago1437 // A `let` binding a borrow keeps the view: `let res = encode(..)?`
1438 // names the caller's buffer, and copying it into a `seq` would still
1439 // print the right bytes while silently breaking the aliasing.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1440 let t = match (ann, &v.ty) {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago1441 (Some(a), _) => a.unvar(),
1442 (None, Some(t)) => t.clone().unvar(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1443 (None, None) => {
1444 return Err(format!(
1445 "cannot infer the type of `let {name}`; annotate it — \
1446 guessing here would change integer width, and with it the \
1447 meaning of any arithmetic on `{name}`"
1448 ))
1449 }
1450 };
1451
1452 if name == "_" {
1453 let code = v.code.clone();
1454 self.line(&format!("discard {code}"));
1455 return Ok(());
1456 }
1457 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
1458 // works in both, so a re-`let` of the same name needs no rename.
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 18h ago1459 //
1460 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
1461 // Rust may write through it, and Nim only accepts a `var` where a
1462 // `var` parameter is wanted, so the binding has to be one.
1463 let mutable = mutable || is_mut_borrow(&init.expr);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1464 let kw = if mutable { "var" } else { "let" };
1465 let line = format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code);
1466 self.line(&line);
1467 self.bind(&name, t);
1468 Ok(())
1469 }
1470
1471 /// Expressions that are statements in Rust and statements in Nim too
1472 /// (control flow). Returns `None` when it emitted lines itself.
1473 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
1474 match e {
1475 Expr::If(_) => {
1476 self.if_stmt(e)?;
1477 Ok(None)
1478 }
1479 Expr::While(w) => {
1480 if w.label.is_some() {
1481 return Err("loop labels are not implemented yet".into());
1482 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1483 self.in_loop_cond = true;
1484 let c = self.expr(&w.cond);
1485 self.in_loop_cond = false;
1486 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1487 self.line(&format!("while {}:", c.code));
1488 let saved = self.target.take();
1489 self.nested_block(&w.body)?;
1490 self.target = saved;
1491 Ok(None)
1492 }
1493 Expr::Loop(l) => {
1494 if l.label.is_some() {
1495 return Err("loop labels are not implemented yet".into());
1496 }
1497 self.line("while true:");
1498 let saved = self.target.take();
1499 self.nested_block(&l.body)?;
1500 self.target = saved;
1501 Ok(None)
1502 }
1503 Expr::ForLoop(f) => {
1504 self.for_loop(f)?;
1505 Ok(None)
1506 }
1507 Expr::Block(b) => {
1508 if b.label.is_some() {
1509 return Err("block labels are not implemented yet".into());
1510 }
1511 self.line("block:");
1512 self.nested_block(&b.block)?;
1513 Ok(None)
1514 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1515 Expr::Unsafe(u) => {
1516 // Transparent in statement position too, for the same reason.
1517 self.nested_block_flat(&u.block)?;
1518 Ok(None)
1519 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1520 Expr::Match(_) => {
1521 self.match_stmt(e)?;
1522 Ok(None)
1523 }
1524 Expr::Return(r) => {
1525 match &r.expr {
1526 Some(e) => {
1527 let want = self.ret.clone();
1528 let v = self.expr_at(e, want.as_ref())?;
1529 self.line(&format!("return {}", v.code));
1530 }
1531 None => self.line("return"),
1532 }
1533 Ok(None)
1534 }
1535 Expr::Break(b) => {
1536 if b.expr.is_some() || b.label.is_some() {
1537 return Err("`break` with a value or a label is not implemented yet".into());
1538 }
1539 self.line("break");
1540 Ok(None)
1541 }
1542 Expr::Continue(c) => {
1543 if c.label.is_some() {
1544 return Err("labelled `continue` is not implemented yet".into());
1545 }
1546 self.line("continue");
1547 Ok(None)
1548 }
1549 Expr::Assign(a) => {
1550 let lhs = self.expr(&a.left)?;
1551 if !expressible(&a.right) {
1552 let target = lhs.code.clone();
1553 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
1554 }
1555 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
1556 self.line(&format!("{} = {}", lhs.code, rhs.code));
1557 Ok(None)
1558 }
1559 Expr::Binary(b) if is_compound(&b.op) => {
1560 let lhs = self.expr(&b.left)?;
1561 // `i += 1` must widen the literal to `i`'s type, not to the
1562 // i32 an unconstrained Rust literal would default to.
1563 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
1564 let op = self.bin_op(&b.op, &lhs, &rhs)?;
1565 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
1566 // both languages, so the expanded form is always correct.
1567 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
1568 Ok(None)
1569 }
1570 Expr::Macro(m) => {
1571 let line = self.macro_call(&m.mac)?;
1572 self.line(&line);
1573 Ok(None)
1574 }
1575 _ => Ok(Some(self.expr(e)?)),
1576 }
1577 }
1578
1579 /// Lower `e` in statement position, assigning each arm's value to
1580 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
1581 /// the trip when their arms are too big for a Nim `if`-expression.
1582 fn assign_from(
1583 &mut self,
1584 e: &Expr,
1585 target: &str,
1586 expect: Option<&Nim>,
1587 ) -> Result<(), String> {
1588 let saved = self.target.replace((target.to_string(), expect.cloned()));
1589 let r = match e {
1590 Expr::If(_) => self.if_stmt(e),
1591 Expr::Match(_) => self.match_stmt(e),
1592 other => {
1593 let v = self.expr_at(other, expect)?;
1594 self.line(&format!("{} = {}", target, v.code));
1595 Ok(())
1596 }
1597 };
1598 self.target = saved;
1599 r
1600 }
1601
1602 /// Emit a block's value into the active assignment target, if there is
1603 /// one, or discard it if there is not.
1604 fn emit_tail(&mut self, v: Option<Val>) {
1605 let Some(v) = v else { return };
1606 match self.target.clone() {
1607 Some((t, _)) => {
1608 let code = v.code.clone();
1609 self.line(&format!("{t} = {code}"));
1610 }
1611 None => {
1612 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1613 let code = v.code.clone();
1614 if needs {
1615 self.line(&format!("discard {code}"));
1616 } else if !code.is_empty() {
1617 self.line(&code);
1618 }
1619 }
1620 }
1621 }
1622
1623 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
1624 let Expr::If(i) = e else { unreachable!() };
1625 if let Expr::Let(_) = &*i.cond {
1626 return Err("`if let` is not implemented yet".into());
1627 }
1628 let c = self.expr(&i.cond)?;
1629 self.line(&format!("if {}:", c.code));
1630 self.nested_block(&i.then_branch)?;
1631 match &i.else_branch {
1632 None => {}
1633 Some((_, els)) => match &**els {
1634 Expr::If(_) => {
1635 // Nim needs `elif`; splice the nested `if` in as one.
1636 let mark = self.out.len();
1637 self.if_stmt(els)?;
1638 let tail = self.out.split_off(mark);
1639 let indent = " ".repeat(self.indent);
1640 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
1641 }
1642 Expr::Block(b) => {
1643 self.line("else:");
1644 self.nested_block(&b.block)?;
1645 }
1646 _ => return Err("unsupported `else` form".into()),
1647 },
1648 }
1649 Ok(())
1650 }
1651
1652 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
1653 if f.label.is_some() {
1654 return Err("loop labels are not implemented yet".into());
1655 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1656 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1657
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1658 // One index loop drives the whole chain. Rust's adaptors are lazy and
1659 // compose; resolving them to an index and binding each name to an
1660 // lvalue reproduces that without materialising anything.
1661 let i = self.fresh("Idx");
1662 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
1663 self.indent += 1;
1664 self.push_scope();
1665 let before = self.out.len();
1666
1667 self.bind_pattern(&f.pat, &it, &i)?;
1668
1669 let saved = self.target.take();
1670 if let Some(v) = self.block_body(&f.body)? {
1671 let code = v.code.clone();
1672 self.line(&format!("discard {code}"));
1673 }
1674 self.target = saved;
1675 if self.out.len() == before {
1676 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1677 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1678 self.pop_scope();
1679 self.indent -= 1;
1680 Ok(())
1681 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1682
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1683 /// Resolve a chain of iterator adaptors into a single `Iter`.
1684 ///
1685 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
1686 /// `filter`, `take_while` and friends are rejected rather than partially
1687 /// honoured: silently dropping an adaptor would change which elements the
1688 /// loop visits.
1689 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
1690 match e {
1691 Expr::Reference(r) => self.resolve_iter(&r.expr),
1692 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1693 Expr::Range(r) => {
1694 let lo = match &r.start {
1695 Some(e) => self.expr(e)?,
1696 None => return Err("a `for` over `..n` needs a start bound".into()),
1697 };
1698 let hi = match &r.end {
1699 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1700 None => {
1701 return Err("a `for` over an unbounded range would not terminate".into())
1702 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1703 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1704 let ty = lo.ty.clone().or(hi.ty.clone());
1705 Ok(Iter::Range {
1706 lo: lo.code,
1707 hi: hi.code,
1708 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
1709 ty,
1710 })
1711 }
1712 Expr::MethodCall(m) => {
1713 let name = m.method.to_string();
1714 match name.as_str() {
1715 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
1716 let mut it = self.resolve_iter(&m.receiver)?;
1717 if name == "iter_mut" {
1718 if let Iter::Elems { mutable, .. } = &mut it {
1719 *mutable = true;
1720 }
1721 }
1722 Ok(it)
1723 }
1724 "enumerate" if m.args.is_empty() => {
1725 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
1726 }
1727 "zip" if m.args.len() == 1 => {
1728 let a = self.resolve_iter(&m.receiver)?;
1729 let b = self.resolve_iter(&m.args[0])?;
1730 Ok(Iter::Zip(Box::new(a), Box::new(b)))
1731 }
1732 "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1733 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1734 let k = self.expr(&m.args[0])?;
1735 Ok(Iter::Chunks {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1736 code,
1737 base,
1738 len,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1739 k: k.code,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1740 elem,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1741 mutable: name.ends_with("_mut"),
1742 })
1743 }
1744 "windows" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1745 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1746 let k = self.expr(&m.args[0])?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1747 Ok(Iter::Windows { code, base, len, k: k.code, elem })
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1748 }
1749 other => Err(format!(
1750 "iterator adaptor `.{other}()` is not implemented; it has \
1751 no index-loop equivalent here, and dropping it would \
1752 change which elements the loop visits"
1753 )),
1754 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1755 }
1756 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1757 // A `for` binding that is itself a window iterates that window,
1758 // not the whole container it points into.
1759 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1760 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1761 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1762 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1763 Ok(Iter::Elems {
1764 len: format!("{}.len", v.code),
1765 elem: elem_of(&v.ty),
1766 code: v.code,
1767 off: "0".into(),
1768 mutable: false,
1769 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1770 }
1771 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1772 }
1773
1774 /// Bind a `for` pattern against a resolved iterator at index `i`.
1775 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
1776 match (p, it) {
1777 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
1778 self.bind_pattern(&t.elems[0], a, i)?;
1779 self.bind_pattern(&t.elems[1], b, i)
1780 }
1781 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
1782 if let Pat::Ident(id) = &t.elems[0] {
1783 let n = id.ident.to_string();
1784 // Rust's `enumerate` counts in `usize`.
1785 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
1786 self.bind(&n, Nim::Prim("uint".into()));
1787 }
1788 self.bind_pattern(&t.elems[1], inner, i)
1789 }
1790 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
1791 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
1792 ),
1793 (Pat::Wild(_), _) => Ok(()),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1794 // `for &byte in xs` — the `&` destructures the reference, which in
1795 // Nim is already the value.
1796 (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
1797 (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1798 (Pat::Ident(id), _) => {
1799 let name = id.ident.to_string();
1800 match it {
1801 Iter::Range { lo, ty, .. } => {
1802 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
1803 // The loop counts from zero; the range's own start is
1804 // added back so the binding has Rust's value and type.
1805 self.line(&format!(
1806 "let {}: {} = {}({}) + {}",
1807 ident(&name),
1808 t.render(),
1809 t.render(),
1810 i,
1811 lo
1812 ));
1813 self.bind(&name, t);
1814 Ok(())
1815 }
1816 Iter::Elems { code, off, elem, mutable, .. } => {
1817 let access = if off == "0" {
1818 format!("{}[{}]", code, i)
1819 } else {
1820 format!("{}[{} + {}]", code, off, i)
1821 };
1822 if *mutable {
1823 // An alias, not a copy: assigning through the
1824 // binding must reach the original element.
1825 self.bind_alias(
1826 &name,
1827 Alias::Value { code: access, ty: elem.clone() },
1828 );
1829 } else {
1830 let t = elem
1831 .clone()
1832 .ok_or("cannot infer the element type of this `for`")?;
1833 self.line(&format!(
1834 "let {}: {} = {}",
1835 ident(&name),
1836 t.render(),
1837 access
1838 ));
1839 self.bind(&name, t);
1840 }
1841 Ok(())
1842 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1843 Iter::Chunks { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1844 self.bind_alias(
1845 &name,
1846 Alias::Window {
1847 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1848 off: format!("({} + {} * int({}))", base, i, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1849 len: format!("int({})", k),
1850 elem: elem.clone(),
1851 },
1852 );
1853 Ok(())
1854 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1855 Iter::Windows { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1856 self.bind_alias(
1857 &name,
1858 Alias::Window {
1859 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1860 off: format!("({} + {})", base, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1861 len: format!("int({})", k),
1862 elem: elem.clone(),
1863 },
1864 );
1865 Ok(())
1866 }
1867 // Handled above: a zip or enumerate needs a tuple pattern,
1868 // and binding one name to the pair is not supported.
1869 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
1870 }
1871 }
1872 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1873 }
1874 }
1875
1876 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
1877 let Expr::Match(m) = e else { unreachable!() };
1878 let scrut = self.expr(&m.expr)?;
1879 let t = scrut
1880 .ty
1881 .clone()
1882 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1883 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1884 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1885
1886 // A `match` whose arms neither bind nor guard is a Nim `case`, which
1887 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
1888 // an if/elif chain, because Nim's `case` cannot destructure.
1889 let plain = m.arms.iter().all(|a| {
1890 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
1891 });
1892 if plain {
1893 self.match_case(m, &name, &t)
1894 } else {
1895 self.match_chain(m, &name, &t)
1896 }
1897 }
1898
1899 fn match_case(
1900 &mut self,
1901 m: &syn::ExprMatch,
1902 name: &str,
1903 t: &Nim,
1904 ) -> Result<(), String> {
1905 // A variant object is discriminated by its `kind` field.
1906 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
1907 self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1908
1909 let mut saw_wild = false;
1910 for arm in &m.arms {
1911 match &arm.pat {
1912 Pat::Wild(_) => {
1913 saw_wild = true;
1914 self.line("else:");
1915 }
1916 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1917 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1918 self.line(&format!("of {}:", labels.join(", ")));
1919 }
1920 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1921 self.arm_body(&arm.body)?;
1922 }
1923 if !saw_wild && !self.case_is_total(t, m) {
1924 // Rust checked exhaustiveness already, but Nim cannot always see
1925 // it -- an integer `case` needs every value covered -- so make the
1926 // unreachable arm explicit rather than leave a compile error.
1927 self.line("else:");
1928 self.line(" rsPanic(\"unreachable match arm\")");
1929 }
1930 Ok(())
1931 }
1932
1933 /// Whether a Nim `case` over this type is already total, in which case
1934 /// adding an `else` would be a compile error rather than a safety net.
1935 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
1936 let Nim::Named(n, _) = t else { return false };
1937 let Some(def) = self.enums.get(n) else { return false };
1938 def.variants.len() == m.arms.len()
1939 }
1940
1941 /// The if/elif form, for arms that bind or destructure.
1942 fn match_chain(
1943 &mut self,
1944 m: &syn::ExprMatch,
1945 name: &str,
1946 t: &Nim,
1947 ) -> Result<(), String> {
1948 let mut first = true;
1949 let mut closed = false;
1950 for arm in &m.arms {
1951 let (pat, guard) = match &arm.pat {
1952 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
1953 p => (p, None),
1954 };
1955 if guard.is_some() && binds(pat) {
1956 return Err("a `match` guard on a binding pattern is not \
1957 implemented yet"
1958 .into());
1959 }
1960 let test = self.pat_test(pat, name, t)?;
1961 let test = match (test, guard) {
1962 (Some(t), Some(g)) => {
1963 let g = self.expr(g)?;
1964 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1965 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1966 (None, Some(g)) => Some(self.expr(g)?.code),
1967 (t, None) => t,
1968 };
1969 match test {
1970 Some(test) => {
1971 self.line(&format!(
1972 "{} {}:",
1973 if first { "if" } else { "elif" },
1974 test
1975 ));
1976 first = false;
1977 }
1978 None => {
1979 // An irrefutable pattern: everything left falls here.
1980 if first {
1981 self.line("block:");
1982 } else {
1983 self.line("else:");
1984 }
1985 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1986 }
1987 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1988 self.indent += 1;
1989 self.push_scope();
1990 let before = self.out.len();
1991 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1992 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1993 self.arm_body_at(&arm.body, before)?;
1994 self.pop_scope();
1995 if closed {
1996 break;
1997 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1998 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1999 if !closed {
2000 // Rust proved this unreachable; Nim cannot see that, and leaving
2001 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2002 self.line("else:");
2003 self.line(" rsPanic(\"unreachable match arm\")");
2004 }
2005 Ok(())
2006 }
2007
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2008 /// The condition that selects this arm, or `None` if it always matches.
2009 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
2010 Ok(match p {
2011 Pat::Wild(_) => None,
2012 Pat::Ident(i) if i.subpat.is_none() => None,
2013 Pat::Or(o) => {
2014 let mut parts = Vec::new();
2015 for c in &o.cases {
2016 match self.pat_test(c, name, t)? {
2017 Some(x) => parts.push(x),
2018 None => return Ok(None),
2019 }
2020 }
2021 Some(format!("({})", parts.join(" or ")))
2022 }
2023 Pat::Lit(_) | Pat::Range(_) => {
2024 let labels = self.pat_labels(p, Some(t))?;
2025 Some(match p {
2026 Pat::Range(_) => format!("({} in {})", name, labels[0]),
2027 _ => format!("({} == {})", name, labels[0]),
2028 })
2029 }
2030 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
2031 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
2032 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
2033 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
2034 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
2035 _ => return Err("unsupported `match` pattern".into()),
2036 })
2037 }
2038
2039 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
2040 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
2041 let last = path_name(path);
2042 match last.as_str() {
2043 "Ok" => return Ok(format!("{name}.ok")),
2044 "Err" => return Ok(format!("(not {name}.ok)")),
2045 "Some" => return Ok(format!("{name}.has")),
2046 "None" => return Ok(format!("(not {name}.has)")),
2047 _ => {}
2048 }
2049 let Some((def, v)) = self.resolve_variant(path) else {
2050 return Err(format!(
2051 "`{last}` in a pattern is not a known enum variant; if it names \
2052 an enum declared in another module, that is not implemented yet"
2053 ));
2054 };
2055 if let Nim::Named(n, _) = t {
2056 if *n != def.name {
2057 return Err(format!(
2058 "pattern `{}::{}` does not match the scrutinee type `{}`",
2059 def.name, v, n
2060 ));
2061 }
2062 }
2063 Ok(if def.simple {
2064 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
2065 } else {
2066 format!("({}.kind == {})", name, def.kind_ident(&v))
2067 })
2068 }
2069
2070 /// Emit the `let`s that a pattern's bindings introduce.
2071 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
2072 match p {
2073 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
2074 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
2075 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
2076 Pat::Ident(i) if i.subpat.is_none() => {
2077 let b = i.ident.to_string();
2078 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
2079 self.bind(&b, t.clone());
2080 Ok(())
2081 }
2082 Pat::TupleStruct(ts) => {
2083 let fields = self.variant_fields(&ts.path, t)?;
2084 for (i, sub) in ts.elems.iter().enumerate() {
2085 let Some((fname, fty)) = fields.get(i) else {
2086 return Err(format!(
2087 "pattern binds {} field(s) but the variant has {}",
2088 ts.elems.len(),
2089 fields.len()
2090 ));
2091 };
2092 let access = format!("{}.{}", name, ident(fname));
2093 self.pat_bind(sub, &access, fty)?;
2094 }
2095 Ok(())
2096 }
2097 Pat::Struct(st) => {
2098 let fields = self.variant_fields(&st.path, t)?;
2099 for f in &st.fields {
2100 let syn::Member::Named(m) = &f.member else {
2101 return Err("unsupported struct pattern field".into());
2102 };
2103 let m = m.to_string();
2104 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
2105 return Err(format!("unknown field `{m}` in pattern"));
2106 };
2107 let access = format!("{}.{}", name, ident(fname));
2108 self.pat_bind(&f.pat, &access, fty)?;
2109 }
2110 Ok(())
2111 }
2112 _ => Err("unsupported `match` pattern".into()),
2113 }
2114 }
2115
2116 /// The payload fields a variant pattern destructures.
2117 fn variant_fields(
2118 &self,
2119 path: &syn::Path,
2120 t: &Nim,
2121 ) -> Result<Vec<(String, Nim)>, String> {
2122 let last = path_name(path);
2123 // `Ok`/`Err`/`Some` read the prelude's own field names.
2124 if let Nim::Named(n, a) = t {
2125 match (n.as_str(), last.as_str()) {
2126 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
2127 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
2128 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
2129 _ => {}
2130 }
2131 }
2132 let Some((def, v)) = self.resolve_variant(path) else {
2133 return Err(format!("`{last}` is not a known enum variant"));
2134 };
2135 Ok(def.get(&v).map(|v| v.fields.clone()).unwrap_or_default())
2136 }
2137
2138 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
2139 self.indent += 1;
2140 let before = self.out.len();
2141 self.indent -= 1;
2142 self.arm_body_at(body, before)
2143 }
2144
2145 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
2146 match body {
2147 Expr::Block(b) => self.nested_block(&b.block)?,
2148 other => {
2149 self.indent += 1;
2150 // An arm's value is the `match`'s value, so it is typed by
2151 // whatever the `match` is being assigned to -- without which
2152 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
2153 let want = self.target.clone().and_then(|(_, t)| t);
2154 let v = match (want, expressible(other)) {
2155 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
2156 _ => self.expr_stmt(other)?,
2157 };
2158 self.emit_tail(v);
2159 self.indent -= 1;
2160 }
2161 }
2162 if self.out.len() == before {
2163 self.indent += 1;
2164 self.line("discard");
2165 self.indent -= 1;
2166 }
2167 Ok(())
2168 }
2169
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2170 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
2171 match p {
2172 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
2173 Pat::Or(o) => {
2174 let mut out = Vec::new();
2175 for p in &o.cases {
2176 out.extend(self.pat_labels(p, expect)?);
2177 }
2178 Ok(out)
2179 }
2180 Pat::Range(r) => {
2181 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
2182 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
2183 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
2184 let op = match r.limits {
2185 syn::RangeLimits::HalfOpen(_) => "..<",
2186 syn::RangeLimits::Closed(_) => "..",
2187 };
2188 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
2189 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2190 Pat::Path(pp) => {
2191 if let Some((def, v)) = self.resolve_variant(&pp.path) {
2192 return Ok(vec![if def.simple {
2193 format!("{}.{}", ident(&def.name), ident(&v))
2194 } else {
2195 def.kind_ident(&v)
2196 }]);
2197 }
2198 Ok(vec![ident(&path_name(&pp.path))])
2199 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2200 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2201 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2202 .into()),
2203 }
2204 }
2205
2206 // --------------------------------------------------------- expressions
2207
2208 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
2209 self.expr_at(e, None)
2210 }
2211
2212 /// Lower `e`, with the type the surrounding code expects of it.
2213 ///
2214 /// Rust infers an unsuffixed integer literal's type from its context and
2215 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
2216 /// expected type down to the literal is what makes `let x: u8 = 255` and
2217 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
2218 /// widths silently diverge, which is exactly the class of bug this
2219 /// project refuses to ship.
2220 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
2221 match e {
2222 Expr::Lit(l) => self.lit_at(&l.lit, expect),
2223 Expr::Path(p) => {
2224 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2225 if name == "None" {
2226 return Ok(Val::new(self.none_of(expect), expect.cloned()));
2227 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2228 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
2229 // declared here. In Nim that is a constructor call.
2230 if p.path.segments.len() > 1 {
2231 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
2232 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
2233 if n == "FmtError" {
2234 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
2235 }
2236 }
2237 }
2238 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
2239 return Ok(Val::new(
2240 format!("{}()", ident(&name)),
2241 Some(Nim::Named(name.clone(), vec![])),
2242 ));
2243 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2244 // A unit enum variant used as a value: `Error::InvalidLength`.
2245 if let Some((def, v)) = self.resolve_variant(&p.path) {
2246 let ty = Some(Nim::Named(def.name.clone(), vec![]));
2247 return Ok(if def.simple {
2248 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), ty)
2249 } else {
2250 Val::new(format!("{}()", def.ctor_ident(&v)), ty)
2251 });
2252 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2253 // A `for` binding that stands for an element of the container
2254 // it came from: using it must read (and assigning through it
2255 // must write) that element, not a copy.
2256 if let Some(a) = self.lookup_alias(&name) {
2257 return Ok(match a {
2258 Alias::Value { code, ty } => Val::new(code, ty),
2259 // A window *is* a slice; as a value it is the view it
2260 // denotes, which is what Rust's `&[T]` means too.
2261 Alias::Window { code, off, len, elem } => Val::new(
2262 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
2263 elem.map(|e| Nim::OpenArray(Box::new(e))),
2264 ),
2265 });
2266 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2267 if let Some(t) = self.lookup(&name) {
2268 return Ok(Val::new(ident(&name), Some(t)));
2269 }
2270 // A top-level function used as a value, e.g. passed to a
2271 // parameter of `impl Fn(..)` type.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2272 if let Some(k) = self.resolve_fn(&p.path) {
2273 let sig = &self.fns[&k];
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2274 let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone()));
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2275 return Ok(Val::new(self.fn_name(&k.0, &k.1), Some(t)));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2276 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2277 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2278 }
2279 Expr::Paren(p) => {
2280 let v = self.expr_at(&p.expr, expect)?;
2281 Ok(Val::new(format!("({})", v.code), v.ty))
2282 }
2283 Expr::Group(g) => self.expr_at(&g.expr, expect),
2284 // `&x` is a value in Nim; `&mut x` in an argument position binds to
2285 // a `var` parameter, which is also just `x` at the call site.
2286 Expr::Reference(r) => self.expr_at(&r.expr, expect),
2287 Expr::Unary(u) => self.unary(u, expect),
2288 Expr::Binary(b) => self.binary(b, expect),
2289 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2290 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
2291 let Expr::Range(r) = &*i.index else { unreachable!() };
2292 let base = self.expr(&i.expr)?;
2293 let lo = match &r.start {
2294 Some(e) => format!("int({})", self.expr(e)?.code),
2295 None => "0".into(),
2296 };
2297 // Nim's `toOpenArray` takes an inclusive upper bound.
2298 let hi = match (&r.end, r.limits) {
2299 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2300 format!("int({}) - 1", self.expr(e)?.code)
2301 }
2302 (Some(e), syn::RangeLimits::Closed(_)) => {
2303 format!("int({})", self.expr(e)?.code)
2304 }
2305 (None, _) => format!("{}.len - 1", base.code),
2306 };
2307 let elem = elem_of(&base.ty)
2308 .ok_or("cannot infer the element type of this slice")?;
2309 Ok(Val::new(
2310 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
2311 Some(Nim::OpenArray(Box::new(elem))),
2312 ))
2313 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2314 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2315 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
2316 let idx = self.expr(&i.index)?;
2317 return Ok(Val::new(
2318 format!("{}[{} + int({})]", code, off, idx.code),
2319 elem,
2320 ));
2321 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2322 let base = self.expr(&i.expr)?;
2323 let idx = self.expr(&i.index)?;
2324 // Rust indexes with usize; Nim wants an `int`, and a `uint`
2325 // index is a type error there rather than a silent conversion.
2326 let idx_code = match &idx.ty {
2327 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
2328 _ => idx.code.clone(),
2329 };
2330 let elem = match base.ty.clone() {
2331 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
2332 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
2333 _ => None,
2334 };
2335 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
2336 }
2337 Expr::Field(f) => {
2338 let base = self.expr(&f.base)?;
2339 let name = match &f.member {
2340 syn::Member::Named(n) => n.to_string(),
2341 syn::Member::Unnamed(i) => format!("f{}", i.index),
2342 };
2343 let t = match &base.ty {
2344 Some(Nim::Named(s, _)) => self
2345 .structs
2346 .get(s)
2347 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
2348 .map(|(_, t)| t.clone()),
2349 _ => None,
2350 };
2351 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
2352 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2353 // `unsafe` is a permission marker, not a semantic change: it does
2354 // not alter what the enclosed operations mean. So the block is
2355 // transparent here, and each operation inside still goes through
2356 // the ordinary lowering -- and is still rejected if it has no
2357 // faithful mapping.
2358 Expr::Unsafe(u) => match single_expr(&u.block) {
2359 Some(e) => self.expr_at(e, expect),
2360 None => Err("an `unsafe` block used as a value must be a single \
2361 expression"
2362 .into()),
2363 },
2364 Expr::Closure(c) => self.closure(c, expect),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2365 Expr::Try(t) => self.try_op(t),
2366 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2367 Expr::MethodCall(m) => self.method(m, expect),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2368 Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
2369 // `vec![..]`'s elements take their type from the annotation on
2370 // the binding, exactly as Rust's would.
2371 let want = match expect {
2372 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
2373 _ => None,
2374 };
2375 let saved = std::mem::replace(&mut self.vec_expect, want.clone());
2376 let code = self.macro_call(&m.mac);
2377 self.vec_expect = saved;
2378 let code = code?;
2379 let ty = match want {
2380 Some(e) => Some(Nim::Seq(Box::new(e))),
2381 None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
2382 };
2383 Ok(Val::new(code, ty))
2384 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2385 Expr::Macro(m) => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago2386 let is_write = matches!(path_name(&m.mac.path).as_str(), "write" | "writeln");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2387 let code = self.macro_call(&m.mac)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago2388 // A formatter write is a statement that appends, not a value.
2389 let ty = if is_write { Some(Nim::Unit) } else { None };
2390 Ok(Val::new(code, ty))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2391 }
2392 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2393 if s.rest.is_some() {
2394 return Err("struct update syntax `..rest` is not implemented yet".into());
2395 }
2396 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
2397 // which is constructed positionally in Nim.
2398 if let Some((def, v)) = self.resolve_variant(&s.path) {
2399 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2400 let mut args = vec![String::new(); fields.len()];
2401 for f in &s.fields {
2402 let syn::Member::Named(m) = &f.member else {
2403 return Err("unsupported enum variant field".into());
2404 };
2405 let want = format!("{}_{}", v, m);
2406 let i = fields
2407 .iter()
2408 .position(|(n, _)| *n == want)
2409 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
2410 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
2411 }
2412 if let Some(i) = args.iter().position(|a| a.is_empty()) {
2413 return Err(format!(
2414 "`{}::{}` is missing field `{}`",
2415 def.name, v, fields[i].0
2416 ));
2417 }
2418 return Ok(Val::new(
2419 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
2420 Some(Nim::Named(def.name.clone(), vec![])),
2421 ));
2422 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2423 let name = path_name(&s.path);
2424 let mut parts = Vec::new();
2425 for f in &s.fields {
2426 let fname = match &f.member {
2427 syn::Member::Named(n) => n.to_string(),
2428 syn::Member::Unnamed(i) => format!("f{}", i.index),
2429 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2430 let want = self
2431 .structs
2432 .get(&name)
2433 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
2434 .map(|(_, t)| t.clone());
2435 let v = self.expr_at(&f.expr, want.as_ref())?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2436 parts.push(format!("{}: {}", ident(&fname), v.code));
2437 }
2438 Ok(Val::new(
2439 format!("{}({})", ident(&name), parts.join(", ")),
2440 Some(Nim::Named(name, vec![])),
2441 ))
2442 }
2443 Expr::Array(a) => {
2444 let mut parts = Vec::new();
2445 let mut elem = match expect {
2446 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
2447 Some((**t).clone())
2448 }
2449 _ => None,
2450 };
2451 for e in &a.elems {
2452 let want = elem.clone();
2453 let v = self.expr_at(e, want.as_ref())?;
2454 elem = elem.or(v.ty.clone());
2455 parts.push(v.code);
2456 }
2457 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
2458 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
2459 }
2460 Expr::Repeat(r) => {
2461 let v = self.expr(&r.expr)?;
2462 let n = self.expr(&r.len)?;
2463 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
2464 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
2465 }
2466 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
2467 Expr::Tuple(t) => {
2468 let mut parts = Vec::new();
2469 let mut tys = Vec::new();
2470 for e in &t.elems {
2471 let v = self.expr(e)?;
2472 tys.push(v.ty.clone());
2473 parts.push(v.code);
2474 }
2475 let ty = tys
2476 .iter()
2477 .cloned()
2478 .collect::<Option<Vec<_>>>()
2479 .map(Nim::Tuple);
2480 Ok(Val::new(format!("({})", parts.join(", ")), ty))
2481 }
2482 // `if` and `match` are expressions in both languages, but only
2483 // when every arm is itself a single expression.
2484 Expr::If(i) => self.if_expr(i, expect),
2485 Expr::Block(b) if b.block.stmts.len() == 1 => {
2486 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
2487 self.expr_at(e, expect)
2488 } else {
2489 Err("block expression with statements in value position is not implemented yet".into())
2490 }
2491 }
2492 other => Err(format!(
2493 "unsupported expression in value position: {}",
2494 expr_kind(other)
2495 )),
2496 }
2497 }
2498
2499 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
2500 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
2501 return Err(
2502 "an `if` used as a value must have an `else` and single-expression arms".into(),
2503 );
2504 };
2505 let c = self.expr(&i.cond)?;
2506 let t = self.expr_at(then, expect)?;
2507 let want = expect.cloned().or_else(|| t.ty.clone());
2508 let e = match &**els {
2509 Expr::Block(b) => match single_expr(&b.block) {
2510 Some(x) => self.expr_at(x, want.as_ref())?,
2511 None => return Err("an `if` used as a value must have single-expression arms".into()),
2512 },
2513 other => self.expr_at(other, want.as_ref())?,
2514 };
2515 let ty = t.ty.clone().or(e.ty.clone());
2516 Ok(Val::new(
2517 format!("(if {}: {} else: {})", c.code, t.code, e.code),
2518 ty,
2519 ))
2520 }
2521
2522 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
2523 match l {
2524 Lit::Int(i) => {
2525 let suffix = i.suffix();
2526 if let Some(why) = ty::rejected(suffix) {
2527 return Err(format!("integer literal `{}`: {}", i, why));
2528 }
2529 let digits = i.base10_digits().to_string();
2530 // Rust's default for an unconstrained integer literal is i32.
2531 // Nim's is `int` (64-bit). Making the width explicit is what
2532 // keeps overflow behaviour the same on both sides.
2533 let t = if suffix.is_empty() {
2534 match expect {
2535 Some(t) if t.is_integer() => t.clone(),
2536 // Rust's fallback for an otherwise-unconstrained
2537 // integer literal.
2538 _ => Nim::Prim("int32".into()),
2539 }
2540 } else {
2541 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
2542 };
2543 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
2544 }
2545 Lit::Float(f) => {
2546 let t = match f.suffix() {
2547 "" => match expect {
2548 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
2549 _ => Nim::Prim("float64".into()),
2550 },
2551 "f64" => Nim::Prim("float64".into()),
2552 "f32" => Nim::Prim("float32".into()),
2553 s => return Err(format!("unknown float suffix `{s}`")),
2554 };
2555 let d = f.base10_digits();
2556 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
2557 Ok(Val::new(d, Some(t)))
2558 }
2559 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
2560 Lit::Str(s) => Ok(Val::new(
2561 fmt::nim_str(&s.value()),
2562 Some(Nim::Prim("string".into())),
2563 )),
2564 Lit::Char(c) => Ok(Val::new(
2565 format!("Rune({})", c.value() as u32),
2566 Some(Nim::Prim("Rune".into())),
2567 )),
2568 Lit::Byte(b) => Ok(Val::new(
2569 format!("{}'u8", b.value()),
2570 Some(Nim::Prim("uint8".into())),
2571 )),
2572 Lit::ByteStr(b) => {
2573 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
2574 Ok(Val::new(
2575 format!("@[{}]", bytes.join(", ")),
2576 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
2577 ))
2578 }
2579 other => Err(format!("unsupported literal: {other:?}")),
2580 }
2581 }
2582
2583 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
2584 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
2585 // the positive half of the range before the negation runs. Folding the
2586 // sign into the literal keeps `i8::MIN` and friends expressible.
2587 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
2588 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
2589 let v = self.lit_at(&l.lit, expect)?;
2590 return Ok(Val::new(format!("-{}", v.code), v.ty));
2591 }
2592 }
2593 let v = self.expr_at(&u.expr, expect)?;
2594 match u.op {
2595 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
2596 // Rust's `!` is logical on bool and bitwise-complement on integers.
2597 // Nim spells those `not` and `not` as well, so one mapping covers
2598 // both — but only because Nim overloads `not` the same way.
2599 UnOp::Not(_) => Ok(Val::new(format!("(not {})", v.code), v.ty)),
2600 UnOp::Deref(_) => Ok(v),
2601 _ => Err("unsupported unary operator".into()),
2602 }
2603 }
2604
2605 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
2606 // A comparison's operands are unrelated to the `bool` it produces, so
2607 // the outer expectation is not passed through to them.
2608 let down = match b.op {
2609 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
2610 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
2611 _ => expect,
2612 };
2613 let mut l = self.expr_at(&b.left, down)?;
2614 // Rust unifies the two operand types; propagating whichever side is
2615 // known to the other reproduces that, and disagreement then surfaces
2616 // as a Nim type error rather than as a silent width change.
2617 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
2618 if l.ty.is_none() && r.ty.is_some() {
2619 l = self.expr_at(&b.left, r.ty.as_ref())?;
2620 }
2621 let r = std::mem::replace(&mut r, Val::untyped(""));
2622 let op = self.bin_op(&b.op, &l, &r)?;
2623 let ty = match b.op {
2624 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
2625 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
2626 // Rust's shift takes its result type from the *left* operand, and
2627 // the right may be a different width entirely.
2628 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
2629 _ => l.ty.clone().or(r.ty.clone()),
2630 };
2631 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
2632 }
2633
2634 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
2635 Ok(match op {
2636 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
2637 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
2638 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
2639 BinOp::Div(_) | BinOp::DivAssign(_) => {
2640 // Nim spells integer division `div`. Both languages truncate
2641 // toward zero, so once the right operator is chosen the
2642 // semantics match, including for negative operands.
2643 let t = l.ty.clone().or(r.ty.clone()).ok_or(
2644 "cannot tell integer from float division here; annotate the operands",
2645 )?;
2646 if t.is_integer() { "div" } else { "/" }
2647 }
2648 BinOp::Rem(_) | BinOp::RemAssign(_) => {
2649 let t = l.ty.clone().or(r.ty.clone()).ok_or(
2650 "cannot tell integer from float remainder here; annotate the operands",
2651 )?;
2652 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
2653 }
2654 BinOp::And(_) => "and",
2655 BinOp::Or(_) => "or",
2656 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
2657 // bools, exactly as Rust's `&`/`|`/`^` are.
2658 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
2659 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
2660 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
2661 // Settled empirically: Nim's `shr` on a signed integer is
2662 // arithmetic, matching Rust. See DESIGN.md.
2663 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
2664 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
2665 BinOp::Eq(_) => "==",
2666 BinOp::Ne(_) => "!=",
2667 BinOp::Lt(_) => "<",
2668 BinOp::Le(_) => "<=",
2669 BinOp::Gt(_) => ">",
2670 BinOp::Ge(_) => ">=",
2671 other => return Err(format!("unsupported binary operator {other:?}")),
2672 })
2673 }
2674
2675 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
2676 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2677 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2678 let from = v.ty.clone().ok_or_else(|| {
2679 format!(
2680 "cannot lower `as {}`: the source type is unknown, and `as` \
2681 truncates, so the source width decides the result",
2682 to.render()
2683 )
2684 })?;
2685
2686 let code = match (&from, &to) {
2687 (f, t) if f.is_integer() && t.is_integer() => {
2688 // Rust's `as` between integers is a pure bit-width truncation
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 18h ago2689 // or sign-extension, never a range check. `cast` says exactly
2690 // that. (Nim's `T(x)` turns out to truncate here as well --
2691 // see DESIGN.md item 5 -- but `cast` is the spelling that
2692 // means it rather than the one that happens to agree.)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2693 format!("cast[{}]({})", t.render(), v.code)
2694 }
2695 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
2696 format!("{}({})", p, v.code)
2697 }
2698 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
2699 format!("{}(ord({}))", t.render(), v.code)
2700 }
2701 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
2702 format!("cast[{}](int32({}))", t.render(), v.code)
2703 }
2704 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
2705 format!("Rune(int32({}))", v.code)
2706 }
2707 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
2708 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
2709 // Rust saturates float->int casts; Nim rounds and range-errors.
2710 // Not the same operation, so it is refused rather than mapped.
2711 return Err(format!(
2712 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
2713 no faithful mapping is implemented",
2714 t.render()
2715 ));
2716 }
2717 (f, t) => {
2718 return Err(format!(
2719 "unsupported cast from `{}` to `{}`",
2720 f.render(),
2721 t.render()
2722 ))
2723 }
2724 };
2725 Ok(Val::new(code, Some(to)))
2726 }
2727
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2728 /// Rust's `?`: return early on the error branch, otherwise yield the value.
2729 ///
2730 /// The early return is statements, not an expression, so they are emitted
2731 /// ahead of the line being built. Every caller lowers its sub-expressions
2732 /// before emitting its own line, which is what makes that ordering hold.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2733 /// The container, start offset, length and element type an expression
2734 /// denotes as a slice. A window alias contributes its own offset, so
2735 /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
2736 /// into the original buffer rather than through a rebuilt view.
2737 fn slice_parts(
2738 &mut self,
2739 e: &Expr,
2740 ) -> Result<(String, String, String, Option<Nim>), String> {
2741 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
2742 return Ok((code, off, len, elem));
2743 }
2744 let v = self.expr(e)?;
2745 let len = format!("{}.len", v.code);
2746 Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
2747 }
2748
2749 /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
2750 fn map_closure(
2751 &mut self,
2752 what: &str,
2753 recv: &Val,
2754 kind: &str,
2755 targs: &[Nim],
2756 c: &syn::ExprClosure,
2757 ) -> Result<Val, String> {
2758 if c.capture.is_some() {
2759 return Err("a `move` closure captures by value; Nim's closures \
2760 capture by reference, and the two are not the same"
2761 .into());
2762 }
2763 if c.inputs.len() != 1 {
2764 return Err(format!("`.{what}()` takes a one-argument closure"));
2765 }
2766 let pname = match &c.inputs[0] {
2767 Pat::Ident(i) => i.ident.to_string(),
2768 Pat::Wild(_) => "unused0".into(),
2769 _ => return Err("only plain identifier closure parameters are supported".into()),
2770 };
2771
2772 let is_opt = kind == "Option";
2773 let tmp = self.fresh("Map");
2774 let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
2775 self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
2776
2777 let body = match &*c.body {
2778 Expr::Block(b) => single_expr(&b.block)
2779 .ok_or("a closure body with statements is not implemented yet")?,
2780 other => other,
2781 };
2782 self.push_scope();
2783 // The parameter names the payload itself, so a view stays a view.
2784 self.bind_alias(
2785 &pname,
2786 Alias::Value {
2787 code: format!("{}.val", tmp),
2788 ty: Some(targs[0].clone()),
2789 },
2790 );
2791 let v = self.expr(body)?;
2792 self.pop_scope();
2793
2794 let inner = v
2795 .ty
2796 .clone()
2797 .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
2798 // `and_then`'s closure already returns the wrapped type; `map`'s does
2799 // not and has to be re-wrapped.
2800 let (test, some_branch, none_branch, out_ty) = if is_opt {
2801 let out = if what == "map" {
2802 Nim::Named("Option".into(), vec![inner.clone()])
2803 } else {
2804 inner.clone()
2805 };
2806 let body_code = if what == "map" {
2807 format!("rsSome[{}]({})", inner.render(), v.code)
2808 } else {
2809 v.code.clone()
2810 };
2811 (
2812 format!("{}.has", tmp),
2813 body_code,
2814 format!("rsNone[{}]()", elem_arg(&out).render()),
2815 out,
2816 )
2817 } else {
2818 let e = targs[1].clone();
2819 let out = if what == "map" {
2820 Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
2821 } else {
2822 inner.clone()
2823 };
2824 let ok_ty = elem_arg(&out);
2825 let body_code = if what == "map" {
2826 format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
2827 } else {
2828 v.code.clone()
2829 };
2830 (
2831 format!("{}.ok", tmp),
2832 body_code,
2833 format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
2834 out,
2835 )
2836 };
2837 Ok(Val::new(
2838 format!("(if {}: {} else: {})", test, some_branch, none_branch),
2839 Some(out_ty),
2840 ))
2841 }
2842
2843 /// `|x| x + 1` -> a Nim anonymous proc.
2844 ///
2845 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
2846 /// A `move` closure captures by value, which is a different thing, so it
2847 /// is rejected rather than lowered to the same construct.
2848 fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
2849 if c.capture.is_some() {
2850 return Err("a `move` closure captures by value; Nim's closures \
2851 capture by reference, and the two are not the same"
2852 .into());
2853 }
2854 let want: Option<&Vec<Nim>> = match expect {
2855 Some(Nim::Proc(a, _)) => Some(a),
2856 _ => None,
2857 };
2858
2859 self.push_scope();
2860 let mut parts = Vec::new();
2861 let mut ptys = Vec::new();
2862 for (i, p) in c.inputs.iter().enumerate() {
2863 let (name, ann) = match p {
2864 Pat::Ident(id) => (id.ident.to_string(), None),
2865 Pat::Type(t) => match &*t.pat {
2866 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
2867 _ => return Err("only plain identifier closure parameters are supported".into()),
2868 },
2869 Pat::Wild(_) => (format!("unused{i}"), None),
2870 _ => return Err("only plain identifier closure parameters are supported".into()),
2871 };
2872 let t = ann
2873 .or_else(|| want.and_then(|w| w.get(i).cloned()))
2874 .ok_or_else(|| {
2875 format!(
2876 "cannot infer the type of closure parameter `{name}`; \
2877 annotate it"
2878 )
2879 })?;
2880 parts.push(format!("{}: {}", ident(&name), t.render()));
2881 self.bind(&name, t.clone());
2882 ptys.push(t);
2883 }
2884
2885 let ret_ann = match &c.output {
2886 ReturnType::Default => None,
2887 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
2888 };
2889 let body = match &*c.body {
2890 Expr::Block(b) => single_expr(&b.block)
2891 .ok_or("a closure body with statements is not implemented yet")?,
2892 other => other,
2893 };
2894 let v = self.expr_at(body, ret_ann.as_ref())?;
2895 self.pop_scope();
2896
2897 let ret = ret_ann
2898 .or_else(|| v.ty.clone())
2899 .ok_or("cannot infer a closure's return type; annotate it")?;
2900 Ok(Val::new(
2901 format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
2902 Some(Nim::Proc(ptys, Box::new(ret))),
2903 ))
2904 }
2905
2906 /// Lower a block's statements at the current indentation, without opening
2907 /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
2908 /// of its own in the generated code.
2909 fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
2910 self.push_scope();
2911 let tail = self.block_body(b)?;
2912 self.emit_tail(tail);
2913 self.pop_scope();
2914 Ok(())
2915 }
2916
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2917 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
2918 if self.in_loop_cond {
2919 return Err("`?` in a loop condition is not implemented yet: the \
2920 early-return it expands to would be evaluated once, \
2921 before the loop, rather than on each iteration"
2922 .into());
2923 }
2924 let v = self.expr(&t.expr)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago2925 if self.fmt_param.is_some() {
2926 // Writing into a string cannot fail, so `?` on a formatter write
2927 // is a no-op. `?` on anything else can fail, and `format!` panics
2928 // when a formatting impl returns an error -- so that is what the
2929 // error branch does here, with std's own message.
2930 if v.ty.as_ref() == Some(&Nim::Unit) {
2931 return Ok(v);
2932 }
2933 if let Some(Nim::Named(n, a)) = v.ty.clone() {
2934 if n == "Result" && a.len() == 2 {
2935 let tmp = self.fresh("Fmt");
2936 self.line(&format!(
2937 "let {}: {} = {}",
2938 tmp,
2939 Nim::Named(n, a.clone()).render(),
2940 v.code
2941 ));
2942 self.line(&format!("if not {}.ok:", tmp));
2943 self.line(
2944 " rsPanic(\"a formatting trait implementation returned an error\")",
2945 );
2946 return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone())));
2947 }
2948 }
2949 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2950 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
2951 // An `Option`/`Result` of a view: the check is emitted here and the
2952 // view itself survives as an alias, since it has no value form.
2953 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
2954 let err = v.guard_err.clone().ok_or(
2955 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
2956 )?;
2957 let Nim::Named(n, ra) = &ret else {
2958 return Err(format!("`?` in a function returning `{}`", ret.render()));
2959 };
2960 if n != "Result" || ra.len() != 2 {
2961 return Err(format!("`?` in a function returning `{}`", ret.render()));
2962 }
2963 self.line(&format!("if not {}:", guard));
2964 self.line(&format!(
2965 " return rsErr[{}, {}]({})",
2966 ra[0].render(),
2967 ra[1].render(),
2968 err
2969 ));
2970 let mut out = Val::new(String::new(), None);
2971 out.window = Some(w);
2972 return Ok(out);
2973 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2974 let vt = v.ty.clone().ok_or(
2975 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
2976 )?;
2977 let ret = self
2978 .ret
2979 .clone()
2980 .ok_or("`?` outside a function with a return type")?;
2981 let tmp = self.fresh("Try");
2982 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
2983
2984 match (&vt, &ret) {
2985 (Nim::Named(a, ai), Nim::Named(b, bi))
2986 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
2987 {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2988 // Rust inserts a `From::from` on the error here. Where the
2989 // types differ we call the crate's own `impl From`; we never
2990 // assume the conversion is the identity.
2991 let err = if ai[1] == bi[1] {
2992 format!("{}.err", tmp)
2993 } else {
2994 let key = (type_name(&ai[1]), type_name(&bi[1]));
2995 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
2996 format!(
2997 "`?` needs `From<{}> for {}` to convert the error, and \
2998 no such `impl` is in scope; assuming the conversion is \
2999 the identity would be a guess",
3000 key.0, key.1
3001 )
3002 })?;
3003 format!("{}({}.err)", f, tmp)
3004 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3005 self.line(&format!("if not {}.ok:", tmp));
3006 self.line(&format!(
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3007 " return rsErr[{}, {}]({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3008 bi[0].render(),
3009 bi[1].render(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3010 err
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3011 ));
3012 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3013 }
3014 (Nim::Named(a, ai), Nim::Named(b, bi))
3015 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
3016 {
3017 self.line(&format!("if not {}.has:", tmp));
3018 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
3019 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3020 }
3021 _ => Err(format!(
3022 "`?` on `{}` in a function returning `{}` is not a supported \
3023 combination",
3024 vt.render(),
3025 ret.render()
3026 )),
3027 }
3028 }
3029
3030 fn call(&mut self, c: &syn::ExprCall, expect: Option<&Nim>) -> Result<Val, String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3031 let Expr::Path(p) = &*c.func else {
3032 return Err("only calls to named functions are supported".into());
3033 };
3034 let name = path_name(&p.path);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3035 let target = self.resolve_fn(&p.path);
3036 let ptys: Vec<Nim> = target
3037 .as_ref()
3038 .and_then(|k| self.fns.get(k))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3039 .map(|s| s.params.clone())
3040 .unwrap_or_default();
3041 let mut args = Vec::new();
3042 for (i, a) in c.args.iter().enumerate() {
3043 let want = ptys.get(i).cloned();
3044 args.push(self.expr_at(a, want.as_ref())?);
3045 }
3046 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
3047
3048 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3049 // `Ok`/`Err` must name the *whole* Result type, not just the half
3050 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
3051 match name.as_str() {
3052 "Some" => {
3053 let inner = match expect {
3054 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
3055 _ => {
3056 return Err("`Some(..)` needs a known `Option<T>` type here; \
3057 annotate the binding or the return type"
3058 .into())
3059 }
3060 };
3061 return Ok(Val::new(
3062 format!("rsSome[{}]({})", inner, codes.join(", ")),
3063 expect.cloned(),
3064 ));
3065 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3066 "Ok" if self.fmt_param.is_some()
3067 && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) =>
3068 {
3069 // `Ok(())` ends a `fmt` body: nothing more is written.
3070 return Ok(Val::new(String::new(), Some(Nim::Unit)));
3071 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3072 "Ok" | "Err" => {
3073 let (t, e) = match expect {
3074 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
3075 (a[0].render(), a[1].render())
3076 }
3077 _ => {
3078 return Err(format!(
3079 "`{name}(..)` needs a known `Result<T, E>` type here; \
3080 annotate the binding or the return type"
3081 ))
3082 }
3083 };
3084 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
3085 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
3086 return Ok(Val::new(
3087 format!("{}[{}, {}]({})", ctor, t, e, arg),
3088 expect.cloned(),
3089 ));
3090 }
3091 _ => {}
3092 }
3093
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3094 // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's
3095 // object constructor names its fields even when Rust's does not.
3096 if let Some(fields) = self.structs.get(&name).cloned() {
3097 if fields.len() == c.args.len() {
3098 let mut parts = Vec::new();
3099 for (i, a) in c.args.iter().enumerate() {
3100 let v = self.expr_at(a, Some(&fields[i].1))?;
3101 parts.push(format!("{}: {}", ident(&fields[i].0), v.code));
3102 }
3103 return Ok(Val::new(
3104 format!("{}({})", ident(&name), parts.join(", ")),
3105 Some(Nim::Named(name.clone(), vec![])),
3106 ));
3107 }
3108 }
3109
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3110 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
3111 // string view; no copy, no validation, same memory.
3112 if name == "from_utf8_unchecked" && codes.len() == 1 {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3113 // `String::from_utf8_unchecked(v)` takes ownership and yields an
3114 // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields
3115 // a view. Same name, different operations -- the qualifier says
3116 // which, and an unqualified call is ambiguous.
3117 let q = p
3118 .path
3119 .segments
3120 .iter()
3121 .rev()
3122 .nth(1)
3123 .map(|s| s.ident.to_string());
3124 return match q.as_deref() {
3125 Some("String") => Ok(Val::new(
3126 format!("rsStringOf({})", codes[0]),
3127 Some(Nim::Prim("string".into())),
3128 )),
3129 Some("str") => Ok(Val::new(
3130 format!("rsStrView({})", codes[0]),
3131 Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
3132 )),
3133 _ => Err(
3134 "`from_utf8_unchecked` must be written as `str::..` (a \
3135 borrowed view) or `String::..` (an owned string); the two \
3136 are different operations"
3137 .into(),
3138 ),
3139 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3140 }
3141
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3142 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
3143 if let Some((def, v)) = self.resolve_variant(&p.path) {
3144 return Ok(Val::new(
3145 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
3146 Some(Nim::Named(def.name.clone(), vec![])),
3147 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3148 }
3149
3150 // A bare path that names a primitive type is Rust's tuple-struct-like
3151 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3152 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
3153 // is invoked.
3154 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
3155 return Ok(Val::new(
3156 format!("{}({})", ident(&name), codes.join(", ")),
3157 Some((*ret).clone()),
3158 ));
3159 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3160 let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone());
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3161 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3162 return Err(format!(
3163 "call to unknown function `{name}`; only functions defined in \
3164 this file and the supported standard-library subset can be lowered"
3165 ));
3166 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3167 let nim = match &target {
3168 Some((m, n)) => self.fn_name(m, n),
3169 None => ident(&name),
3170 };
3171 Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3172 }
3173
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3174 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3175 let name = m.method.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3176 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
3177 match name.as_str() {
3178 "len" => {
3179 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
3180 }
3181 "is_empty" => {
3182 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
3183 }
3184 other => {
3185 return Err(format!(
3186 "`.{other}()` on a slice window from `chunks_exact`/\
3187 `windows` is not implemented; only indexing and \
3188 `len()` are"
3189 ))
3190 }
3191 }
3192 }
3193 let recv = self.expr(&m.receiver)?;
3194 let rt0 = recv.ty.clone();
3195
3196// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
3197 // way to put a view in an object, so instead of materialising an
3198 // Option the view and its validity condition travel together until
3199 // an `ok_or`/`?`/`unwrap` resolves them.
3200 if matches!(name.as_str(), "get" | "get_mut")
3201 && matches!(m.args.first(), Some(Expr::Range(_)))
3202 {
3203 let Some(Expr::Range(r)) = m.args.first() else { unreachable!() };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3204 let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3205 let lo = match &r.start {
3206 Some(e) => format!("int({})", self.expr(e)?.code),
3207 None => "0".into(),
3208 };
3209 let len = match (&r.end, r.limits) {
3210 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
3211 format!("(int({}) - {})", self.expr(e)?.code, lo)
3212 }
3213 (Some(e), syn::RangeLimits::Closed(_)) => {
3214 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
3215 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3216 (None, _) => format!("({} - {})", blen, lo),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3217 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3218 // Hoisted, so the bounds are computed once -- as Rust computes
3219 // them once -- and cannot be re-evaluated later in a scope where
3220 // the names they mention have been shadowed by a loop pattern.
3221 let off_t = self.fresh("Off");
3222 let len_t = self.fresh("Len");
3223 self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
3224 self.line(&format!("let {}: int = {}", len_t, len));
3225 let elem = belem
3226 .or_else(|| elem_of(&rt0))
3227 .ok_or("cannot infer the element type of this slice")?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3228 let mut v = Val::new(
3229 String::new(),
3230 Some(Nim::Named(
3231 "Option".into(),
3232 vec![Nim::OpenArray(Box::new(elem.clone()))],
3233 )),
3234 );
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3235 v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3236 v.window = Some(Alias::Window {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3237 code,
3238 off: off_t,
3239 len: len_t,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3240 elem: Some(elem),
3241 });
3242 return Ok(v);
3243 }
3244
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3245 // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
3246 // parameter type comes from the receiver, so they are handled before
3247 // the arguments are lowered. The closure is expanded inline, with its
3248 // parameter aliased to the payload: that keeps the whole thing an
3249 // expression and avoids handing a view to a generic proc.
3250 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
3251 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
3252 (recv.ty.clone(), &m.args[0])
3253 {
3254 if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
3255 {
3256 return self.map_closure(&name, &recv, &kind, &targs, c);
3257 }
3258 }
3259 }
3260
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3261 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
3262 // own type; `v.push(e)` takes the element type.
3263 let arg_want = match (name.as_str(), &recv.ty) {
3264 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
3265 (_, t) => t.clone(),
3266 };
3267 let mut args = Vec::new();
3268 for a in &m.args {
3269 args.push(self.expr_at(a, arg_want.as_ref())?);
3270 }
3271 let a0 = args.first().map(|a| a.code.clone());
3272 let rt = recv.ty.clone();
3273
3274 let (code, ty) = match name.as_str() {
3275 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
3276 // explicit so that a `usize` binding type-checks on the Nim side.
3277 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
3278 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
3279 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
3280 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
3281 | "into_iter" => (recv.code.clone(), rt.clone()),
3282 "unwrap" | "expect" => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3283 // Expanded inline rather than called as a generic proc: when
3284 // the payload is a view, Nim can only borrow from a path
3285 // expression, which a proc body containing the panic is not.
3286 let (kind, inner) = match &rt {
3287 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
3288 ("Option", a[0].clone())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3289 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3290 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
3291 ("Result", a[0].clone())
3292 }
3293 _ => {
3294 return Err(format!(
3295 "`.{name}()` needs a known `Option`/`Result` receiver type"
3296 ))
3297 }
3298 };
3299 if self.in_loop_cond {
3300 return Err(format!(
3301 "`.{name}()` in a loop condition is not implemented yet: the \
3302 check it expands to would run once, before the loop"
3303 ));
3304 }
3305 let tmp = self.fresh("Unwrap");
3306 let rty = rt.clone().unwrap();
3307 self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code));
3308 let (test, msg) = if kind == "Option" {
3309 (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value")
3310 } else {
3311 (format!("{}.ok", tmp), "called `Result::unwrap()` on an `Err` value")
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3312 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3313 let msg = if name == "expect" {
3314 args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg))
3315 } else {
3316 fmt::nim_str(msg)
3317 };
3318 self.line(&format!("if not {}:", test));
3319 self.line(&format!(" rsPanic({})", msg));
3320 // If the payload is a view, hand back an alias rather than a
3321 // value: Nim will not let a `let` borrow out of a local, and a
3322 // view is a reference anyway, so there is nothing to bind.
3323 // `{tmp}.val` is a plain field access, so substituting it at
3324 // each use re-evaluates nothing.
3325 if matches!(inner, Nim::OpenArray(_)) {
3326 let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone()));
3327 v.window = Some(Alias::Value {
3328 code: format!("{}.val", tmp),
3329 ty: Some(inner),
3330 });
3331 return Ok(v);
3332 }
3333 (format!("{}.val", tmp), Some(inner))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3334 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3335 "ok_or" if recv.guard.is_some() => {
3336 let e = args.first().ok_or("`ok_or` takes one argument")?;
3337 let ety = e.ty.clone();
3338 let mut v = recv.clone();
3339 v.guard_err = Some(e.code.clone());
3340 v.ty = match (&recv.ty, ety) {
3341 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
3342 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
3343 }
3344 _ => None,
3345 };
3346 return Ok(v);
3347 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3348 "ok_or" => {
3349 let inner = match &rt {
3350 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
3351 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
3352 };
3353 let e = args.first().ok_or("`ok_or` takes one argument")?;
3354 let ety = e
3355 .ty
3356 .clone()
3357 .ok_or("`ok_or` needs a known error type for its argument")?;
3358 (
3359 format!(
3360 "rsOkOr[{}, {}]({}, {})",
3361 inner.render(),
3362 ety.render(),
3363 recv.code,
3364 e.code
3365 ),
3366 Some(Nim::Named("Result".into(), vec![inner, ety])),
3367 )
3368 }
3369 "unwrap_or" => {
3370 let inner = match &rt {
3371 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
3372 Some(a[0].clone())
3373 }
3374 _ => None,
3375 };
3376 (
3377 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
3378 inner,
3379 )
3380 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3381 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
3382 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
3383 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
3384 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
3385
3386 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
3387 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
3388 // Nim raises OverflowDefect, so the operation is routed through
3389 // the unsigned view of the same width, which is what Rust's
3390 // wrapping_* is defined to compute.
3391 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
3392 let op = match name.as_str() {
3393 "wrapping_add" => "+",
3394 "wrapping_sub" => "-",
3395 _ => "*",
3396 };
3397 let t = rt.clone().ok_or_else(|| {
3398 format!("`{name}` needs a known receiver type to pick the wrapping width")
3399 })?;
3400 if !t.is_integer() {
3401 return Err(format!("`{name}` on a non-integer type"));
3402 }
3403 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
3404 if t.is_unsigned() {
3405 (format!("({} {} {})", recv.code, op, arg), Some(t))
3406 } else {
3407 let u = unsigned_peer(&t)?;
3408 (
3409 format!(
3410 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
3411 t.render(), u, recv.code, op, u, arg
3412 ),
3413 Some(t),
3414 )
3415 }
3416 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3417 // Inside a formatting impl, a write through the `Formatter` *is*
3418 // the value the proc returns, so it lowers to the string written.
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3419 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => {
3420 let a = args.first().ok_or("`write_str` takes one argument")?;
3421 // A `&str` argument is a character view, not a Nim string.
3422 let text = match &a.ty {
3423 Some(Nim::Prim(p)) if p == "string" => a.code.clone(),
3424 _ => format!("rsDisplay({})", a.code),
3425 };
3426 (format!("result.add({})", text), Some(Nim::Unit))
3427 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3428 "abs" => (format!("abs({})", recv.code), rt.clone()),
3429 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
3430 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
3431 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
3432 "as_bytes" | "into_bytes" => (
3433 format!("rsBytes({})", recv.code),
3434 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
3435 ),
3436
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3437 "into" => {
3438 // `.into()` resolves through the `impl From` declarations, and
3439 // needs the target type to pick one.
3440 let from = rt
3441 .clone()
3442 .ok_or("`.into()` needs a known receiver type")?;
3443 let to = expect
3444 .ok_or("`.into()` needs a known target type; annotate the binding")?;
3445 let key = (type_name(&from), type_name(to));
3446 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
3447 format!(
3448 "no `impl From<{}> for {}` in this file, so `.into()` has \
3449 no conversion to call",
3450 key.0, key.1
3451 )
3452 })?;
3453 (format!("{}({})", f, recv.code), Some(to.clone()))
3454 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3455 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3456 // A method defined in this file via `impl`, found by the
3457 // receiver's type rather than by name alone.
3458 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
3459 let sig = key.and_then(|k| self.methods.get(&k)).map(|s| s.ret.clone());
3460 if let Some(ret) = sig {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3461 let mut all = vec![recv.code.clone()];
3462 all.extend(args.iter().map(|a| a.code.clone()));
3463 (format!("{}({})", ident(&name), all.join(", ")), Some(ret))
3464 } else {
3465 return Err(format!(
3466 "unsupported method `.{name}()`; it is neither defined in \
3467 this file nor part of the standard-library subset that \
3468 has a verified Nim equivalent"
3469 ));
3470 }
3471 }
3472 };
3473 Ok(Val::new(code, ty))
3474 }
3475
3476 // -------------------------------------------------------------- macros
3477
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3478 /// The element type of a `vec![..]`, from its first element.
3479 fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
3480 let body = mac.tokens.to_string();
3481 if body.trim().is_empty() {
3482 return Ok(None);
3483 }
3484 let first: Option<Expr> = if body.contains(';') {
3485 // The whole body must be consumed or the parse fails, so the
3486 // length is parsed too even though only the element is wanted.
3487 mac.parse_body_with(|input: syn::parse::ParseStream| {
3488 let v: Expr = input.parse()?;
3489 input.parse::<syn::Token![;]>()?;
3490 let _len: Expr = input.parse()?;
3491 Ok(v)
3492 })
3493 .ok()
3494 } else {
3495 mac.parse_body_with(
3496 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3497 )
3498 .ok()
3499 .and_then(|p| p.into_iter().next())
3500 };
3501 match first {
3502 Some(e) => Ok(self.expr(&e)?.ty),
3503 None => Ok(None),
3504 }
3505 }
3506
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3507 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
3508 let name = path_name(&mac.path);
3509 match name.as_str() {
3510 "println" | "print" | "eprintln" | "eprint" => {
3511 let s = self.format_args(mac)?;
3512 let nl = name.ends_with("ln");
3513 Ok(match (name.starts_with('e'), nl) {
3514 (false, true) => format!("echo {s}"),
3515 (false, false) => format!("stdout.write({s})"),
3516 (true, true) => format!("stderr.writeLine({s})"),
3517 (true, false) => format!("stderr.write({s})"),
3518 })
3519 }
3520 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3521 "write" | "writeln" => {
3522 // `write!(f, "..", ..)` inside a formatting impl: the first
3523 // argument is the sink, the rest is an ordinary format call.
3524 let args: Vec<Expr> = mac
3525 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
3526 .map_err(|e| format!("write!: {e}"))?
3527 .into_iter()
3528 .collect();
3529 let sink = args.first().ok_or("`write!` needs a sink")?;
3530 if !self.is_fmt_param(sink) {
3531 return Err("`write!` to anything but the `Formatter` of the \
3532 enclosing formatting impl is not implemented"
3533 .into());
3534 }
3535 let s = self.format_pieces(&args[1..])?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3536 let s = if name == "writeln" {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3537 format!("({} & \"\\n\")", s)
3538 } else {
3539 s
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3540 };
3541 Ok(format!("result.add({})", s))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3542 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3543 "panic" => {
3544 let s = self.format_args(mac)?;
3545 Ok(format!("rsPanic({s})"))
3546 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3547 // `debug_assert*` fires in debug builds, which is the profile
3548 // this project models, so it lowers the same as `assert*`.
3549 "assert" | "debug_assert" => {
3550 let args: Vec<Expr> = mac
3551 .parse_body_with(
3552 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3553 )
3554 .map_err(|e| format!("{name}!: {e}"))?
3555 .into_iter()
3556 .collect();
3557 let cond = args.first().ok_or("`assert!` needs a condition")?;
3558 let v = self.expr(cond)?;
3559 let msg = if args.len() > 1 {
3560 self.format_pieces(&args[1..])?
3561 } else {
3562 fmt::nim_str("assertion failed")
3563 };
3564 Ok(format!("(if not ({}): rsPanic({}))", v.code, msg))
3565 }
3566 "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => {
3567 let args: Vec<Expr> = mac
3568 .parse_body_with(
3569 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3570 )
3571 .map_err(|e| format!("{name}!: {e}"))?
3572 .into_iter()
3573 .collect();
3574 if args.len() < 2 {
3575 return Err(format!("`{name}!` takes two operands"));
3576 }
3577 let a = self.expr(&args[0])?;
3578 let b = self.expr_at(&args[1], a.ty.as_ref())?;
3579 let ne = name.ends_with("_ne");
3580 let op = if ne { "!=" } else { "==" };
3581 // Rust's message shows both sides; reproducing it keeps a
3582 // failing assertion as informative as the original.
3583 let label = if ne { "assertion failed: `(left != right)`" } else { "assertion failed: `(left == right)`" };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3584 Ok(format!(
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3585 "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))",
3586 a.code, op, b.code, fmt::nim_str(label), a.code, b.code
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3587 ))
3588 }
3589 "vec" => {
3590 let body = mac.tokens.to_string();
3591 if body.trim().is_empty() {
3592 return Ok("@[]".into());
3593 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3594 // `vec![elem; n]` is the repeat form, not a list. The macro
3595 // body has no brackets, so it is parsed directly.
3596 if body.contains(';') {
3597 let (v, n) = mac
3598 .parse_body_with(|input: syn::parse::ParseStream| {
3599 let v: Expr = input.parse()?;
3600 input.parse::<syn::Token![;]>()?;
3601 let n: Expr = input.parse()?;
3602 Ok((v, n))
3603 })
3604 .map_err(|e| format!("vec![elem; n]: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3605 let want = self.vec_expect.clone();
3606 let v = self.expr_at(&v, want.as_ref())?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3607 let n = self.expr(&n)?;
3608 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
3609 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3610 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
3611 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
3612 .map_err(|e| format!("vec!: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3613 let want = self.vec_expect.clone();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3614 let mut parts = Vec::new();
3615 for e in &elems {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3616 parts.push(self.expr_at(e, want.as_ref())?.code);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3617 }
3618 Ok(format!("@[{}]", parts.join(", ")))
3619 }
3620 other => Err(format!(
3621 "unsupported macro `{other}!`; a macro whose expansion is not \
3622 known cannot be lowered faithfully"
3623 )),
3624 }
3625 }
3626
3627 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
3628 fn format_args(&mut self, mac: &syn::Macro) -> Result<String, String> {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3629 let args: Vec<Expr> = mac
3630 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
3631 .map_err(|e| format!("format arguments: {e}"))?
3632 .into_iter()
3633 .collect();
3634 self.format_pieces(&args)
3635 }
3636
3637 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
3638 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
3639 let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = args.first() else {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3640 if args.is_empty() {
3641 return Ok("\"\"".into());
3642 }
3643 return Err("the first argument must be a literal format string".into());
3644 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3645 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3646
3647 let pieces = fmt::parse(&s.value())?;
3648 let mut parts: Vec<String> = Vec::new();
3649 let mut next = 0usize;
3650 let mut used = vec![false; rest.len()];
3651 for p in &pieces {
3652 match p {
3653 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
3654 fmt::Piece::Arg { r#ref, spec } => {
3655 let v = match r#ref {
3656 fmt::Ref::Next => {
3657 let e = rest.get(next).ok_or("too few arguments for format string")?;
3658 used[next] = true;
3659 next += 1;
3660 self.expr(e)?
3661 }
3662 fmt::Ref::Index(i) => {
3663 let e = rest.get(*i).ok_or("format index out of range")?;
3664 used[*i] = true;
3665 self.expr(e)?
3666 }
3667 fmt::Ref::Named(n) => {
3668 let t = self.lookup(n).ok_or_else(|| {
3669 format!("`{{{n}}}` captures `{n}`, which is not in scope")
3670 })?;
3671 Val::new(ident(n), Some(t))
3672 }
3673 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3674 let integer = v.ty.as_ref().is_some_and(|t| t.is_integer());
3675 if spec.radix.is_some() && !integer && v.ty.is_none() {
3676 return Err(
3677 "a radix format (`{:x}`, `{:b}`, ...) needs a known \
3678 argument type: on an integer it formats the bit \
3679 pattern, on anything else it calls that type's own \
3680 impl"
3681 .into(),
3682 );
3683 }
3684 parts.push(fmt::render_arg(&v.code, spec, integer));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3685 }
3686 }
3687 }
3688 // Rust rejects an argument that no `{}` consumes; so do we, rather
3689 // than dropping it from the output.
3690 if let Some(i) = used.iter().position(|u| !u) {
3691 return Err(format!(
3692 "argument {} is never used by the format string",
3693 i + 1
3694 ));
3695 }
3696 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
3697 }
3698}
3699
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3700/// Whether a pattern introduces a binding.
3701fn binds(p: &Pat) -> bool {
3702 match p {
3703 Pat::Ident(_) => true,
3704 Pat::Guard(g) => binds(&g.pat),
3705 Pat::Paren(x) => binds(&x.pat),
3706 Pat::Reference(r) => binds(&r.pat),
3707 Pat::Or(o) => o.cases.iter().any(binds),
3708 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
3709 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
3710 _ => false,
3711 }
3712}
3713
3714/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
3715fn destructures(p: &Pat) -> bool {
3716 matches!(
3717 p,
3718 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
3719 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
3720 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
3721 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
3722}
3723
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3724/// Whether an expression has a direct Nim expression form.
3725///
3726/// Nim's `if` is an expression only when every arm is a single expression, and
3727/// its `case` is never one here. Anything else has to be lowered as statements
3728/// that assign into a target.
3729fn expressible(e: &Expr) -> bool {
3730 match e {
3731 Expr::If(i) => {
3732 let Some(then) = single_expr(&i.then_branch) else { return false };
3733 if !expressible(then) {
3734 return false;
3735 }
3736 match &i.else_branch {
3737 None => false,
3738 Some((_, els)) => match &**els {
3739 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
3740 other => expressible(other),
3741 },
3742 }
3743 }
3744 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
3745 _ => true,
3746 }
3747}
3748
3749/// The single expression a block consists of, if that is all it is. An `if`
3750/// can only be lowered as a Nim `if`-expression when both arms are this shape.
3751fn single_expr(b: &syn::Block) -> Option<&Expr> {
3752 match (b.stmts.len(), b.stmts.first()) {
3753 (1, Some(Stmt::Expr(e, None))) => Some(e),
3754 _ => None,
3755 }
3756}
3757
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3758/// Substitute `params[i] -> args[i]` through a type. Enough of the type
3759/// grammar is covered to expand the aliases we accept; anything else is left
3760/// alone and will be reported by `ty::map` if it is unsupported.
3761fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
3762 use syn::Type;
3763 match t {
3764 Type::Path(p) => {
3765 if p.qself.is_none() && p.path.segments.len() == 1 {
3766 let seg = &p.path.segments[0];
3767 if seg.arguments.is_empty() {
3768 let name = seg.ident.to_string();
3769 if let Some(i) = params.iter().position(|x| *x == name) {
3770 return args[i].clone();
3771 }
3772 }
3773 }
3774 let mut p = p.clone();
3775 for seg in &mut p.path.segments {
3776 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
3777 for g in &mut a.args {
3778 if let syn::GenericArgument::Type(t) = g {
3779 *t = substitute(t, params, args);
3780 }
3781 }
3782 }
3783 }
3784 Type::Path(p)
3785 }
3786 Type::Reference(r) => {
3787 let mut r = r.clone();
3788 r.elem = Box::new(substitute(&r.elem, params, args));
3789 Type::Reference(r)
3790 }
3791 Type::Slice(sl) => {
3792 let mut sl = sl.clone();
3793 sl.elem = Box::new(substitute(&sl.elem, params, args));
3794 Type::Slice(sl)
3795 }
3796 Type::Array(a) => {
3797 let mut a = a.clone();
3798 a.elem = Box::new(substitute(&a.elem, params, args));
3799 Type::Array(a)
3800 }
3801 Type::Tuple(tp) => {
3802 let mut tp = tp.clone();
3803 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
3804 Type::Tuple(tp)
3805 }
3806 Type::Paren(p) => substitute(&p.elem, params, args),
3807 Type::Group(g) => substitute(&g.elem, params, args),
3808 other => other.clone(),
3809 }
3810}
3811
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3812// --------------------------------------------------------------- utilities
3813
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3814/// Whether a return type is a borrow of one of the arguments, which Nim
3815/// models with a view rather than with an owned copy.
3816fn returns_borrow(t: &syn::Type) -> bool {
3817 match t {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3818 syn::Type::Reference(r) => match &*r.elem {
3819 syn::Type::Slice(_) => true,
3820 // `&str` is a borrow of someone else's bytes too, and returning it
3821 // means returning a view, not an owned string.
3822 syn::Type::Path(p) => p.path.is_ident("str"),
3823 _ => false,
3824 },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3825 syn::Type::Paren(p) => returns_borrow(&p.elem),
3826 syn::Type::Group(g) => returns_borrow(&g.elem),
3827 _ => false,
3828 }
3829}
3830
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3831/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
3832/// to the crate root, which is where a flattened module's items live unless
3833/// they came from one of the extra input files.
3834fn module_of(prefix: &[String]) -> String {
3835 match prefix.last() {
3836 Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
3837 _ => String::new(),
3838 }
3839}
3840
3841/// The first type argument of an `Option[T]` / `Result[T, E]`.
3842fn elem_arg(t: &Nim) -> Nim {
3843 match t {
3844 Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
3845 other => other.clone(),
3846 }
3847}
3848
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3849/// The element type of a sequence-like Nim type.
3850fn elem_of(t: &Option<Nim>) -> Option<Nim> {
3851 match t {
3852 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
3853 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
3854 _ => None,
3855 }
3856}
3857
3858/// The short name a Nim type is known by, for keying method tables.
3859fn type_name(t: &Nim) -> String {
3860 match t {
3861 Nim::Named(n, _) => n.clone(),
3862 Nim::Prim(p) => p.clone(),
3863 other => other.render(),
3864 }
3865}
3866
3867fn is_fmt_trait(t: &str) -> bool {
3868 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
3869}
3870
3871/// The prelude proc a formatting trait's output is produced by.
3872fn fmt_proc(t: &str) -> &'static str {
3873 match t {
3874 "Display" => "rsDisplay",
3875 "Debug" => "rsDebug",
3876 "LowerHex" => "rsLowerHex",
3877 "UpperHex" => "rsUpperHex",
3878 "Binary" => "rsBinary",
3879 _ => "rsOctal",
3880 }
3881}
3882
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 18h ago3883/// Whether an expression denotes a place -- a variable, a field, or an index
3884/// or slice of one -- and so may be re-evaluated with no side effect.
3885fn is_pure_place(e: &Expr) -> bool {
3886 match e {
3887 Expr::Path(_) => true,
3888 Expr::Field(f) => is_pure_place(&f.base),
3889 Expr::Index(i) => {
3890 is_pure_place(&i.expr)
3891 && match &*i.index {
3892 Expr::Range(r) => {
3893 r.start.as_deref().map_or(true, is_pure_place)
3894 && r.end.as_deref().map_or(true, is_pure_place)
3895 }
3896 other => is_pure_place(other),
3897 }
3898 }
3899 Expr::Lit(_) => true,
3900 Expr::Reference(r) => is_pure_place(&r.expr),
3901 Expr::Paren(p) => is_pure_place(&p.expr),
3902 Expr::Group(g) => is_pure_place(&g.expr),
3903 // Arithmetic on places is still side-effect free, so a bound like
3904 // `..want - 1` does not stop the binding being an alias.
3905 Expr::Binary(b) if !is_compound(&b.op) => {
3906 is_pure_place(&b.left) && is_pure_place(&b.right)
3907 }
3908 Expr::Unary(u) => is_pure_place(&u.expr),
3909 Expr::Cast(c) => is_pure_place(&c.expr),
3910 _ => false,
3911 }
3912}
3913
3914/// Whether an expression is a `&mut` borrow, directly or through parens.
3915fn is_mut_borrow(e: &Expr) -> bool {
3916 match e {
3917 Expr::Reference(r) => r.mutability.is_some(),
3918 Expr::Paren(p) => is_mut_borrow(&p.expr),
3919 Expr::Group(g) => is_mut_borrow(&g.expr),
3920 _ => false,
3921 }
3922}
3923
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3924fn takes_self(sig: &syn::Signature) -> bool {
3925 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
3926}
3927
3928fn path_name(p: &syn::Path) -> String {
3929 p.segments
3930 .last()
3931 .map(|s| s.ident.to_string())
3932 .unwrap_or_default()
3933}
3934
3935fn is_compound(op: &BinOp) -> bool {
3936 matches!(
3937 op,
3938 BinOp::AddAssign(_)
3939 | BinOp::SubAssign(_)
3940 | BinOp::MulAssign(_)
3941 | BinOp::DivAssign(_)
3942 | BinOp::RemAssign(_)
3943 | BinOp::BitAndAssign(_)
3944 | BinOp::BitOrAssign(_)
3945 | BinOp::BitXorAssign(_)
3946 | BinOp::ShlAssign(_)
3947 | BinOp::ShrAssign(_)
3948 )
3949}
3950
3951/// The Nim literal suffix for an integer type (`5'i32`).
3952fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
3953 let Nim::Prim(p) = t else {
3954 return Err("not a primitive integer".into());
3955 };
3956 Ok(match p.as_str() {
3957 "int8" => "i8",
3958 "int16" => "i16",
3959 "int32" => "i32",
3960 "int64" => "i64",
3961 "int" => "i",
3962 "uint8" => "u8",
3963 "uint16" => "u16",
3964 "uint32" => "u32",
3965 "uint64" => "u64",
3966 "uint" => "u",
3967 other => return Err(format!("no Nim literal suffix for `{other}`")),
3968 })
3969}
3970
3971/// The unsigned integer type of the same width, used to spell `wrapping_*`.
3972fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
3973 let Nim::Prim(p) = t else {
3974 return Err("not a primitive integer".into());
3975 };
3976 Ok(match p.as_str() {
3977 "int8" => "uint8",
3978 "int16" => "uint16",
3979 "int32" => "uint32",
3980 "int64" => "uint64",
3981 "int" => "uint",
3982 other => return Err(format!("`{other}` has no unsigned peer")),
3983 })
3984}
3985
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3986fn quote_meta(m: &syn::Meta) -> String {
3987 match m {
3988 syn::Meta::Path(p) => path_name(p),
3989 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
3990 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
3991 }
3992}
3993
3994fn item_attrs(i: &Item) -> &[syn::Attribute] {
3995 match i {
3996 Item::Fn(f) => &f.attrs,
3997 Item::Struct(s) => &s.attrs,
3998 Item::Enum(e) => &e.attrs,
3999 Item::Impl(x) => &x.attrs,
4000 Item::Const(c) => &c.attrs,
4001 Item::Type(t) => &t.attrs,
4002 Item::Mod(m) => &m.attrs,
4003 Item::Use(u) => &u.attrs,
4004 Item::ExternCrate(e) => &e.attrs,
4005 Item::Static(s) => &s.attrs,
4006 _ => &[],
4007 }
4008}
4009
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4010fn item_kind(i: &Item) -> &'static str {
4011 match i {
4012 Item::Trait(_) => "`trait`",
4013 Item::Static(_) => "`static`",
4014 Item::Macro(_) => "macro definition",
4015 Item::Union(_) => "`union`",
4016 Item::ForeignMod(_) => "`extern` block",
4017 _ => "item",
4018 }
4019}
4020
4021fn expr_kind(e: &Expr) -> &'static str {
4022 match e {
4023 Expr::Async(_) => "`async` block",
4024 Expr::Await(_) => "`.await`",
4025 Expr::Try(_) => "`?`",
4026 Expr::Range(_) => "range",
4027 Expr::Match(_) => "`match` (only statement position is implemented)",
4028 Expr::Let(_) => "`let` expression",
4029 Expr::Unsafe(_) => "`unsafe` block",
4030 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
4031 _ => "expression",
4032 }
4033}