nandi/rustnimpublic Fork 0
0e6c394efb2da64065352ff67a15f6cba4e8899a
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 · 4544 lines · 189.0 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h 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 21h 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 22h 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 21h 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 20h 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 21h 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 20h 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 21h 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 20h 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 21h 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 22h ago103 }
104}
105
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h 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> },
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago113 /// The name stands for an iterator that has not been consumed yet, as in
114 /// `let it = xs.chunks_exact(k);`. Rust's iterators are values; ours are
115 /// resolved chains, so the chain is carried until a `for` consumes it.
116 Iterator(Box<Iter>),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago117}
118
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago119/// A lowered expression: its Nim text, and its type where we know it.
120///
121/// The type is not decoration. Nim needs it to pick `div` over `/`, to size a
122/// `cast`, and to annotate every binding so that Nim's own type checker
123/// catches a mistake in this file rather than letting it through as output
124/// that runs and is wrong.
125#[derive(Clone, Debug)]
126struct Val {
127 code: String,
128 ty: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago129 /// Set when the value *is* a slice view rather than a Nim value: binding
130 /// it introduces an alias, not a copy.
131 window: Option<Alias>,
132 /// For `get`/`get_mut`: the condition under which the `Option` is `Some`,
133 /// carried until an `ok_or`/`?` or an `unwrap` consumes it. Nim's view
134 /// types cannot live inside an object, so an `Option` of a view has no
135 /// runtime representation -- it is tracked here instead.
136 guard: Option<String>,
137 /// The error an `ok_or` attached to that guard.
138 guard_err: Option<String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago139}
140
141impl Val {
142 fn new(code: impl Into<String>, ty: Option<Nim>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago143 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 22h ago144 }
145 fn untyped(code: impl Into<String>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago146 Val::new(code, None)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago147 }
148}
149
150struct Sig {
151 params: Vec<Nim>,
152 ret: Nim,
153}
154
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago155/// One variant of a Rust enum.
156#[derive(Clone)]
157struct Variant {
158 name: String,
159 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
160 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
161 /// Nim requires the branches of a variant object to have distinct fields.
162 fields: Vec<(String, Nim)>,
163}
164
165#[derive(Clone)]
166struct EnumDef {
167 name: String,
168 /// True when every variant is a unit variant, which Nim represents as a
169 /// plain `enum` rather than an object variant.
170 simple: bool,
171 variants: Vec<Variant>,
172}
173
174impl EnumDef {
175 fn kind_ident(&self, v: &str) -> String {
176 format!("k{}{}", self.name, v)
177 }
178 fn ctor_ident(&self, v: &str) -> String {
179 format!("{}{}", self.name, v)
180 }
181 fn get(&self, v: &str) -> Option<&Variant> {
182 self.variants.iter().find(|x| x.name == v)
183 }
184}
185
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago186pub struct Lowerer {
187 out: String,
188 indent: usize,
189 scopes: Vec<HashMap<String, Nim>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago190 /// Names introduced by a `for` pattern that stand for an lvalue or a
191 /// window into a container, rather than for a variable of their own.
192 alias_scopes: Vec<HashMap<String, Alias>>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago193 /// `(module, name) -> signature`. Rust keeps `lower::decode` and
194 /// `mixed::decode` apart by module; flattening into one Nim module would
195 /// merge them, so the module is part of the key and of the emitted name.
196 fns: HashMap<(String, String), Sig>,
197 /// Module being lowered: the file stem, or empty for the crate root.
198 cur_mod: String,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago199 /// The type of the `impl` block being lowered, which `Self` names.
200 self_ty: Option<Nim>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago201 /// `use` brings a name into scope from another module. Flattening loses
202 /// the module structure, so the mapping is recorded and consulted when a
203 /// bare call is resolved.
204 use_map: HashMap<String, String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago205 /// struct name -> (field, type)
206 structs: HashMap<String, Vec<(String, Nim)>>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago207 enums: HashMap<String, EnumDef>,
208 /// variant name -> enums declaring it. A variant named by more than one
209 /// enum must be written qualified, or it is rejected as ambiguous.
210 variant_owner: HashMap<String, Vec<String>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago211 /// `(receiver type, method) -> signature`. Keyed by type because two
212 /// types may define the same method name, and Nim tells them apart by
213 /// overload resolution on the first parameter.
214 methods: HashMap<(String, String), Sig>,
215 /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
216 /// on a user type can be checked rather than assumed.
217 fmt_impls: HashMap<(String, String), ()>,
218 /// `(from, to)` conversions declared by `impl From<A> for B`.
219 from_impls: HashMap<(String, String), String>,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago220 /// Operator traits implemented for a type, so `a += b` on a user type can
221 /// be dispatched to the impl rather than to Nim's built-in operator.
222 op_impls: HashMap<(String, String), ()>,
223 /// `(type, method) -> nim name`, for calls written as `Type::method(..)`.
224 statics: HashMap<(String, String), String>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago225 /// Forward declarations, emitted between the type definitions and the
226 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
227 /// proc is declared up front rather than the input being reordered --
228 /// which would not work for mutual recursion anyway.
229 forwards: Vec<String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago230 /// Element type a `vec![..]` should build, from the binding's annotation.
231 vec_expect: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago232 /// While lowering a formatting impl: the `Formatter` parameter's name.
233 /// Writes through it produce the proc's string result.
234 fmt_param: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago235 /// `type X<T> = ...`, expanded before any type is mapped.
236 aliases: HashMap<String, (Vec<String>, syn::Type)>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago237 /// Module names supplied as separate input files. A `mod x;` naming one
238 /// of these is satisfied by that file having been passed in.
239 pub modules: Vec<String>,
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 19h ago240 /// How many items were actually translated. If this is zero the input
241 /// produced nothing but the prelude, and reporting success for that is
242 /// the precise failure this project exists to avoid -- see `findings/`.
243 emitted: usize,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago244 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
245 /// evaluated against these exactly as rustc would, so an item that is
246 /// dropped here is genuinely not part of the program being compiled.
247 pub features: Vec<String>,
248 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago249 /// Return type of the proc being lowered, so `return e` and a trailing
250 /// expression can type their literals the way Rust's inference would.
251 ret: Option<Nim>,
252 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
253 /// statement must assign their value to.
254 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago255 /// Set while lowering a `while` condition, which Nim re-evaluates each
256 /// iteration and so cannot have statements hoisted out of it.
257 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago258 tmp: usize,
259}
260
261impl Lowerer {
262 pub fn new() -> Self {
263 Lowerer {
264 out: String::new(),
265 indent: 0,
266 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago267 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago268 fns: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago269 cur_mod: String::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago270 self_ty: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago271 use_map: HashMap::new(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago272 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago273 enums: HashMap::new(),
274 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago275 methods: HashMap::new(),
276 fmt_impls: HashMap::new(),
277 from_impls: HashMap::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago278 op_impls: HashMap::new(),
279 statics: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago280 fmt_param: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago281 vec_expect: None,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago282 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago283 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago284 modules: Vec::new(),
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 19h ago285 emitted: 0,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago286 features: Vec::new(),
287 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago288 ret: None,
289 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago290 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago291 tmp: 0,
292 }
293 }
294
295 // ------------------------------------------------------------ emission
296
297 fn line(&mut self, s: &str) {
298 for _ in 0..self.indent {
299 self.out.push_str(" ");
300 }
301 self.out.push_str(s);
302 self.out.push('\n');
303 }
304
305 fn blank(&mut self) {
306 self.out.push('\n');
307 }
308
309 fn fresh(&mut self, hint: &str) -> String {
310 self.tmp += 1;
311 format!("rsTmp{}{}", hint, self.tmp)
312 }
313
314 // --------------------------------------------------------------- scope
315
316 fn push_scope(&mut self) {
317 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago318 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago319 }
320 fn pop_scope(&mut self) {
321 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago322 self.alias_scopes.pop();
323 }
324 fn bind_alias(&mut self, name: &str, a: Alias) {
325 self.alias_scopes
326 .last_mut()
327 .unwrap()
328 .insert(name.to_string(), a);
329 }
330 fn lookup_alias(&self, name: &str) -> Option<Alias> {
331 self.alias_scopes
332 .iter()
333 .rev()
334 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago335 }
336 fn bind(&mut self, name: &str, t: Nim) {
337 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
338 }
339 fn lookup(&self, name: &str) -> Option<Nim> {
340 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
341 }
342
343 // ---------------------------------------------------------------- file
344
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago345 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 22h ago346 self.out.push_str(include_str!("prelude.nim"));
347 self.blank();
348
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago349 // Pass 0: type aliases. A signature in one file may use an alias
350 // declared in another, and inputs are given in whatever order suits
351 // 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 20h ago352 for (m, f) in files {
353 self.cur_mod = m.clone();
354 for item in &f.items {
355 self.collect_aliases(item)?;
356 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago357 }
358
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago359 // Pass 1: signatures and struct shapes, so that a call can be typed
360 // regardless of declaration order (Rust has no forward declarations).
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago361 for (m, f) in files {
362 self.cur_mod = m.clone();
363 for item in &f.items {
364 self.collect(item)?;
365 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago366 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago367 // Pass 2: type definitions, which every signature may mention.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago368 for (m, f) in files {
369 self.cur_mod = m.clone();
370 for item in &f.items {
371 self.item_types(item)?;
372 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago373 }
374
375 // Pass 3: forward declarations. Rust imposes no declaration order and
376 // Nim does, so everything is declared before any body is emitted;
377 // reordering the input would not handle mutual recursion anyway.
378 if !self.forwards.is_empty() {
379 for f in self.forwards.clone() {
380 self.line(&f);
381 }
382 self.blank();
383 }
384
385 // Pass 4: bodies.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago386 for (m, f) in files {
387 self.cur_mod = m.clone();
388 for item in &f.items {
389 self.item(item)?;
390 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago391 }
392
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 19h ago393 // An input that translates to nothing is a failure, however plausible
394 // the output file looks. The prelude alone is not a translation.
395 if self.emitted == 0 {
396 return Err(format!(
397 "nothing was translated: the input has no items this lowering \
398 emits{}. Writing a file containing only the prelude would \
399 report success for work that was not done",
400 if self.dropped_by_cfg > 0 {
401 format!(
402 " ({} item(s) were dropped by `#[cfg]`; enable them with \
403 `--cfg feature=<name>`)",
404 self.dropped_by_cfg
405 )
406 } else {
407 String::new()
408 }
409 ));
410 }
411
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago412 if self.fns.contains_key(&(String::new(), "main".to_string())) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago413 self.blank();
414 self.line("when isMainModule:");
415 self.indent += 1;
416 self.line("try:");
417 self.line(" main()");
418 // Rust's panic exits 101 with a message on stderr. Nim's Defects
419 // exit 1. Mapping them here is what keeps the differential runner's
420 // exit-status comparison meaningful for panicking programs.
421 self.line("except RustPanic as e:");
422 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
423 self.line(" quit(101)");
424 self.line("except Defect as e:");
425 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
426 self.line(" quit(101)");
427 self.indent -= 1;
428 }
429 Ok(std::mem::take(&mut self.out))
430 }
431
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago432 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
433 if !self.cfg_keeps(item_attrs(item))? {
434 return Ok(());
435 }
436 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago437 Item::Use(u) => self.collect_use(&u.tree, &[]),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago438 Item::Type(t) => {
439 let params: Vec<String> = t
440 .generics
441 .params
442 .iter()
443 .filter_map(|g| match g {
444 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
445 _ => None,
446 })
447 .collect();
448 self.aliases
449 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
450 }
451 Item::Mod(m) if m.content.is_some() => {
452 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
453 for i in &items {
454 self.collect_aliases(i)?;
455 }
456 }
457 _ => {}
458 }
459 Ok(())
460 }
461
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago462 /// Record what a `use` brings into scope, as `name -> module`.
463 fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
464 use syn::UseTree;
465 match t {
466 UseTree::Path(p) => {
467 let mut pre = prefix.to_vec();
468 pre.push(p.ident.to_string());
469 self.collect_use(&p.tree, &pre);
470 }
471 UseTree::Group(g) => {
472 for t in &g.items {
473 self.collect_use(t, prefix);
474 }
475 }
476 UseTree::Name(n) => {
477 let m = module_of(prefix);
478 self.use_map.insert(n.ident.to_string(), m);
479 }
480 UseTree::Rename(r) => {
481 let m = module_of(prefix);
482 self.use_map.insert(r.rename.to_string(), m);
483 }
484 // A glob brings in an unknown set of names; resolution falls back
485 // to the current module and the root, as it would without it.
486 UseTree::Glob(_) => {}
487 }
488 }
489
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago490 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago491 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
492 // silently would change what the program does; picking a feature set
493 // on the user's behalf would be a guess. So it is reported, except on
494 // items that carry no runtime meaning here anyway.
495 if !self.cfg_keeps(item_attrs(item))? {
496 self.dropped_by_cfg += 1;
497 return Ok(());
498 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago499 match item {
500 Item::Fn(f) => {
501 let (params, ret) = self.signature(&f.sig)?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago502 let name = f.sig.ident.to_string();
503 let nim = self.fn_name(&self.cur_mod, &name);
504 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
505 self.fns
506 .insert((self.cur_mod.clone(), name), Sig { params, ret });
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago507 }
508 Item::Struct(s) => {
509 let mut fields = Vec::new();
510 for (i, f) in s.fields.iter().enumerate() {
511 let name = match &f.ident {
512 Some(id) => id.to_string(),
513 None => format!("f{i}"), // tuple struct
514 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago515 // A field of `&[T]` / `&str` type is a borrow, and Nim's
516 // view types allow it as an object field, so it stays a
517 // view rather than being copied into a `seq`.
518 let t = self.map_ty(&f.ty)?;
519 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
520 fields.push((name, t));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago521 }
522 self.structs.insert(s.ident.to_string(), fields);
523 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago524 Item::Mod(m) if m.content.is_some() => {
525 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
526 for i in &items {
527 self.collect(i)?;
528 }
529 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago530 Item::Type(t) => {
531 let params: Vec<String> = t
532 .generics
533 .params
534 .iter()
535 .filter_map(|g| match g {
536 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
537 _ => None,
538 })
539 .collect();
540 self.aliases
541 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
542 }
543 Item::Enum(e) => {
544 let name = e.ident.to_string();
545 if e.generics.params.iter().any(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
546 return Err(format!("`enum {name}` is generic: not implemented yet"));
547 }
548 let mut variants = Vec::new();
549 for v in &e.variants {
550 let vname = v.ident.to_string();
551 if v.discriminant.is_some() {
552 return Err(format!(
553 "`{name}::{vname}` has an explicit discriminant; Rust's \
554 `as` on such an enum has a value this lowering does not \
555 yet preserve"
556 ));
557 }
558 let mut fields = Vec::new();
559 for (i, f) in v.fields.iter().enumerate() {
560 // Nim requires the branches of a variant object to have
561 // distinct field names, so each is prefixed.
562 let fname = match &f.ident {
563 Some(id) => format!("{vname}_{id}"),
564 None => format!("{vname}_f{i}"),
565 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago566 let t = self.map_ty(&f.ty)?;
567 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
568 fields.push((fname, t));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago569 }
570 variants.push(Variant { name: vname, fields });
571 }
572 let simple = variants.iter().all(|v| v.fields.is_empty());
573 for v in &variants {
574 self.variant_owner
575 .entry(v.name.clone())
576 .or_default()
577 .push(name.clone());
578 }
579 self.enums.insert(
580 name.clone(),
581 EnumDef { name, simple, variants },
582 );
583 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago584 Item::Impl(im) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago585 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago586 let outer_self = self.self_ty.replace(self_ty.clone());
587 let r = self.collect_impl(im, &self_ty);
588 self.self_ty = outer_self;
589 return r;
590 }
591 _ => {}
592 }
593 Ok(())
594 }
595
596 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
597 {
598 let self_ty = self_ty.clone();
599 let tyname = type_name(&self_ty);
600 if let Some((path, _)) = &im.trait_ {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago601 let tr = path_name(path);
602 if im.items.is_empty() {
603 // A marker trait with no items. We do not model trait
604 // resolution at all, so it generates nothing; any use
605 // that actually needed the trait (a `dyn`, a bound) is
606 // rejected where it appears.
607 return Ok(());
608 }
609 if is_fmt_trait(&tr) {
610 self.forwards.push(format!(
611 "proc {}*(self: {}): string",
612 fmt_proc(&tr),
613 self_ty.render()
614 ));
615 self.fmt_impls.insert((tyname, tr), ());
616 return Ok(());
617 }
618 if tr == "From" {
619 let syn::ImplItem::Fn(m) = &im.items[0] else {
620 return Err("`impl From` must contain `fn from`".into());
621 };
622 let (params, _) = self.signature(&m.sig)?;
623 let src = params
624 .first()
625 .ok_or("`fn from` takes one argument")?
626 .clone();
627 let name = format!("rsFrom{}{}", tyname, type_name(&src));
628 self.forwards.push(self.head_of(&name, &m.sig, None)?);
629 self.from_impls
630 .insert((type_name(&src), tyname), name);
631 return Ok(());
632 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago633 // Any other trait: its methods are emitted as procs on
634 // the type, named after the trait so two traits declaring
635 // the same method name do not collide. The *trait* is not
636 // modelled -- no dynamic dispatch, no bounds -- and a use
637 // that needs it is rejected where it appears.
638 if let Some(op) = operator_trait(&tr) {
639 self.op_impls.insert((tyname.clone(), op.to_string()), ());
640 }
641 for it in &im.items {
642 let syn::ImplItem::Fn(m) = it else {
643 return Err(format!("unsupported item in `impl {tr}`"));
644 };
645 let mname = m.sig.ident.to_string();
646 let (mut params, ret) = self.signature(&m.sig)?;
647 let recv = if takes_self(&m.sig) {
648 params.insert(0, self_ty.clone());
649 Some(self_ty.clone())
650 } else {
651 None
652 };
653 let nim = trait_method_name(&tyname, &tr, &mname);
654 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
655 self.methods
656 .insert((tyname.clone(), mname.clone()), Sig { params, ret });
657 self.statics.insert((tyname.clone(), mname), nim);
658 }
659 return Ok(());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago660 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago661 for it in &im.items {
662 if let syn::ImplItem::Fn(m) = it {
663 let (mut params, ret) = self.signature(&m.sig)?;
664 if takes_self(&m.sig) {
665 params.insert(0, self_ty.clone());
666 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago667 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago668 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
669 let head = self.head_of(&nim, &m.sig, recv.as_ref())?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago670 self.forwards.push(head);
671 self.methods
672 .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret });
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago673 self.statics
674 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago675 }
676 }
677 }
678 Ok(())
679 }
680
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago681 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
682 ///
683 /// This is evaluation, not approximation: rustc does the same thing, and
684 /// an item whose predicate is false is not part of the compiled program.
685 /// A predicate that cannot be evaluated is reported rather than assumed.
686 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
687 for a in attrs {
688 if a.path().is_ident("cfg") {
689 let pred: syn::Meta = a
690 .parse_args()
691 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
692 if !self.cfg_eval(&pred)? {
693 return Ok(false);
694 }
695 }
696 }
697 Ok(true)
698 }
699
700 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
701 match m {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago702 // Bare flags whose value is determined by the profile this project
703 // models: a normal (non-`--test`) debug build, not a docs build.
704 // Anything platform-specific stays rejected, since we would be
705 // picking a target on the user's behalf.
706 syn::Meta::Path(p) if p.is_ident("test") => Ok(false),
707 syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true),
708 syn::Meta::Path(p) if p.is_ident("docsrs") || p.is_ident("doc") => Ok(false),
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 19h ago709 syn::Meta::Path(p) if p.is_ident("doctest") || p.is_ident("miri") => Ok(false),
710 // Host facts. The generated Nim is compiled for this machine, so
711 // these are known rather than chosen. See DESIGN.md item 10: it
712 // does make the output host-shaped.
713 syn::Meta::Path(p) if p.is_ident("unix") => Ok(cfg!(unix)),
714 syn::Meta::Path(p) if p.is_ident("windows") => Ok(cfg!(windows)),
715 syn::Meta::NameValue(nv)
716 if nv.path.is_ident("target_os")
717 || nv.path.is_ident("target_arch")
718 || nv.path.is_ident("target_family")
719 || nv.path.is_ident("target_vendor") =>
720 {
721 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
722 return Err("this `cfg` key expects a string".into());
723 };
724 let key = nv.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
725 Ok(s.value()
726 == match key.as_str() {
727 "target_os" => std::env::consts::OS,
728 "target_arch" => std::env::consts::ARCH,
729 "target_family" => std::env::consts::FAMILY,
730 _ => "unknown",
731 })
732 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago733 // The generated Nim is compiled for the same machine, so the
734 // target's word size and endianness are known rather than
735 // guessed. This does mean the output is host-shaped: a crate that
736 // branches on pointer width has had that branch decided here.
737 syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => {
738 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
739 return Err("`target_pointer_width = ..` expects a string".into());
740 };
741 Ok(s.value() == (usize::BITS).to_string())
742 }
743 syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => {
744 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
745 return Err("`target_endian = ..` expects a string".into());
746 };
747 Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" })
748 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago749 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
750 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
751 return Err("`feature = ..` expects a string".into());
752 };
753 Ok(self.features.iter().any(|f| *f == s.value()))
754 }
755 syn::Meta::List(l) if l.path.is_ident("not") => {
756 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
757 Ok(!self.cfg_eval(&inner)?)
758 }
759 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
760 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
761 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
762 .map_err(|e| e.to_string())?;
763 let all = l.path.is_ident("all");
764 let mut acc = all;
765 for i in &items {
766 let v = self.cfg_eval(i)?;
767 acc = if all { acc && v } else { acc || v };
768 }
769 Ok(acc)
770 }
771 other => Err(format!(
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 19h ago772 "`#[cfg({})]` is not a predicate rustnim can evaluate. \
773 Features (`--cfg feature=..`), host facts (`unix`, `windows`, \
774 `target_os`, `target_arch`, `target_family`, \
775 `target_pointer_width`, `target_endian`), `doc`/`doctest`/\
776 `miri`, and `not`/`all`/`any` over those are. A custom or \
777 build-script `cfg` has no value we could know",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago778 quote_meta(other)
779 )),
780 }
781 }
782
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago783 /// Map a Rust type, resolving `Self` and expanding any `type` alias. Every type in the
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago784 /// lowering goes through here rather than calling `ty::map` directly, so
785 /// an alias cannot be missed in one position and honoured in another.
786 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago787 let n = ty::map(&self.expand(t, 0)?)?;
788 Ok(self.subst_self(n))
789 }
790
791 /// `Self` inside an `impl` block names the type being implemented.
792 fn subst_self(&self, t: Nim) -> Nim {
793 let Some(me) = &self.self_ty else { return t };
794 match t {
795 Nim::Named(n, _) if n == "Self" => me.clone(),
796 Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))),
797 Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))),
798 Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))),
799 Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))),
800 Nim::Named(n, a) => {
801 Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect())
802 }
803 other => other,
804 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago805 }
806
807 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
808 if depth > 16 {
809 return Err("type alias expansion did not terminate; is it cyclic?".into());
810 }
811 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
812 // Only an unqualified name can be one of this file's aliases.
813 // `fmt::Result` and `core::result::Result` are different types that
814 // merely end in the same segment.
815 if p.path.segments.len() != 1 {
816 return Ok(t.clone());
817 }
818 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
819 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
820 return Ok(t.clone());
821 };
822 let args: Vec<syn::Type> = match &seg.arguments {
823 syn::PathArguments::AngleBracketed(a) => a
824 .args
825 .iter()
826 .filter_map(|g| match g {
827 GenericArgument::Type(t) => Some(t.clone()),
828 _ => None,
829 })
830 .collect(),
831 _ => vec![],
832 };
833 if args.len() != params.len() {
834 // Flattening several files into one module can bring a crate's own
835 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
836 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
837 // module; here they are told apart by arity, and a use that fits
838 // neither is left for `ty::map` to report.
839 return Ok(t.clone());
840 }
841 self.expand(&substitute(target, params, &args), depth + 1)
842 }
843
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago844 /// The Nim name for a function, qualified by its module.
845 fn fn_name(&self, module: &str, name: &str) -> String {
846 if module.is_empty() {
847 ident(name)
848 } else {
849 format!("{}_{}", module, ident(name))
850 }
851 }
852
853 /// Resolve a call path to the module and name it refers to: an explicit
854 /// `mixed::decode`, then the current module, then the crate root.
855 fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
856 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
857 let last = segs.last()?.clone();
858 if segs.len() >= 2 {
859 let q = &segs[segs.len() - 2];
860 if self.fns.contains_key(&(q.clone(), last.clone())) {
861 return Some((q.clone(), last));
862 }
863 }
864 let imported = self.use_map.get(&last).cloned();
865 for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
866 .into_iter()
867 .flatten()
868 {
869 if self.fns.contains_key(&(m.clone(), last.clone())) {
870 return Some((m, last));
871 }
872 }
873 None
874 }
875
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago876 /// The Nim `proc` head for a Rust signature, used both for the forward
877 /// declaration and for the definition, so the two cannot drift apart.
878 fn head_of(
879 &self,
880 name: &str,
881 sig: &syn::Signature,
882 recv: Option<&Nim>,
883 ) -> Result<String, String> {
884 let (ptys, ret) = self.signature(sig)?;
885 let mut parts = Vec::new();
886 if let Some(self_ty) = recv {
887 let mutable = matches!(
888 sig.inputs.first(),
889 Some(FnArg::Receiver(r))
890 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
891 );
892 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
893 parts.push(format!("self: {}", t.render()));
894 }
895 let typed: Vec<&syn::PatType> = sig
896 .inputs
897 .iter()
898 .filter_map(|a| match a {
899 FnArg::Typed(t) => Some(t),
900 _ => None,
901 })
902 .collect();
903 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
904 let pname = match &*p.pat {
905 Pat::Ident(id) => id.ident.to_string(),
906 Pat::Wild(_) => format!("unused{}", parts.len()),
907 _ => return Err("only plain identifier parameters are supported".into()),
908 };
909 let _ = i;
910 parts.push(format!("{}: {}", ident(&pname), t.render()));
911 }
912 Ok(if ret == Nim::Unit {
913 format!("proc {}*({})", ident(name), parts.join(", "))
914 } else {
915 format!("proc {}*({}): {}", ident(name), parts.join(", "), ret.render())
916 })
917 }
918
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago919 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 20h ago920 // `unsafe fn` marks a contract for callers; it does not change what
921 // the body means, so it lowers like any other proc.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago922 if sig.asyncness.is_some() {
923 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
924 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago925 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
926 // `fn encode<'a>(..)` is not generic for our purposes. Type and const
927 // parameters genuinely are, and are rejected.
928 if let Some(p) = sig.generics.params.iter().find(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
929 let what = match p {
930 syn::GenericParam::Const(_) => "const",
931 _ => "type",
932 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago933 return Err(format!(
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago934 "`fn {}` has a {what} parameter: generics are not implemented yet",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago935 sig.ident
936 ));
937 }
938 let mut params = Vec::new();
939 for a in &sig.inputs {
940 if let FnArg::Typed(t) = a {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago941 params.push(self.map_ty(&t.ty)?);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago942 }
943 }
944 let ret = match &sig.output {
945 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago946 // A returned `&[T]` is a borrow of the caller's buffer, so it
947 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
948 // a `seq`, which `owned()` would do to both.
949 ReturnType::Type(_, t) => {
950 let n = self.map_ty(t)?;
951 if returns_borrow(t) { n } else { n.owned() }
952 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago953 };
954 Ok((params, ret))
955 }
956
957 // --------------------------------------------------------------- items
958
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago959 /// Emit the type definitions only: they must precede every signature.
960 fn item_types(&mut self, item: &Item) -> Result<(), String> {
961 if !self.cfg_keeps(item_attrs(item))? {
962 return Ok(());
963 }
964 match item {
965 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
966 Item::Mod(m) if m.content.is_some() => {
967 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
968 for i in &items {
969 self.item_types(i)?;
970 }
971 Ok(())
972 }
973 _ => Ok(()),
974 }
975 }
976
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago977 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago978 if !self.cfg_keeps(item_attrs(item))? {
979 return Ok(());
980 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago981 // Types were emitted in their own pass.
982 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
983 return Ok(());
984 }
985 self.item_inner(item)
986 }
987
988 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 19h ago989 if !matches!(item, Item::Use(_) | Item::ExternCrate(_) | Item::Mod(_) | Item::Type(_)) {
990 self.emitted += 1;
991 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago992 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago993 Item::Fn(f) => {
994 let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
995 self.func_named(&nim, &f.sig, &f.block, None)
996 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago997 Item::Struct(s) => {
998 let name = s.ident.to_string();
999 let fields = self.structs[&name].clone();
1000 self.line(&format!("type {}* = object", ident(&name)));
1001 self.indent += 1;
1002 if fields.is_empty() {
1003 self.line("discard");
1004 }
1005 for (fname, fty) in &fields {
1006 self.line(&format!("{}*: {}", ident(fname), fty.render()));
1007 }
1008 self.indent -= 1;
1009 self.blank();
1010 Ok(())
1011 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago1012 Item::Type(_) => Ok(()), // expanded at every use site
1013 Item::Enum(e) => {
1014 let def = self.enums[&e.ident.to_string()].clone();
1015 self.emit_enum(&def);
1016 Ok(())
1017 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1018 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago1019 let t = self.map_ty(&c.ty)?.owned();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1020 // The annotation types the initialiser, exactly as it does for
1021 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
1022 let v = self.expr_at(&c.expr, Some(&t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1023 self.bind(&c.ident.to_string(), t.clone());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1024 // Only a top-level const is exported; `*` on a local is not
1025 // Nim syntax.
1026 let star = if self.indent == 0 { "*" } else { "" };
1027 let line = format!(
1028 "const {}{}: {} = {}",
1029 ident(&c.ident.to_string()),
1030 star,
1031 t.render(),
1032 v.code
1033 );
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1034 self.line(&line);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1035 if self.indent == 0 {
1036 self.blank();
1037 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1038 Ok(())
1039 }
1040 Item::Impl(im) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago1041 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1042 let outer = self.self_ty.replace(self_ty.clone());
1043 let r = self.impl_body(im, &self_ty);
1044 self.self_ty = outer;
1045 r
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1046 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago1047 // `use` and `extern crate` are resolution directives with no Nim
1048 // analogue once everything is one module.
1049 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
1050 Item::Mod(m) if m.content.is_some() => {
1051 // An inline `mod` is flattened; Nim has no nested modules in a
1052 // single file.
1053 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1054 for i in &items {
1055 self.item(i)?;
1056 }
1057 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1058 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1059 Item::Mod(m) => {
1060 // Satisfied if that file was passed in too; everything is one
1061 // Nim module, so the declaration itself emits nothing.
1062 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
1063 return Ok(());
1064 }
1065 Err(format!(
1066 "`mod {};` refers to another file that was not passed to \
1067 rustnim; add it to the input list",
1068 m.ident
1069 ))
1070 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1071 other => Err(format!("unsupported item: {}", item_kind(other))),
1072 }
1073 }
1074
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago1075 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
1076 fn none_of(&self, expect: Option<&Nim>) -> String {
1077 match expect {
1078 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
1079 format!("rsNone[{}]()", a[0].render())
1080 }
1081 _ => "rsNone()".to_string(),
1082 }
1083 }
1084
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1085 fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
1086 if let Some((path, _)) = &im.trait_ {
1087 let tr = path_name(path);
1088 if im.items.is_empty() {
1089 return Ok(());
1090 }
1091 if is_fmt_trait(&tr) {
1092 let syn::ImplItem::Fn(m) = &im.items[0] else {
1093 return Err(format!("unsupported item in `impl {tr}`"));
1094 };
1095 return self.fmt_impl(&tr, self_ty, &m.sig, &m.block);
1096 }
1097 if tr == "From" {
1098 let syn::ImplItem::Fn(m) = &im.items[0] else {
1099 return Err("`impl From` must contain `fn from`".into());
1100 };
1101 let name = {
1102 let (params, _) = self.signature(&m.sig)?;
1103 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
1104 self.from_impls[&(type_name(&src), type_name(self_ty))].clone()
1105 };
1106 return self.func_named(&name, &m.sig, &m.block, None);
1107 }
1108 let tyname = type_name(self_ty);
1109 for it in &im.items {
1110 let syn::ImplItem::Fn(m) = it else {
1111 return Err(format!("unsupported item in `impl {tr}`"));
1112 };
1113 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1114 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
1115 self.func_named(&nim, &m.sig, &m.block, recv)?;
1116 }
1117 return Ok(());
1118 }
1119 for it in &im.items {
1120 match it {
1121 syn::ImplItem::Fn(m) => {
1122 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1123 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
1124 self.func_named(&nim, &m.sig, &m.block, recv)?;
1125 }
1126 _ => return Err("only `fn` items are supported inside `impl`".into()),
1127 }
1128 }
1129 Ok(())
1130 }
1131
1132 /// The type an operator impl declares for its right-hand operand.
1133 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
1134 let n = type_name(t.as_ref()?);
1135 let sig = self.methods.get(&(n, op_method(op).to_string()))?;
1136 sig.params.get(1).cloned().map(|t| t.unvar())
1137 }
1138
1139 /// The proc implementing `op` for a user type, if there is one.
1140 fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> {
1141 let n = type_name(t.as_ref()?);
1142 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
1143 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
1144 Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1))
1145 } else {
1146 None
1147 }
1148 }
1149
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago1150 fn emit_enum(&mut self, def: &EnumDef) {
1151 let name = ident(&def.name);
1152 if def.simple {
1153 // Every variant is a unit variant, so a plain Nim enum is an exact
1154 // fit: it compares, orders and `case`-checks like Rust's.
1155 self.line(&format!("type {name}* = enum"));
1156 self.indent += 1;
1157 for v in &def.variants {
1158 self.line(&format!("{}", ident(&v.name)));
1159 }
1160 self.indent -= 1;
1161 self.blank();
1162 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1163 self.indent += 1;
1164 self.line("case x");
1165 for v in &def.variants {
1166 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
1167 }
1168 self.indent -= 1;
1169 self.blank();
1170 return;
1171 }
1172
1173 // A data-carrying enum is a Nim object variant: one discriminant enum
1174 // plus a branch per variant. This is the same shape the prelude uses
1175 // for `Option` and `Result`.
1176 self.line("type");
1177 self.indent += 1;
1178 self.line(&format!("{}Kind* = enum", name));
1179 self.indent += 1;
1180 for v in &def.variants {
1181 self.line(&def.kind_ident(&v.name));
1182 }
1183 self.indent -= 1;
1184 self.blank();
1185 self.line(&format!("{}* = object", name));
1186 self.indent += 1;
1187 self.line(&format!("case kind*: {}Kind", name));
1188 for v in &def.variants {
1189 if v.fields.is_empty() {
1190 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
1191 } else {
1192 self.line(&format!("of {}:", def.kind_ident(&v.name)));
1193 self.indent += 1;
1194 for (f, t) in &v.fields {
1195 self.line(&format!("{}*: {}", ident(f), t.render()));
1196 }
1197 self.indent -= 1;
1198 }
1199 }
1200 self.indent -= 2;
1201 self.blank();
1202
1203 for v in &def.variants {
1204 let args: Vec<String> = v
1205 .fields
1206 .iter()
1207 .enumerate()
1208 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
1209 .collect();
1210 let inits: Vec<String> = v
1211 .fields
1212 .iter()
1213 .enumerate()
1214 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
1215 .collect();
1216 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1217 all.extend(inits);
1218 self.line(&format!(
1219 "proc {}*({}): {} = {}({})",
1220 def.ctor_ident(&v.name),
1221 args.join(", "),
1222 name,
1223 name,
1224 all.join(", ")
1225 ));
1226 }
1227 self.blank();
1228
1229 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1230 self.indent += 1;
1231 self.line("case x.kind");
1232 for v in &def.variants {
1233 if v.fields.is_empty() {
1234 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
1235 } else {
1236 let parts: Vec<String> = v
1237 .fields
1238 .iter()
1239 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
1240 .collect();
1241 self.line(&format!(
1242 "of {}: \"{}(\" & {} & \")\"",
1243 def.kind_ident(&v.name),
1244 v.name,
1245 parts.join(" & \", \" & ")
1246 ));
1247 }
1248 }
1249 self.indent -= 1;
1250 self.blank();
1251 }
1252
1253 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1254 /// to the enum that declares it.
1255 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
1256 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1257 let last = segs.last()?.clone();
1258 if segs.len() >= 2 {
1259 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
1260 if def.get(&last).is_some() {
1261 return Some((def.clone(), last));
1262 }
1263 }
1264 }
1265 // Unqualified: only unambiguous if exactly one enum declares it.
1266 match self.variant_owner.get(&last) {
1267 Some(owners) if owners.len() == 1 => {
1268 let def = self.enums.get(&owners[0])?;
1269 Some((def.clone(), last))
1270 }
1271 _ => None,
1272 }
1273 }
1274
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1275 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
1276 ///
1277 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
1278 /// observable result of `{}` is exactly the bytes written. So the method
1279 /// becomes `proc rsDisplay(self: T): string` and every write through the
1280 /// formatter produces that string. A `fmt` body that does anything else
1281 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
1282 /// because those affect the output and this model does not carry them.
1283 /// The window an expression names, if it names one.
1284 fn window_of(&self, e: &Expr) -> Option<Alias> {
1285 match e {
1286 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
1287 Some(a @ Alias::Window { .. }) => Some(a),
1288 _ => None,
1289 },
1290 Expr::Reference(r) => self.window_of(&r.expr),
1291 Expr::Paren(p) => self.window_of(&p.expr),
1292 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
1293 _ => None,
1294 }
1295 }
1296
1297 /// Whether an expression is the `Formatter` parameter of the formatting
1298 /// impl currently being lowered.
1299 fn is_fmt_param(&self, e: &Expr) -> bool {
1300 let Some(f) = &self.fmt_param else { return false };
1301 match e {
1302 Expr::Path(p) => path_name(&p.path) == *f,
1303 Expr::Reference(r) => self.is_fmt_param(&r.expr),
1304 Expr::Paren(p) => self.is_fmt_param(&p.expr),
1305 _ => false,
1306 }
1307 }
1308
1309 fn fmt_impl(
1310 &mut self,
1311 tr: &str,
1312 self_ty: &Nim,
1313 sig: &syn::Signature,
1314 body: &syn::Block,
1315 ) -> Result<(), String> {
1316 let proc_name = fmt_proc(tr);
1317 // The formatter is the parameter after `self`.
1318 let f = sig
1319 .inputs
1320 .iter()
1321 .filter_map(|a| match a {
1322 FnArg::Typed(t) => match &*t.pat {
1323 Pat::Ident(i) => Some(i.ident.to_string()),
1324 _ => None,
1325 },
1326 _ => None,
1327 })
1328 .next()
1329 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1330
1331 self.push_scope();
1332 self.bind("self", self_ty.clone());
1333 let saved = self.fmt_param.replace(f);
1334 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 20h ago1335 // No assignment target: a formatter write *appends*, because a `fmt`
1336 // body may write repeatedly -- `UpperHex` writes once per byte in a
1337 // loop -- and assigning would keep only the last one.
1338 let outer_target = self.target.take();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1339
1340 self.line(&format!(
1341 "proc {}*(self: {}): string =",
1342 proc_name,
1343 self_ty.render()
1344 ));
1345 self.indent += 1;
1346 let before = self.out.len();
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago1347 let tail = self.block_body(body)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1348 self.emit_tail(tail);
1349 if self.out.len() == before {
1350 self.line("discard");
1351 }
1352 self.indent -= 1;
1353
1354 self.target = outer_target;
1355 self.ret = outer_ret;
1356 self.fmt_param = saved;
1357 self.pop_scope();
1358 self.blank();
1359 Ok(())
1360 }
1361
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1362 fn func(
1363 &mut self,
1364 sig: &syn::Signature,
1365 body: &syn::Block,
1366 recv: Option<Nim>,
1367 ) -> Result<(), String> {
1368 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1369 self.func_named(&name.clone(), sig, body, recv)
1370 }
1371
1372 fn func_named(
1373 &mut self,
1374 name: &str,
1375 sig: &syn::Signature,
1376 body: &syn::Block,
1377 recv: Option<Nim>,
1378 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1379 let (ptys, ret) = self.signature(sig)?;
1380
1381 self.push_scope();
1382 let mut rendered: Vec<String> = Vec::new();
1383
1384 if let Some(self_ty) = recv {
1385 // `&mut self` and `mut self` both mean the body may mutate the
1386 // receiver; only the former is observable by the caller, and a Nim
1387 // `var` parameter is the faithful spelling of that.
1388 let mutable = matches!(
1389 sig.inputs.first(),
1390 Some(FnArg::Receiver(r))
1391 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1392 );
1393 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1394 rendered.push(format!("self: {}", t.render()));
1395 self.bind("self", self_ty);
1396 }
1397
1398 let typed: Vec<&syn::PatType> = sig
1399 .inputs
1400 .iter()
1401 .filter_map(|a| match a {
1402 FnArg::Typed(t) => Some(t),
1403 _ => None,
1404 })
1405 .collect();
1406 for (p, t) in typed.iter().zip(ptys.iter()) {
1407 let pname = match &*p.pat {
1408 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1409 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
1410 // still needs a name for it.
1411 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1412 _ => return Err("only plain identifier parameters are supported".into()),
1413 };
1414 rendered.push(format!("{}: {}", ident(&pname), t.render()));
1415 // Inside the body a `var T` parameter is used exactly like a `T`.
1416 self.bind(&pname, t.clone().owned());
1417 }
1418
1419 let head = if ret == Nim::Unit {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1420 format!("proc {}*({}) =", ident(name), rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1421 } else {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1422 format!("proc {}*({}): {} =", ident(name), rendered.join(", "), ret.render())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1423 };
1424 self.line(&head);
1425 self.indent += 1;
1426 let outer_ret = self.ret.replace(ret.clone());
1427
1428 // A Rust fn's trailing expression is its return value. Naming Nim's
1429 // implicit `result` as the target makes that true whether the tail is
1430 // a plain expression or an `if`/`match` with statement arms.
1431 let outer_target = if ret == Nim::Unit {
1432 self.target.take()
1433 } else {
1434 self.target.replace(("result".to_string(), Some(ret.clone())))
1435 };
1436 let before = self.out.len();
1437 let tail = self.block_body_at(body, Some(&ret))?;
1438 self.target = outer_target;
1439 match tail {
1440 Some(v) if ret != Nim::Unit => {
1441 let code = v.code.clone();
1442 self.line(&format!("result = {code}"));
1443 }
1444 Some(v) => {
1445 // A trailing expression in a `()`-returning fn is evaluated for
1446 // its effect; Nim requires an explicit discard.
1447 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1448 if needs_discard && !v.code.is_empty() {
1449 let code = v.code.clone();
1450 self.line(&format!("discard {code}"));
1451 }
1452 }
1453 None => {}
1454 }
1455 if self.out.len() == before {
1456 self.line("discard");
1457 }
1458
1459 self.indent -= 1;
1460 self.ret = outer_ret;
1461 self.pop_scope();
1462 self.blank();
1463 Ok(())
1464 }
1465
1466 // ---------------------------------------------------------- statements
1467
1468 /// Lower a block's statements. Returns the block's trailing expression,
1469 /// if it has one, *without* emitting it — the caller decides whether that
1470 /// value is a return value, a binding, or discarded.
1471 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
1472 self.block_body_at(b, None)
1473 }
1474
1475 fn block_body_at(
1476 &mut self,
1477 b: &syn::Block,
1478 expect: Option<&Nim>,
1479 ) -> Result<Option<Val>, String> {
1480 // An assignment target belongs to *this* block's trailing expression
1481 // only. A non-final `if` is a statement and must not assign anything.
1482 let target = self.target.take();
1483 let n = b.stmts.len();
1484 let mut tail = None;
1485 for (i, st) in b.stmts.iter().enumerate() {
1486 let last = i + 1 == n;
1487 match st {
1488 Stmt::Expr(e, None) if last && expressible(e) => {
1489 tail = Some(self.expr_at(e, expect)?)
1490 }
1491 Stmt::Expr(e, None) if last => {
1492 // A trailing `if`/`match` with statement arms, or a loop.
1493 // Lower it as statements; if this block's value is wanted,
1494 // each arm assigns it.
1495 match &target {
1496 Some((t, ty)) => {
1497 let (t, ty) = (t.clone(), ty.clone());
1498 self.assign_from(e, &t, ty.as_ref())?;
1499 }
1500 None => self.stmt(st)?,
1501 }
1502 }
1503 _ => self.stmt(st)?,
1504 }
1505 }
1506 self.target = target;
1507 Ok(tail)
1508 }
1509
1510 /// Lower a block in statement position (loop bodies, `if` arms).
1511 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
1512 self.push_scope();
1513 self.indent += 1;
1514 let before = self.out.len();
1515 let want = self.target.clone().and_then(|(_, t)| t);
1516 let tail = self.block_body_at(b, want.as_ref())?;
1517 self.emit_tail(tail);
1518 if self.out.len() == before {
1519 self.line("discard");
1520 }
1521 self.indent -= 1;
1522 self.pop_scope();
1523 Ok(())
1524 }
1525
1526 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
1527 match s {
1528 Stmt::Local(l) => self.local(l),
1529 Stmt::Expr(e, _) => {
1530 let v = self.expr_stmt(e)?;
1531 if let Some(v) = v {
1532 // A bare expression with a value must be discarded in Nim.
1533 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1534 let code = v.code.clone();
1535 if needs {
1536 self.line(&format!("discard {code}"));
1537 } else if !code.is_empty() {
1538 self.line(&code);
1539 }
1540 }
1541 Ok(())
1542 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1543 // A `const` declared inside a function body is local to it, and
1544 // must be emitted here rather than skipped as an already-emitted
1545 // top-level type.
1546 Stmt::Item(i) => self.item_inner(i),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1547 Stmt::Macro(m) => {
1548 let line = self.macro_call(&m.mac)?;
1549 self.line(&line);
1550 Ok(())
1551 }
1552 }
1553 }
1554
1555 fn local(&mut self, l: &Local) -> Result<(), String> {
1556 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
1557 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
1558 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago1559 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 22h ago1560 _ => return Err("only `let <ident>` bindings are supported".into()),
1561 },
1562 Pat::Wild(_) => ("_".into(), false, None),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1563 Pat::Tuple(t) => return self.local_tuple(l, t),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1564 _ => return Err("destructuring `let` is not implemented yet".into()),
1565 };
1566
1567 let Some(init) = &l.init else {
1568 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
1569 // not. Rust's own rules make reading it before assignment illegal,
1570 // so the two agree on every program rustc accepts.
1571 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
1572 let t = t.owned();
1573 self.line(&format!("var {}: {}", ident(&name), t.render()));
1574 self.bind(&name, t);
1575 return Ok(());
1576 };
1577 if init.diverge.is_some() {
1578 return Err("`let ... else` is not implemented yet".into());
1579 }
1580
1581 if !expressible(&init.expr) && name != "_" {
1582 // The initialiser is an `if`/`match` whose arms are statements.
1583 // Declare first, then let each arm assign into the binding.
1584 let t = ann
1585 .clone()
1586 .ok_or_else(|| {
1587 format!(
1588 "`let {name} = match/if ...` needs a type annotation: \
1589 its arms are statements, so the binding must be \
1590 declared before they run"
1591 )
1592 })?
1593 .owned();
1594 self.line(&format!("var {}: {}", ident(&name), t.render()));
1595 self.bind(&name, t.clone());
1596 let target = ident(&name);
1597 return self.assign_from(&init.expr, &target, Some(&t));
1598 }
1599
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1600 // `let it = xs.chunks_exact(k)` binds an iterator, not a value.
1601 if is_iterator_expr(&init.expr) {
1602 let it = self.resolve_iter(&init.expr)?;
1603 self.bind_alias(&name, Alias::Iterator(Box::new(it)));
1604 return Ok(());
1605 }
1606
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1607 let v = self.expr_at(&init.expr, ann.as_ref())?;
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 20h ago1608
1609 // `let s = &buf[..n]` binds a view of a place that is already in
1610 // scope. Nim's borrow checker will not let a `let` borrow out of a
1611 // local, and there is nothing to materialise anyway -- a view is a
1612 // reference. Binding it as an alias substitutes the same expression at
1613 // each use, which re-evaluates nothing because the initialiser is a
1614 // place expression with no side effects.
1615 if v.window.is_none()
1616 && matches!(v.ty, Some(Nim::OpenArray(_)))
1617 && is_pure_place(&init.expr)
1618 {
1619 let t = v.ty.clone().unwrap();
1620 let elem = match &t {
1621 Nim::OpenArray(e) => Some((**e).clone()),
1622 _ => None,
1623 };
1624 self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) });
1625 let _ = elem;
1626 return Ok(());
1627 }
1628
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1629 if let Some(w) = v.window.clone() {
1630 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
1631 // view into the caller's buffer. Copying it into a `seq` would
1632 // still print the right bytes but would stop writes reaching the
1633 // caller, so it is bound as an alias.
1634 if v.guard.is_some() && v.guard_err.is_some() {
1635 return Err(format!(
1636 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
1637 which Nim cannot represent; apply `?` or `unwrap()` to it \
1638 in the same expression"
1639 ));
1640 }
1641 self.bind_alias(&name, w);
1642 return Ok(());
1643 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago1644 // A `let` binding a borrow keeps the view: `let res = encode(..)?`
1645 // names the caller's buffer, and copying it into a `seq` would still
1646 // print the right bytes while silently breaking the aliasing.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1647 let t = match (ann, &v.ty) {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago1648 (Some(a), _) => a.unvar(),
1649 (None, Some(t)) => t.clone().unvar(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1650 (None, None) => {
1651 return Err(format!(
1652 "cannot infer the type of `let {name}`; annotate it — \
1653 guessing here would change integer width, and with it the \
1654 meaning of any arithmetic on `{name}`"
1655 ))
1656 }
1657 };
1658
1659 if name == "_" {
1660 let code = v.code.clone();
1661 self.line(&format!("discard {code}"));
1662 return Ok(());
1663 }
1664 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
1665 // 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 20h ago1666 //
1667 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
1668 // Rust may write through it, and Nim only accepts a `var` where a
1669 // `var` parameter is wanted, so the binding has to be one.
1670 let mutable = mutable || is_mut_borrow(&init.expr);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1671 let kw = if mutable { "var" } else { "let" };
1672 let line = format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code);
1673 self.line(&line);
1674 self.bind(&name, t);
1675 Ok(())
1676 }
1677
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1678 /// `let (a, b) = ..` — tuple destructuring.
1679 fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> {
1680 let Some(init) = &l.init else {
1681 return Err("a destructuring `let` needs an initialiser".into());
1682 };
1683 let names: Vec<(String, bool)> = t
1684 .elems
1685 .iter()
1686 .map(|p| match p {
1687 Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())),
1688 Pat::Wild(_) => Ok(("_".to_string(), false)),
1689 _ => Err("only plain identifiers are supported in a destructuring `let`"),
1690 })
1691 .collect::<Result<_, _>>()?;
1692
1693 // `split_at` hands back two *views* of the same slice. Nim has no
1694 // tuple of views, and there is nothing to materialise anyway, so each
1695 // name becomes a window into the original.
1696 if let Expr::MethodCall(m) = &*init.expr {
1697 let mname = m.method.to_string();
1698 if (mname == "split_at" || mname == "split_at_mut")
1699 && m.args.len() == 1
1700 && names.len() == 2
1701 {
1702 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
1703 let at = self.expr(&m.args[0])?;
1704 let cut = self.fresh("Cut");
1705 self.line(&format!("let {}: int = int({})", cut, at.code));
1706 self.bind_alias(
1707 &names[0].0,
1708 Alias::Window {
1709 code: code.clone(),
1710 off: base.clone(),
1711 len: cut.clone(),
1712 elem: elem.clone(),
1713 },
1714 );
1715 self.bind_alias(
1716 &names[1].0,
1717 Alias::Window {
1718 code,
1719 off: format!("({} + {})", base, cut),
1720 len: format!("({} - {})", len, cut),
1721 elem,
1722 },
1723 );
1724 return Ok(());
1725 }
1726 }
1727
1728 let v = self.expr(&init.expr)?;
1729 let tys = match &v.ty {
1730 Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(),
1731 _ => {
1732 return Err(format!(
1733 "cannot destructure this into {} bindings: its type is not a \
1734 tuple of that many elements",
1735 names.len()
1736 ))
1737 }
1738 };
1739 let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" };
1740 let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect();
1741 self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code));
1742 for ((n, _), t) in names.iter().zip(tys) {
1743 self.bind(n, t);
1744 }
1745 Ok(())
1746 }
1747
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1748 /// Expressions that are statements in Rust and statements in Nim too
1749 /// (control flow). Returns `None` when it emitted lines itself.
1750 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
1751 match e {
1752 Expr::If(_) => {
1753 self.if_stmt(e)?;
1754 Ok(None)
1755 }
1756 Expr::While(w) => {
1757 if w.label.is_some() {
1758 return Err("loop labels are not implemented yet".into());
1759 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago1760 self.in_loop_cond = true;
1761 let c = self.expr(&w.cond);
1762 self.in_loop_cond = false;
1763 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1764 self.line(&format!("while {}:", c.code));
1765 let saved = self.target.take();
1766 self.nested_block(&w.body)?;
1767 self.target = saved;
1768 Ok(None)
1769 }
1770 Expr::Loop(l) => {
1771 if l.label.is_some() {
1772 return Err("loop labels are not implemented yet".into());
1773 }
1774 self.line("while true:");
1775 let saved = self.target.take();
1776 self.nested_block(&l.body)?;
1777 self.target = saved;
1778 Ok(None)
1779 }
1780 Expr::ForLoop(f) => {
1781 self.for_loop(f)?;
1782 Ok(None)
1783 }
1784 Expr::Block(b) => {
1785 if b.label.is_some() {
1786 return Err("block labels are not implemented yet".into());
1787 }
1788 self.line("block:");
1789 self.nested_block(&b.block)?;
1790 Ok(None)
1791 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago1792 Expr::Unsafe(u) => {
1793 // Transparent in statement position too, for the same reason.
1794 self.nested_block_flat(&u.block)?;
1795 Ok(None)
1796 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1797 Expr::Match(_) => {
1798 self.match_stmt(e)?;
1799 Ok(None)
1800 }
1801 Expr::Return(r) => {
1802 match &r.expr {
1803 Some(e) => {
1804 let want = self.ret.clone();
1805 let v = self.expr_at(e, want.as_ref())?;
1806 self.line(&format!("return {}", v.code));
1807 }
1808 None => self.line("return"),
1809 }
1810 Ok(None)
1811 }
1812 Expr::Break(b) => {
1813 if b.expr.is_some() || b.label.is_some() {
1814 return Err("`break` with a value or a label is not implemented yet".into());
1815 }
1816 self.line("break");
1817 Ok(None)
1818 }
1819 Expr::Continue(c) => {
1820 if c.label.is_some() {
1821 return Err("labelled `continue` is not implemented yet".into());
1822 }
1823 self.line("continue");
1824 Ok(None)
1825 }
1826 Expr::Assign(a) => {
1827 let lhs = self.expr(&a.left)?;
1828 if !expressible(&a.right) {
1829 let target = lhs.code.clone();
1830 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
1831 }
1832 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
1833 self.line(&format!("{} = {}", lhs.code, rhs.code));
1834 Ok(None)
1835 }
1836 Expr::Binary(b) if is_compound(&b.op) => {
1837 let lhs = self.expr(&b.left)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago1838 // A compound assignment on a user type goes to that type's own
1839 // `impl OpAssign`, not to Nim's built-in operator.
1840 if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) {
1841 // The impl's own parameter type types the right operand,
1842 // so `b_vec *= 4` takes 4 at the width the impl declares.
1843 let want = self.op_param(&lhs.ty, compound_symbol(&b.op));
1844 let rhs = self.expr_at(&b.right, want.as_ref())?;
1845 self.line(&format!("{}({}, {})", f, lhs.code, rhs.code));
1846 return Ok(None);
1847 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1848 // `i += 1` must widen the literal to `i`'s type, not to the
1849 // i32 an unconstrained Rust literal would default to.
1850 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
1851 let op = self.bin_op(&b.op, &lhs, &rhs)?;
1852 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
1853 // both languages, so the expanded form is always correct.
1854 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
1855 Ok(None)
1856 }
1857 Expr::Macro(m) => {
1858 let line = self.macro_call(&m.mac)?;
1859 self.line(&line);
1860 Ok(None)
1861 }
1862 _ => Ok(Some(self.expr(e)?)),
1863 }
1864 }
1865
1866 /// Lower `e` in statement position, assigning each arm's value to
1867 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
1868 /// the trip when their arms are too big for a Nim `if`-expression.
1869 fn assign_from(
1870 &mut self,
1871 e: &Expr,
1872 target: &str,
1873 expect: Option<&Nim>,
1874 ) -> Result<(), String> {
1875 let saved = self.target.replace((target.to_string(), expect.cloned()));
1876 let r = match e {
1877 Expr::If(_) => self.if_stmt(e),
1878 Expr::Match(_) => self.match_stmt(e),
1879 other => {
1880 let v = self.expr_at(other, expect)?;
1881 self.line(&format!("{} = {}", target, v.code));
1882 Ok(())
1883 }
1884 };
1885 self.target = saved;
1886 r
1887 }
1888
1889 /// Emit a block's value into the active assignment target, if there is
1890 /// one, or discard it if there is not.
1891 fn emit_tail(&mut self, v: Option<Val>) {
1892 let Some(v) = v else { return };
1893 match self.target.clone() {
1894 Some((t, _)) => {
1895 let code = v.code.clone();
1896 self.line(&format!("{t} = {code}"));
1897 }
1898 None => {
1899 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1900 let code = v.code.clone();
1901 if needs {
1902 self.line(&format!("discard {code}"));
1903 } else if !code.is_empty() {
1904 self.line(&code);
1905 }
1906 }
1907 }
1908 }
1909
1910 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
1911 let Expr::If(i) = e else { unreachable!() };
1912 if let Expr::Let(_) = &*i.cond {
1913 return Err("`if let` is not implemented yet".into());
1914 }
1915 let c = self.expr(&i.cond)?;
1916 self.line(&format!("if {}:", c.code));
1917 self.nested_block(&i.then_branch)?;
1918 match &i.else_branch {
1919 None => {}
1920 Some((_, els)) => match &**els {
1921 Expr::If(_) => {
1922 // Nim needs `elif`; splice the nested `if` in as one.
1923 let mark = self.out.len();
1924 self.if_stmt(els)?;
1925 let tail = self.out.split_off(mark);
1926 let indent = " ".repeat(self.indent);
1927 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
1928 }
1929 Expr::Block(b) => {
1930 self.line("else:");
1931 self.nested_block(&b.block)?;
1932 }
1933 _ => return Err("unsupported `else` form".into()),
1934 },
1935 }
1936 Ok(())
1937 }
1938
1939 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
1940 if f.label.is_some() {
1941 return Err("loop labels are not implemented yet".into());
1942 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1943 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1944
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1945 // One index loop drives the whole chain. Rust's adaptors are lazy and
1946 // compose; resolving them to an index and binding each name to an
1947 // lvalue reproduces that without materialising anything.
1948 let i = self.fresh("Idx");
1949 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
1950 self.indent += 1;
1951 self.push_scope();
1952 let before = self.out.len();
1953
1954 self.bind_pattern(&f.pat, &it, &i)?;
1955
1956 let saved = self.target.take();
1957 if let Some(v) = self.block_body(&f.body)? {
1958 let code = v.code.clone();
1959 self.line(&format!("discard {code}"));
1960 }
1961 self.target = saved;
1962 if self.out.len() == before {
1963 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1964 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1965 self.pop_scope();
1966 self.indent -= 1;
1967 Ok(())
1968 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1969
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1970 /// Resolve a chain of iterator adaptors into a single `Iter`.
1971 ///
1972 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
1973 /// `filter`, `take_while` and friends are rejected rather than partially
1974 /// honoured: silently dropping an adaptor would change which elements the
1975 /// loop visits.
1976 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
1977 match e {
1978 Expr::Reference(r) => self.resolve_iter(&r.expr),
1979 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1980 Expr::Range(r) => {
1981 let lo = match &r.start {
1982 Some(e) => self.expr(e)?,
1983 None => return Err("a `for` over `..n` needs a start bound".into()),
1984 };
1985 let hi = match &r.end {
1986 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1987 None => {
1988 return Err("a `for` over an unbounded range would not terminate".into())
1989 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago1990 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago1991 let ty = lo.ty.clone().or(hi.ty.clone());
1992 Ok(Iter::Range {
1993 lo: lo.code,
1994 hi: hi.code,
1995 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
1996 ty,
1997 })
1998 }
1999 Expr::MethodCall(m) => {
2000 let name = m.method.to_string();
2001 match name.as_str() {
2002 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
2003 let mut it = self.resolve_iter(&m.receiver)?;
2004 if name == "iter_mut" {
2005 if let Iter::Elems { mutable, .. } = &mut it {
2006 *mutable = true;
2007 }
2008 }
2009 Ok(it)
2010 }
2011 "enumerate" if m.args.is_empty() => {
2012 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
2013 }
2014 "zip" if m.args.len() == 1 => {
2015 let a = self.resolve_iter(&m.receiver)?;
2016 let b = self.resolve_iter(&m.args[0])?;
2017 Ok(Iter::Zip(Box::new(a), Box::new(b)))
2018 }
2019 "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 20h ago2020 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2021 let k = self.expr(&m.args[0])?;
2022 Ok(Iter::Chunks {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2023 code,
2024 base,
2025 len,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2026 k: k.code,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2027 elem,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2028 mutable: name.ends_with("_mut"),
2029 })
2030 }
2031 "windows" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2032 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2033 let k = self.expr(&m.args[0])?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2034 Ok(Iter::Windows { code, base, len, k: k.code, elem })
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2035 }
2036 other => Err(format!(
2037 "iterator adaptor `.{other}()` is not implemented; it has \
2038 no index-loop equivalent here, and dropping it would \
2039 change which elements the loop visits"
2040 )),
2041 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2042 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago2043 Expr::Path(p) => {
2044 let n = path_name(&p.path);
2045 if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) {
2046 return Ok((*it).clone());
2047 }
2048 if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) {
2049 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
2050 }
2051 let v = self.expr(e)?;
2052 Ok(Iter::Elems {
2053 len: format!("{}.len", v.code),
2054 elem: elem_of(&v.ty),
2055 code: v.code,
2056 off: "0".into(),
2057 mutable: false,
2058 })
2059 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2060 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2061 // A `for` binding that is itself a window iterates that window,
2062 // not the whole container it points into.
2063 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 20h ago2064 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2065 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2066 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2067 Ok(Iter::Elems {
2068 len: format!("{}.len", v.code),
2069 elem: elem_of(&v.ty),
2070 code: v.code,
2071 off: "0".into(),
2072 mutable: false,
2073 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2074 }
2075 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2076 }
2077
2078 /// Bind a `for` pattern against a resolved iterator at index `i`.
2079 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
2080 match (p, it) {
2081 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
2082 self.bind_pattern(&t.elems[0], a, i)?;
2083 self.bind_pattern(&t.elems[1], b, i)
2084 }
2085 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
2086 if let Pat::Ident(id) = &t.elems[0] {
2087 let n = id.ident.to_string();
2088 // Rust's `enumerate` counts in `usize`.
2089 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
2090 self.bind(&n, Nim::Prim("uint".into()));
2091 }
2092 self.bind_pattern(&t.elems[1], inner, i)
2093 }
2094 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
2095 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
2096 ),
2097 (Pat::Wild(_), _) => Ok(()),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2098 // `for &byte in xs` — the `&` destructures the reference, which in
2099 // Nim is already the value.
2100 (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
2101 (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2102 (Pat::Ident(id), _) => {
2103 let name = id.ident.to_string();
2104 match it {
2105 Iter::Range { lo, ty, .. } => {
2106 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
2107 // The loop counts from zero; the range's own start is
2108 // added back so the binding has Rust's value and type.
2109 self.line(&format!(
2110 "let {}: {} = {}({}) + {}",
2111 ident(&name),
2112 t.render(),
2113 t.render(),
2114 i,
2115 lo
2116 ));
2117 self.bind(&name, t);
2118 Ok(())
2119 }
2120 Iter::Elems { code, off, elem, mutable, .. } => {
2121 let access = if off == "0" {
2122 format!("{}[{}]", code, i)
2123 } else {
2124 format!("{}[{} + {}]", code, off, i)
2125 };
2126 if *mutable {
2127 // An alias, not a copy: assigning through the
2128 // binding must reach the original element.
2129 self.bind_alias(
2130 &name,
2131 Alias::Value { code: access, ty: elem.clone() },
2132 );
2133 } else {
2134 let t = elem
2135 .clone()
2136 .ok_or("cannot infer the element type of this `for`")?;
2137 self.line(&format!(
2138 "let {}: {} = {}",
2139 ident(&name),
2140 t.render(),
2141 access
2142 ));
2143 self.bind(&name, t);
2144 }
2145 Ok(())
2146 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2147 Iter::Chunks { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2148 self.bind_alias(
2149 &name,
2150 Alias::Window {
2151 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2152 off: format!("({} + {} * int({}))", base, i, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2153 len: format!("int({})", k),
2154 elem: elem.clone(),
2155 },
2156 );
2157 Ok(())
2158 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2159 Iter::Windows { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2160 self.bind_alias(
2161 &name,
2162 Alias::Window {
2163 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2164 off: format!("({} + {})", base, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2165 len: format!("int({})", k),
2166 elem: elem.clone(),
2167 },
2168 );
2169 Ok(())
2170 }
2171 // Handled above: a zip or enumerate needs a tuple pattern,
2172 // and binding one name to the pair is not supported.
2173 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
2174 }
2175 }
2176 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2177 }
2178 }
2179
2180 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
2181 let Expr::Match(m) = e else { unreachable!() };
2182 let scrut = self.expr(&m.expr)?;
2183 let t = scrut
2184 .ty
2185 .clone()
2186 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2187 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2188 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2189
2190 // A `match` whose arms neither bind nor guard is a Nim `case`, which
2191 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
2192 // an if/elif chain, because Nim's `case` cannot destructure.
2193 let plain = m.arms.iter().all(|a| {
2194 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
2195 });
2196 if plain {
2197 self.match_case(m, &name, &t)
2198 } else {
2199 self.match_chain(m, &name, &t)
2200 }
2201 }
2202
2203 fn match_case(
2204 &mut self,
2205 m: &syn::ExprMatch,
2206 name: &str,
2207 t: &Nim,
2208 ) -> Result<(), String> {
2209 // A variant object is discriminated by its `kind` field.
2210 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
2211 self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2212
2213 let mut saw_wild = false;
2214 for arm in &m.arms {
2215 match &arm.pat {
2216 Pat::Wild(_) => {
2217 saw_wild = true;
2218 self.line("else:");
2219 }
2220 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2221 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2222 self.line(&format!("of {}:", labels.join(", ")));
2223 }
2224 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2225 self.arm_body(&arm.body)?;
2226 }
2227 if !saw_wild && !self.case_is_total(t, m) {
2228 // Rust checked exhaustiveness already, but Nim cannot always see
2229 // it -- an integer `case` needs every value covered -- so make the
2230 // unreachable arm explicit rather than leave a compile error.
2231 self.line("else:");
2232 self.line(" rsPanic(\"unreachable match arm\")");
2233 }
2234 Ok(())
2235 }
2236
2237 /// Whether a Nim `case` over this type is already total, in which case
2238 /// adding an `else` would be a compile error rather than a safety net.
2239 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
2240 let Nim::Named(n, _) = t else { return false };
2241 let Some(def) = self.enums.get(n) else { return false };
2242 def.variants.len() == m.arms.len()
2243 }
2244
2245 /// The if/elif form, for arms that bind or destructure.
2246 fn match_chain(
2247 &mut self,
2248 m: &syn::ExprMatch,
2249 name: &str,
2250 t: &Nim,
2251 ) -> Result<(), String> {
2252 let mut first = true;
2253 let mut closed = false;
2254 for arm in &m.arms {
2255 let (pat, guard) = match &arm.pat {
2256 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
2257 p => (p, None),
2258 };
2259 if guard.is_some() && binds(pat) {
2260 return Err("a `match` guard on a binding pattern is not \
2261 implemented yet"
2262 .into());
2263 }
2264 let test = self.pat_test(pat, name, t)?;
2265 let test = match (test, guard) {
2266 (Some(t), Some(g)) => {
2267 let g = self.expr(g)?;
2268 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2269 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2270 (None, Some(g)) => Some(self.expr(g)?.code),
2271 (t, None) => t,
2272 };
2273 match test {
2274 Some(test) => {
2275 self.line(&format!(
2276 "{} {}:",
2277 if first { "if" } else { "elif" },
2278 test
2279 ));
2280 first = false;
2281 }
2282 None => {
2283 // An irrefutable pattern: everything left falls here.
2284 if first {
2285 self.line("block:");
2286 } else {
2287 self.line("else:");
2288 }
2289 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2290 }
2291 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2292 self.indent += 1;
2293 self.push_scope();
2294 let before = self.out.len();
2295 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2296 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2297 self.arm_body_at(&arm.body, before)?;
2298 self.pop_scope();
2299 if closed {
2300 break;
2301 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2302 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2303 if !closed {
2304 // Rust proved this unreachable; Nim cannot see that, and leaving
2305 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2306 self.line("else:");
2307 self.line(" rsPanic(\"unreachable match arm\")");
2308 }
2309 Ok(())
2310 }
2311
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2312 /// The condition that selects this arm, or `None` if it always matches.
2313 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
2314 Ok(match p {
2315 Pat::Wild(_) => None,
2316 Pat::Ident(i) if i.subpat.is_none() => None,
2317 Pat::Or(o) => {
2318 let mut parts = Vec::new();
2319 for c in &o.cases {
2320 match self.pat_test(c, name, t)? {
2321 Some(x) => parts.push(x),
2322 None => return Ok(None),
2323 }
2324 }
2325 Some(format!("({})", parts.join(" or ")))
2326 }
2327 Pat::Lit(_) | Pat::Range(_) => {
2328 let labels = self.pat_labels(p, Some(t))?;
2329 Some(match p {
2330 Pat::Range(_) => format!("({} in {})", name, labels[0]),
2331 _ => format!("({} == {})", name, labels[0]),
2332 })
2333 }
2334 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
2335 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
2336 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
2337 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
2338 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
2339 _ => return Err("unsupported `match` pattern".into()),
2340 })
2341 }
2342
2343 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
2344 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
2345 let last = path_name(path);
2346 match last.as_str() {
2347 "Ok" => return Ok(format!("{name}.ok")),
2348 "Err" => return Ok(format!("(not {name}.ok)")),
2349 "Some" => return Ok(format!("{name}.has")),
2350 "None" => return Ok(format!("(not {name}.has)")),
2351 _ => {}
2352 }
2353 let Some((def, v)) = self.resolve_variant(path) else {
2354 return Err(format!(
2355 "`{last}` in a pattern is not a known enum variant; if it names \
2356 an enum declared in another module, that is not implemented yet"
2357 ));
2358 };
2359 if let Nim::Named(n, _) = t {
2360 if *n != def.name {
2361 return Err(format!(
2362 "pattern `{}::{}` does not match the scrutinee type `{}`",
2363 def.name, v, n
2364 ));
2365 }
2366 }
2367 Ok(if def.simple {
2368 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
2369 } else {
2370 format!("({}.kind == {})", name, def.kind_ident(&v))
2371 })
2372 }
2373
2374 /// Emit the `let`s that a pattern's bindings introduce.
2375 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
2376 match p {
2377 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
2378 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
2379 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
2380 Pat::Ident(i) if i.subpat.is_none() => {
2381 let b = i.ident.to_string();
2382 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
2383 self.bind(&b, t.clone());
2384 Ok(())
2385 }
2386 Pat::TupleStruct(ts) => {
2387 let fields = self.variant_fields(&ts.path, t)?;
2388 for (i, sub) in ts.elems.iter().enumerate() {
2389 let Some((fname, fty)) = fields.get(i) else {
2390 return Err(format!(
2391 "pattern binds {} field(s) but the variant has {}",
2392 ts.elems.len(),
2393 fields.len()
2394 ));
2395 };
2396 let access = format!("{}.{}", name, ident(fname));
2397 self.pat_bind(sub, &access, fty)?;
2398 }
2399 Ok(())
2400 }
2401 Pat::Struct(st) => {
2402 let fields = self.variant_fields(&st.path, t)?;
2403 for f in &st.fields {
2404 let syn::Member::Named(m) = &f.member else {
2405 return Err("unsupported struct pattern field".into());
2406 };
2407 let m = m.to_string();
2408 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
2409 return Err(format!("unknown field `{m}` in pattern"));
2410 };
2411 let access = format!("{}.{}", name, ident(fname));
2412 self.pat_bind(&f.pat, &access, fty)?;
2413 }
2414 Ok(())
2415 }
2416 _ => Err("unsupported `match` pattern".into()),
2417 }
2418 }
2419
2420 /// The payload fields a variant pattern destructures.
2421 fn variant_fields(
2422 &self,
2423 path: &syn::Path,
2424 t: &Nim,
2425 ) -> Result<Vec<(String, Nim)>, String> {
2426 let last = path_name(path);
2427 // `Ok`/`Err`/`Some` read the prelude's own field names.
2428 if let Nim::Named(n, a) = t {
2429 match (n.as_str(), last.as_str()) {
2430 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
2431 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
2432 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
2433 _ => {}
2434 }
2435 }
2436 let Some((def, v)) = self.resolve_variant(path) else {
2437 return Err(format!("`{last}` is not a known enum variant"));
2438 };
2439 Ok(def.get(&v).map(|v| v.fields.clone()).unwrap_or_default())
2440 }
2441
2442 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
2443 self.indent += 1;
2444 let before = self.out.len();
2445 self.indent -= 1;
2446 self.arm_body_at(body, before)
2447 }
2448
2449 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
2450 match body {
2451 Expr::Block(b) => self.nested_block(&b.block)?,
2452 other => {
2453 self.indent += 1;
2454 // An arm's value is the `match`'s value, so it is typed by
2455 // whatever the `match` is being assigned to -- without which
2456 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
2457 let want = self.target.clone().and_then(|(_, t)| t);
2458 let v = match (want, expressible(other)) {
2459 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
2460 _ => self.expr_stmt(other)?,
2461 };
2462 self.emit_tail(v);
2463 self.indent -= 1;
2464 }
2465 }
2466 if self.out.len() == before {
2467 self.indent += 1;
2468 self.line("discard");
2469 self.indent -= 1;
2470 }
2471 Ok(())
2472 }
2473
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2474 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
2475 match p {
2476 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
2477 Pat::Or(o) => {
2478 let mut out = Vec::new();
2479 for p in &o.cases {
2480 out.extend(self.pat_labels(p, expect)?);
2481 }
2482 Ok(out)
2483 }
2484 Pat::Range(r) => {
2485 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
2486 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
2487 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
2488 let op = match r.limits {
2489 syn::RangeLimits::HalfOpen(_) => "..<",
2490 syn::RangeLimits::Closed(_) => "..",
2491 };
2492 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
2493 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2494 Pat::Path(pp) => {
2495 if let Some((def, v)) = self.resolve_variant(&pp.path) {
2496 return Ok(vec![if def.simple {
2497 format!("{}.{}", ident(&def.name), ident(&v))
2498 } else {
2499 def.kind_ident(&v)
2500 }]);
2501 }
2502 Ok(vec![ident(&path_name(&pp.path))])
2503 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2504 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2505 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2506 .into()),
2507 }
2508 }
2509
2510 // --------------------------------------------------------- expressions
2511
2512 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
2513 self.expr_at(e, None)
2514 }
2515
2516 /// Lower `e`, with the type the surrounding code expects of it.
2517 ///
2518 /// Rust infers an unsuffixed integer literal's type from its context and
2519 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
2520 /// expected type down to the literal is what makes `let x: u8 = 255` and
2521 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
2522 /// widths silently diverge, which is exactly the class of bug this
2523 /// project refuses to ship.
2524 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
2525 match e {
2526 Expr::Lit(l) => self.lit_at(&l.lit, expect),
2527 Expr::Path(p) => {
2528 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2529 if name == "None" {
2530 return Ok(Val::new(self.none_of(expect), expect.cloned()));
2531 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2532 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
2533 // declared here. In Nim that is a constructor call.
2534 if p.path.segments.len() > 1 {
2535 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
2536 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
2537 if n == "FmtError" {
2538 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
2539 }
2540 }
2541 }
2542 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
2543 return Ok(Val::new(
2544 format!("{}()", ident(&name)),
2545 Some(Nim::Named(name.clone(), vec![])),
2546 ));
2547 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2548 // A unit enum variant used as a value: `Error::InvalidLength`.
2549 if let Some((def, v)) = self.resolve_variant(&p.path) {
2550 let ty = Some(Nim::Named(def.name.clone(), vec![]));
2551 return Ok(if def.simple {
2552 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), ty)
2553 } else {
2554 Val::new(format!("{}()", def.ctor_ident(&v)), ty)
2555 });
2556 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2557 // A `for` binding that stands for an element of the container
2558 // it came from: using it must read (and assigning through it
2559 // must write) that element, not a copy.
2560 if let Some(a) = self.lookup_alias(&name) {
2561 return Ok(match a {
2562 Alias::Value { code, ty } => Val::new(code, ty),
2563 // A window *is* a slice; as a value it is the view it
2564 // denotes, which is what Rust's `&[T]` means too.
2565 Alias::Window { code, off, len, elem } => Val::new(
2566 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
2567 elem.map(|e| Nim::OpenArray(Box::new(e))),
2568 ),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago2569 // An iterator is not a value here: it is consumed by a
2570 // `for`, or asked for its `.remainder()`.
2571 Alias::Iterator(_) => {
2572 return Err(format!(
2573 "`{name}` is an iterator; it can be iterated or asked \
2574 for its `remainder()`, but not used as a value"
2575 ))
2576 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2577 });
2578 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2579 if let Some(t) = self.lookup(&name) {
2580 return Ok(Val::new(ident(&name), Some(t)));
2581 }
2582 // A top-level function used as a value, e.g. passed to a
2583 // parameter of `impl Fn(..)` type.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2584 if let Some(k) = self.resolve_fn(&p.path) {
2585 let sig = &self.fns[&k];
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2586 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 20h ago2587 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 22h ago2588 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2589 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2590 }
2591 Expr::Paren(p) => {
2592 let v = self.expr_at(&p.expr, expect)?;
2593 Ok(Val::new(format!("({})", v.code), v.ty))
2594 }
2595 Expr::Group(g) => self.expr_at(&g.expr, expect),
2596 // `&x` is a value in Nim; `&mut x` in an argument position binds to
2597 // a `var` parameter, which is also just `x` at the call site.
2598 Expr::Reference(r) => self.expr_at(&r.expr, expect),
2599 Expr::Unary(u) => self.unary(u, expect),
2600 Expr::Binary(b) => self.binary(b, expect),
2601 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2602 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
2603 let Expr::Range(r) = &*i.index else { unreachable!() };
2604 let base = self.expr(&i.expr)?;
2605 let lo = match &r.start {
2606 Some(e) => format!("int({})", self.expr(e)?.code),
2607 None => "0".into(),
2608 };
2609 // Nim's `toOpenArray` takes an inclusive upper bound.
2610 let hi = match (&r.end, r.limits) {
2611 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2612 format!("int({}) - 1", self.expr(e)?.code)
2613 }
2614 (Some(e), syn::RangeLimits::Closed(_)) => {
2615 format!("int({})", self.expr(e)?.code)
2616 }
2617 (None, _) => format!("{}.len - 1", base.code),
2618 };
2619 let elem = elem_of(&base.ty)
2620 .ok_or("cannot infer the element type of this slice")?;
2621 Ok(Val::new(
2622 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
2623 Some(Nim::OpenArray(Box::new(elem))),
2624 ))
2625 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2626 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2627 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
2628 let idx = self.expr(&i.index)?;
2629 return Ok(Val::new(
2630 format!("{}[{} + int({})]", code, off, idx.code),
2631 elem,
2632 ));
2633 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2634 let base = self.expr(&i.expr)?;
2635 let idx = self.expr(&i.index)?;
2636 // Rust indexes with usize; Nim wants an `int`, and a `uint`
2637 // index is a type error there rather than a silent conversion.
2638 let idx_code = match &idx.ty {
2639 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
2640 _ => idx.code.clone(),
2641 };
2642 let elem = match base.ty.clone() {
2643 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
2644 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
2645 _ => None,
2646 };
2647 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
2648 }
2649 Expr::Field(f) => {
2650 let base = self.expr(&f.base)?;
2651 let name = match &f.member {
2652 syn::Member::Named(n) => n.to_string(),
2653 syn::Member::Unnamed(i) => format!("f{}", i.index),
2654 };
2655 let t = match &base.ty {
2656 Some(Nim::Named(s, _)) => self
2657 .structs
2658 .get(s)
2659 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
2660 .map(|(_, t)| t.clone()),
2661 _ => None,
2662 };
2663 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
2664 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2665 // `unsafe` is a permission marker, not a semantic change: it does
2666 // not alter what the enclosed operations mean. So the block is
2667 // transparent here, and each operation inside still goes through
2668 // the ordinary lowering -- and is still rejected if it has no
2669 // faithful mapping.
2670 Expr::Unsafe(u) => match single_expr(&u.block) {
2671 Some(e) => self.expr_at(e, expect),
2672 None => Err("an `unsafe` block used as a value must be a single \
2673 expression"
2674 .into()),
2675 },
2676 Expr::Closure(c) => self.closure(c, expect),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2677 Expr::Try(t) => self.try_op(t),
2678 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago2679 Expr::MethodCall(m) => self.method(m, expect),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago2680 Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
2681 // `vec![..]`'s elements take their type from the annotation on
2682 // the binding, exactly as Rust's would.
2683 let want = match expect {
2684 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
2685 _ => None,
2686 };
2687 let saved = std::mem::replace(&mut self.vec_expect, want.clone());
2688 let code = self.macro_call(&m.mac);
2689 self.vec_expect = saved;
2690 let code = code?;
2691 let ty = match want {
2692 Some(e) => Some(Nim::Seq(Box::new(e))),
2693 None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
2694 };
2695 Ok(Val::new(code, ty))
2696 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2697 Expr::Macro(m) => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago2698 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 22h ago2699 let code = self.macro_call(&m.mac)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago2700 // A formatter write is a statement that appends, not a value.
2701 let ty = if is_write { Some(Nim::Unit) } else { None };
2702 Ok(Val::new(code, ty))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2703 }
2704 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2705 if s.rest.is_some() {
2706 return Err("struct update syntax `..rest` is not implemented yet".into());
2707 }
2708 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
2709 // which is constructed positionally in Nim.
2710 if let Some((def, v)) = self.resolve_variant(&s.path) {
2711 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2712 let mut args = vec![String::new(); fields.len()];
2713 for f in &s.fields {
2714 let syn::Member::Named(m) = &f.member else {
2715 return Err("unsupported enum variant field".into());
2716 };
2717 let want = format!("{}_{}", v, m);
2718 let i = fields
2719 .iter()
2720 .position(|(n, _)| *n == want)
2721 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
2722 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
2723 }
2724 if let Some(i) = args.iter().position(|a| a.is_empty()) {
2725 return Err(format!(
2726 "`{}::{}` is missing field `{}`",
2727 def.name, v, fields[i].0
2728 ));
2729 }
2730 return Ok(Val::new(
2731 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
2732 Some(Nim::Named(def.name.clone(), vec![])),
2733 ));
2734 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2735 let name = path_name(&s.path);
2736 let mut parts = Vec::new();
2737 for f in &s.fields {
2738 let fname = match &f.member {
2739 syn::Member::Named(n) => n.to_string(),
2740 syn::Member::Unnamed(i) => format!("f{}", i.index),
2741 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago2742 let want = self
2743 .structs
2744 .get(&name)
2745 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
2746 .map(|(_, t)| t.clone());
2747 let v = self.expr_at(&f.expr, want.as_ref())?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2748 parts.push(format!("{}: {}", ident(&fname), v.code));
2749 }
2750 Ok(Val::new(
2751 format!("{}({})", ident(&name), parts.join(", ")),
2752 Some(Nim::Named(name, vec![])),
2753 ))
2754 }
2755 Expr::Array(a) => {
2756 let mut parts = Vec::new();
2757 let mut elem = match expect {
2758 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
2759 Some((**t).clone())
2760 }
2761 _ => None,
2762 };
2763 for e in &a.elems {
2764 let want = elem.clone();
2765 let v = self.expr_at(e, want.as_ref())?;
2766 elem = elem.or(v.ty.clone());
2767 parts.push(v.code);
2768 }
2769 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
2770 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
2771 }
2772 Expr::Repeat(r) => {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago2773 // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size
2774 // array from a `seq`, so the expected type decides which, and
2775 // an array needs its elements written out.
2776 let want_elem = match expect {
2777 Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => {
2778 Some((**e).clone())
2779 }
2780 _ => None,
2781 };
2782 let v = self.expr_at(&r.expr, want_elem.as_ref())?;
2783 if let Some(Nim::Array(n, _)) = expect {
2784 let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect();
2785 let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t)));
2786 return Ok(Val::new(format!("[{}]", elems.join(", ")), t));
2787 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2788 let n = self.expr(&r.len)?;
2789 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
2790 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
2791 }
2792 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
2793 Expr::Tuple(t) => {
2794 let mut parts = Vec::new();
2795 let mut tys = Vec::new();
2796 for e in &t.elems {
2797 let v = self.expr(e)?;
2798 tys.push(v.ty.clone());
2799 parts.push(v.code);
2800 }
2801 let ty = tys
2802 .iter()
2803 .cloned()
2804 .collect::<Option<Vec<_>>>()
2805 .map(Nim::Tuple);
2806 Ok(Val::new(format!("({})", parts.join(", ")), ty))
2807 }
2808 // `if` and `match` are expressions in both languages, but only
2809 // when every arm is itself a single expression.
2810 Expr::If(i) => self.if_expr(i, expect),
2811 Expr::Block(b) if b.block.stmts.len() == 1 => {
2812 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
2813 self.expr_at(e, expect)
2814 } else {
2815 Err("block expression with statements in value position is not implemented yet".into())
2816 }
2817 }
2818 other => Err(format!(
2819 "unsupported expression in value position: {}",
2820 expr_kind(other)
2821 )),
2822 }
2823 }
2824
2825 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
2826 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
2827 return Err(
2828 "an `if` used as a value must have an `else` and single-expression arms".into(),
2829 );
2830 };
2831 let c = self.expr(&i.cond)?;
2832 let t = self.expr_at(then, expect)?;
2833 let want = expect.cloned().or_else(|| t.ty.clone());
2834 let e = match &**els {
2835 Expr::Block(b) => match single_expr(&b.block) {
2836 Some(x) => self.expr_at(x, want.as_ref())?,
2837 None => return Err("an `if` used as a value must have single-expression arms".into()),
2838 },
2839 other => self.expr_at(other, want.as_ref())?,
2840 };
2841 let ty = t.ty.clone().or(e.ty.clone());
2842 Ok(Val::new(
2843 format!("(if {}: {} else: {})", c.code, t.code, e.code),
2844 ty,
2845 ))
2846 }
2847
2848 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
2849 match l {
2850 Lit::Int(i) => {
2851 let suffix = i.suffix();
2852 if let Some(why) = ty::rejected(suffix) {
2853 return Err(format!("integer literal `{}`: {}", i, why));
2854 }
2855 let digits = i.base10_digits().to_string();
2856 // Rust's default for an unconstrained integer literal is i32.
2857 // Nim's is `int` (64-bit). Making the width explicit is what
2858 // keeps overflow behaviour the same on both sides.
2859 let t = if suffix.is_empty() {
2860 match expect {
2861 Some(t) if t.is_integer() => t.clone(),
2862 // Rust's fallback for an otherwise-unconstrained
2863 // integer literal.
2864 _ => Nim::Prim("int32".into()),
2865 }
2866 } else {
2867 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
2868 };
2869 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
2870 }
2871 Lit::Float(f) => {
2872 let t = match f.suffix() {
2873 "" => match expect {
2874 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
2875 _ => Nim::Prim("float64".into()),
2876 },
2877 "f64" => Nim::Prim("float64".into()),
2878 "f32" => Nim::Prim("float32".into()),
2879 s => return Err(format!("unknown float suffix `{s}`")),
2880 };
2881 let d = f.base10_digits();
2882 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
2883 Ok(Val::new(d, Some(t)))
2884 }
2885 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
2886 Lit::Str(s) => Ok(Val::new(
2887 fmt::nim_str(&s.value()),
2888 Some(Nim::Prim("string".into())),
2889 )),
2890 Lit::Char(c) => Ok(Val::new(
2891 format!("Rune({})", c.value() as u32),
2892 Some(Nim::Prim("Rune".into())),
2893 )),
2894 Lit::Byte(b) => Ok(Val::new(
2895 format!("{}'u8", b.value()),
2896 Some(Nim::Prim("uint8".into())),
2897 )),
2898 Lit::ByteStr(b) => {
2899 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
2900 Ok(Val::new(
2901 format!("@[{}]", bytes.join(", ")),
2902 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
2903 ))
2904 }
2905 other => Err(format!("unsupported literal: {other:?}")),
2906 }
2907 }
2908
2909 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
2910 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
2911 // the positive half of the range before the negation runs. Folding the
2912 // sign into the literal keeps `i8::MIN` and friends expressible.
2913 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
2914 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
2915 let v = self.lit_at(&l.lit, expect)?;
2916 return Ok(Val::new(format!("-{}", v.code), v.ty));
2917 }
2918 }
2919 let v = self.expr_at(&u.expr, expect)?;
2920 match u.op {
2921 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
2922 // Rust's `!` is logical on bool and bitwise-complement on integers.
2923 // Nim spells those `not` and `not` as well, so one mapping covers
2924 // both — but only because Nim overloads `not` the same way.
2925 UnOp::Not(_) => Ok(Val::new(format!("(not {})", v.code), v.ty)),
2926 UnOp::Deref(_) => Ok(v),
2927 _ => Err("unsupported unary operator".into()),
2928 }
2929 }
2930
2931 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
2932 // A comparison's operands are unrelated to the `bool` it produces, so
2933 // the outer expectation is not passed through to them.
2934 let down = match b.op {
2935 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
2936 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
2937 _ => expect,
2938 };
2939 let mut l = self.expr_at(&b.left, down)?;
2940 // Rust unifies the two operand types; propagating whichever side is
2941 // known to the other reproduces that, and disagreement then surfaces
2942 // as a Nim type error rather than as a silent width change.
2943 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
2944 if l.ty.is_none() && r.ty.is_some() {
2945 l = self.expr_at(&b.left, r.ty.as_ref())?;
2946 }
2947 let r = std::mem::replace(&mut r, Val::untyped(""));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago2948 // A binary operator on a user type goes to that type's own impl.
2949 if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) {
2950 let want = self.op_param(&l.ty, binary_symbol(&b.op));
2951 let r = self.expr_at(&b.right, want.as_ref())?;
2952 let ret = self
2953 .methods
2954 .get(&(
2955 type_name(l.ty.as_ref().unwrap()),
2956 op_method(binary_symbol(&b.op)).to_string(),
2957 ))
2958 .map(|s| s.ret.clone());
2959 return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret));
2960 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago2961 let op = self.bin_op(&b.op, &l, &r)?;
2962 let ty = match b.op {
2963 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
2964 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
2965 // Rust's shift takes its result type from the *left* operand, and
2966 // the right may be a different width entirely.
2967 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
2968 _ => l.ty.clone().or(r.ty.clone()),
2969 };
2970 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
2971 }
2972
2973 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
2974 Ok(match op {
2975 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
2976 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
2977 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
2978 BinOp::Div(_) | BinOp::DivAssign(_) => {
2979 // Nim spells integer division `div`. Both languages truncate
2980 // toward zero, so once the right operator is chosen the
2981 // semantics match, including for negative operands.
2982 let t = l.ty.clone().or(r.ty.clone()).ok_or(
2983 "cannot tell integer from float division here; annotate the operands",
2984 )?;
2985 if t.is_integer() { "div" } else { "/" }
2986 }
2987 BinOp::Rem(_) | BinOp::RemAssign(_) => {
2988 let t = l.ty.clone().or(r.ty.clone()).ok_or(
2989 "cannot tell integer from float remainder here; annotate the operands",
2990 )?;
2991 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
2992 }
2993 BinOp::And(_) => "and",
2994 BinOp::Or(_) => "or",
2995 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
2996 // bools, exactly as Rust's `&`/`|`/`^` are.
2997 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
2998 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
2999 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
3000 // Settled empirically: Nim's `shr` on a signed integer is
3001 // arithmetic, matching Rust. See DESIGN.md.
3002 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
3003 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
3004 BinOp::Eq(_) => "==",
3005 BinOp::Ne(_) => "!=",
3006 BinOp::Lt(_) => "<",
3007 BinOp::Le(_) => "<=",
3008 BinOp::Gt(_) => ">",
3009 BinOp::Ge(_) => ">=",
3010 other => return Err(format!("unsupported binary operator {other:?}")),
3011 })
3012 }
3013
3014 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
3015 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3016 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3017 let from = v.ty.clone().ok_or_else(|| {
3018 format!(
3019 "cannot lower `as {}`: the source type is unknown, and `as` \
3020 truncates, so the source width decides the result",
3021 to.render()
3022 )
3023 })?;
3024
3025 let code = match (&from, &to) {
3026 (f, t) if f.is_integer() && t.is_integer() => {
3027 // Rust's `as` between integers is a pure bit-width truncation
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 20h ago3028 // or sign-extension, never a range check. `cast` says exactly
3029 // that. (Nim's `T(x)` turns out to truncate here as well --
3030 // see DESIGN.md item 5 -- but `cast` is the spelling that
3031 // means it rather than the one that happens to agree.)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3032 format!("cast[{}]({})", t.render(), v.code)
3033 }
3034 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
3035 format!("{}({})", p, v.code)
3036 }
3037 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
3038 format!("{}(ord({}))", t.render(), v.code)
3039 }
3040 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
3041 format!("cast[{}](int32({}))", t.render(), v.code)
3042 }
3043 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
3044 format!("Rune(int32({}))", v.code)
3045 }
3046 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
3047 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
3048 // Rust saturates float->int casts; Nim rounds and range-errors.
3049 // Not the same operation, so it is refused rather than mapped.
3050 return Err(format!(
3051 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
3052 no faithful mapping is implemented",
3053 t.render()
3054 ));
3055 }
3056 (f, t) => {
3057 return Err(format!(
3058 "unsupported cast from `{}` to `{}`",
3059 f.render(),
3060 t.render()
3061 ))
3062 }
3063 };
3064 Ok(Val::new(code, Some(to)))
3065 }
3066
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3067 /// Rust's `?`: return early on the error branch, otherwise yield the value.
3068 ///
3069 /// The early return is statements, not an expression, so they are emitted
3070 /// ahead of the line being built. Every caller lowers its sub-expressions
3071 /// 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 20h ago3072 /// The container, start offset, length and element type an expression
3073 /// denotes as a slice. A window alias contributes its own offset, so
3074 /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
3075 /// into the original buffer rather than through a rebuilt view.
3076 fn slice_parts(
3077 &mut self,
3078 e: &Expr,
3079 ) -> Result<(String, String, String, Option<Nim>), String> {
3080 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
3081 return Ok((code, off, len, elem));
3082 }
3083 let v = self.expr(e)?;
3084 let len = format!("{}.len", v.code);
3085 Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
3086 }
3087
3088 /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
3089 fn map_closure(
3090 &mut self,
3091 what: &str,
3092 recv: &Val,
3093 kind: &str,
3094 targs: &[Nim],
3095 c: &syn::ExprClosure,
3096 ) -> Result<Val, String> {
3097 if c.capture.is_some() {
3098 return Err("a `move` closure captures by value; Nim's closures \
3099 capture by reference, and the two are not the same"
3100 .into());
3101 }
3102 if c.inputs.len() != 1 {
3103 return Err(format!("`.{what}()` takes a one-argument closure"));
3104 }
3105 let pname = match &c.inputs[0] {
3106 Pat::Ident(i) => i.ident.to_string(),
3107 Pat::Wild(_) => "unused0".into(),
3108 _ => return Err("only plain identifier closure parameters are supported".into()),
3109 };
3110
3111 let is_opt = kind == "Option";
3112 let tmp = self.fresh("Map");
3113 let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
3114 self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
3115
3116 let body = match &*c.body {
3117 Expr::Block(b) => single_expr(&b.block)
3118 .ok_or("a closure body with statements is not implemented yet")?,
3119 other => other,
3120 };
3121 self.push_scope();
3122 // The parameter names the payload itself, so a view stays a view.
3123 self.bind_alias(
3124 &pname,
3125 Alias::Value {
3126 code: format!("{}.val", tmp),
3127 ty: Some(targs[0].clone()),
3128 },
3129 );
3130 let v = self.expr(body)?;
3131 self.pop_scope();
3132
3133 let inner = v
3134 .ty
3135 .clone()
3136 .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
3137 // `and_then`'s closure already returns the wrapped type; `map`'s does
3138 // not and has to be re-wrapped.
3139 let (test, some_branch, none_branch, out_ty) = if is_opt {
3140 let out = if what == "map" {
3141 Nim::Named("Option".into(), vec![inner.clone()])
3142 } else {
3143 inner.clone()
3144 };
3145 let body_code = if what == "map" {
3146 format!("rsSome[{}]({})", inner.render(), v.code)
3147 } else {
3148 v.code.clone()
3149 };
3150 (
3151 format!("{}.has", tmp),
3152 body_code,
3153 format!("rsNone[{}]()", elem_arg(&out).render()),
3154 out,
3155 )
3156 } else {
3157 let e = targs[1].clone();
3158 let out = if what == "map" {
3159 Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
3160 } else {
3161 inner.clone()
3162 };
3163 let ok_ty = elem_arg(&out);
3164 let body_code = if what == "map" {
3165 format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
3166 } else {
3167 v.code.clone()
3168 };
3169 (
3170 format!("{}.ok", tmp),
3171 body_code,
3172 format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
3173 out,
3174 )
3175 };
3176 Ok(Val::new(
3177 format!("(if {}: {} else: {})", test, some_branch, none_branch),
3178 Some(out_ty),
3179 ))
3180 }
3181
3182 /// `|x| x + 1` -> a Nim anonymous proc.
3183 ///
3184 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
3185 /// A `move` closure captures by value, which is a different thing, so it
3186 /// is rejected rather than lowered to the same construct.
3187 fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
3188 if c.capture.is_some() {
3189 return Err("a `move` closure captures by value; Nim's closures \
3190 capture by reference, and the two are not the same"
3191 .into());
3192 }
3193 let want: Option<&Vec<Nim>> = match expect {
3194 Some(Nim::Proc(a, _)) => Some(a),
3195 _ => None,
3196 };
3197
3198 self.push_scope();
3199 let mut parts = Vec::new();
3200 let mut ptys = Vec::new();
3201 for (i, p) in c.inputs.iter().enumerate() {
3202 let (name, ann) = match p {
3203 Pat::Ident(id) => (id.ident.to_string(), None),
3204 Pat::Type(t) => match &*t.pat {
3205 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
3206 _ => return Err("only plain identifier closure parameters are supported".into()),
3207 },
3208 Pat::Wild(_) => (format!("unused{i}"), None),
3209 _ => return Err("only plain identifier closure parameters are supported".into()),
3210 };
3211 let t = ann
3212 .or_else(|| want.and_then(|w| w.get(i).cloned()))
3213 .ok_or_else(|| {
3214 format!(
3215 "cannot infer the type of closure parameter `{name}`; \
3216 annotate it"
3217 )
3218 })?;
3219 parts.push(format!("{}: {}", ident(&name), t.render()));
3220 self.bind(&name, t.clone());
3221 ptys.push(t);
3222 }
3223
3224 let ret_ann = match &c.output {
3225 ReturnType::Default => None,
3226 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
3227 };
3228 let body = match &*c.body {
3229 Expr::Block(b) => single_expr(&b.block)
3230 .ok_or("a closure body with statements is not implemented yet")?,
3231 other => other,
3232 };
3233 let v = self.expr_at(body, ret_ann.as_ref())?;
3234 self.pop_scope();
3235
3236 let ret = ret_ann
3237 .or_else(|| v.ty.clone())
3238 .ok_or("cannot infer a closure's return type; annotate it")?;
3239 Ok(Val::new(
3240 format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
3241 Some(Nim::Proc(ptys, Box::new(ret))),
3242 ))
3243 }
3244
3245 /// Lower a block's statements at the current indentation, without opening
3246 /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
3247 /// of its own in the generated code.
3248 fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
3249 self.push_scope();
3250 let tail = self.block_body(b)?;
3251 self.emit_tail(tail);
3252 self.pop_scope();
3253 Ok(())
3254 }
3255
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3256 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
3257 if self.in_loop_cond {
3258 return Err("`?` in a loop condition is not implemented yet: the \
3259 early-return it expands to would be evaluated once, \
3260 before the loop, rather than on each iteration"
3261 .into());
3262 }
3263 let v = self.expr(&t.expr)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3264 if self.fmt_param.is_some() {
3265 // Writing into a string cannot fail, so `?` on a formatter write
3266 // is a no-op. `?` on anything else can fail, and `format!` panics
3267 // when a formatting impl returns an error -- so that is what the
3268 // error branch does here, with std's own message.
3269 if v.ty.as_ref() == Some(&Nim::Unit) {
3270 return Ok(v);
3271 }
3272 if let Some(Nim::Named(n, a)) = v.ty.clone() {
3273 if n == "Result" && a.len() == 2 {
3274 let tmp = self.fresh("Fmt");
3275 self.line(&format!(
3276 "let {}: {} = {}",
3277 tmp,
3278 Nim::Named(n, a.clone()).render(),
3279 v.code
3280 ));
3281 self.line(&format!("if not {}.ok:", tmp));
3282 self.line(
3283 " rsPanic(\"a formatting trait implementation returned an error\")",
3284 );
3285 return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone())));
3286 }
3287 }
3288 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3289 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
3290 // An `Option`/`Result` of a view: the check is emitted here and the
3291 // view itself survives as an alias, since it has no value form.
3292 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
3293 let err = v.guard_err.clone().ok_or(
3294 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
3295 )?;
3296 let Nim::Named(n, ra) = &ret else {
3297 return Err(format!("`?` in a function returning `{}`", ret.render()));
3298 };
3299 if n != "Result" || ra.len() != 2 {
3300 return Err(format!("`?` in a function returning `{}`", ret.render()));
3301 }
3302 self.line(&format!("if not {}:", guard));
3303 self.line(&format!(
3304 " return rsErr[{}, {}]({})",
3305 ra[0].render(),
3306 ra[1].render(),
3307 err
3308 ));
3309 let mut out = Val::new(String::new(), None);
3310 out.window = Some(w);
3311 return Ok(out);
3312 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3313 let vt = v.ty.clone().ok_or(
3314 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
3315 )?;
3316 let ret = self
3317 .ret
3318 .clone()
3319 .ok_or("`?` outside a function with a return type")?;
3320 let tmp = self.fresh("Try");
3321 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
3322
3323 match (&vt, &ret) {
3324 (Nim::Named(a, ai), Nim::Named(b, bi))
3325 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
3326 {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3327 // Rust inserts a `From::from` on the error here. Where the
3328 // types differ we call the crate's own `impl From`; we never
3329 // assume the conversion is the identity.
3330 let err = if ai[1] == bi[1] {
3331 format!("{}.err", tmp)
3332 } else {
3333 let key = (type_name(&ai[1]), type_name(&bi[1]));
3334 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
3335 format!(
3336 "`?` needs `From<{}> for {}` to convert the error, and \
3337 no such `impl` is in scope; assuming the conversion is \
3338 the identity would be a guess",
3339 key.0, key.1
3340 )
3341 })?;
3342 format!("{}({}.err)", f, tmp)
3343 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3344 self.line(&format!("if not {}.ok:", tmp));
3345 self.line(&format!(
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3346 " return rsErr[{}, {}]({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3347 bi[0].render(),
3348 bi[1].render(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3349 err
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3350 ));
3351 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3352 }
3353 (Nim::Named(a, ai), Nim::Named(b, bi))
3354 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
3355 {
3356 self.line(&format!("if not {}.has:", tmp));
3357 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
3358 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3359 }
3360 _ => Err(format!(
3361 "`?` on `{}` in a function returning `{}` is not a supported \
3362 combination",
3363 vt.render(),
3364 ret.render()
3365 )),
3366 }
3367 }
3368
3369 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 22h ago3370 let Expr::Path(p) = &*c.func else {
3371 return Err("only calls to named functions are supported".into());
3372 };
3373 let name = path_name(&p.path);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3374 let target = self.resolve_fn(&p.path);
3375 let ptys: Vec<Nim> = target
3376 .as_ref()
3377 .and_then(|k| self.fns.get(k))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3378 .map(|s| s.params.clone())
3379 .unwrap_or_default();
3380 let mut args = Vec::new();
3381 for (i, a) in c.args.iter().enumerate() {
3382 let want = ptys.get(i).cloned();
3383 args.push(self.expr_at(a, want.as_ref())?);
3384 }
3385 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
3386
3387 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3388 // `Ok`/`Err` must name the *whole* Result type, not just the half
3389 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
3390 match name.as_str() {
3391 "Some" => {
3392 let inner = match expect {
3393 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
3394 _ => {
3395 return Err("`Some(..)` needs a known `Option<T>` type here; \
3396 annotate the binding or the return type"
3397 .into())
3398 }
3399 };
3400 return Ok(Val::new(
3401 format!("rsSome[{}]({})", inner, codes.join(", ")),
3402 expect.cloned(),
3403 ));
3404 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3405 "Ok" if self.fmt_param.is_some()
3406 && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) =>
3407 {
3408 // `Ok(())` ends a `fmt` body: nothing more is written.
3409 return Ok(Val::new(String::new(), Some(Nim::Unit)));
3410 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3411 "Ok" | "Err" => {
3412 let (t, e) = match expect {
3413 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
3414 (a[0].render(), a[1].render())
3415 }
3416 _ => {
3417 return Err(format!(
3418 "`{name}(..)` needs a known `Result<T, E>` type here; \
3419 annotate the binding or the return type"
3420 ))
3421 }
3422 };
3423 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
3424 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
3425 return Ok(Val::new(
3426 format!("{}[{}, {}]({})", ctor, t, e, arg),
3427 expect.cloned(),
3428 ));
3429 }
3430 _ => {}
3431 }
3432
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3433 // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's
3434 // object constructor names its fields even when Rust's does not.
3435 if let Some(fields) = self.structs.get(&name).cloned() {
3436 if fields.len() == c.args.len() {
3437 let mut parts = Vec::new();
3438 for (i, a) in c.args.iter().enumerate() {
3439 let v = self.expr_at(a, Some(&fields[i].1))?;
3440 parts.push(format!("{}: {}", ident(&fields[i].0), v.code));
3441 }
3442 return Ok(Val::new(
3443 format!("{}({})", ident(&name), parts.join(", ")),
3444 Some(Nim::Named(name.clone(), vec![])),
3445 ));
3446 }
3447 }
3448
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago3449 // `u32::from(b)`: `From` between primitives is lossless by definition
3450 // -- it is the widening direction only -- so a plain Nim conversion is
3451 // exact. (The truncating direction is `as`, which is `cast`.)
3452 if name == "from" && codes.len() == 1 {
3453 if let Some(q) = p.path.segments.iter().rev().nth(1) {
3454 if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) {
3455 return Ok(Val::new(
3456 format!("{}({})", t, codes[0]),
3457 Some(Nim::Prim(t)),
3458 ));
3459 }
3460 }
3461 }
3462
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3463 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
3464 // string view; no copy, no validation, same memory.
3465 if name == "from_utf8_unchecked" && codes.len() == 1 {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3466 // `String::from_utf8_unchecked(v)` takes ownership and yields an
3467 // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields
3468 // a view. Same name, different operations -- the qualifier says
3469 // which, and an unqualified call is ambiguous.
3470 let q = p
3471 .path
3472 .segments
3473 .iter()
3474 .rev()
3475 .nth(1)
3476 .map(|s| s.ident.to_string());
3477 return match q.as_deref() {
3478 Some("String") => Ok(Val::new(
3479 format!("rsStringOf({})", codes[0]),
3480 Some(Nim::Prim("string".into())),
3481 )),
3482 Some("str") => Ok(Val::new(
3483 format!("rsStrView({})", codes[0]),
3484 Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
3485 )),
3486 _ => Err(
3487 "`from_utf8_unchecked` must be written as `str::..` (a \
3488 borrowed view) or `String::..` (an owned string); the two \
3489 are different operations"
3490 .into(),
3491 ),
3492 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3493 }
3494
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3495 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
3496 if let Some((def, v)) = self.resolve_variant(&p.path) {
3497 return Ok(Val::new(
3498 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
3499 Some(Nim::Named(def.name.clone(), vec![])),
3500 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3501 }
3502
3503 // A bare path that names a primitive type is Rust's tuple-struct-like
3504 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3505 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
3506 // is invoked.
3507 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
3508 return Ok(Val::new(
3509 format!("{}({})", ident(&name), codes.join(", ")),
3510 Some((*ret).clone()),
3511 ));
3512 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago3513 // `Adler32::new()` / `Adler32::default()`: a method called through
3514 // its type rather than through a receiver.
3515 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3516 // `Self::new()` inside an `impl` names the type being implemented.
3517 let q = if q == "Self" {
3518 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3519 } else {
3520 q
3521 };
3522 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
3523 let ret = sig.ret.clone();
3524 let nim = self
3525 .statics
3526 .get(&(q.clone(), name.clone()))
3527 .cloned()
3528 .unwrap_or_else(|| ident(&name));
3529 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
3530 }
3531 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3532 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 21h ago3533 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 22h ago3534 return Err(format!(
3535 "call to unknown function `{name}`; only functions defined in \
3536 this file and the supported standard-library subset can be lowered"
3537 ));
3538 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3539 let nim = match &target {
3540 Some((m, n)) => self.fn_name(m, n),
3541 None => ident(&name),
3542 };
3543 Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3544 }
3545
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3546 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 22h ago3547 let name = m.method.to_string();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago3548 // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield.
3549 if name == "remainder" && m.args.is_empty() {
3550 if let Expr::Path(p) = &*m.receiver {
3551 if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) {
3552 if let Iter::Chunks { code, base, len, k, elem, .. } = &*it {
3553 let kept = format!("(({} div int({})) * int({}))", len, k, k);
3554 let mut v = Val::new(
3555 String::new(),
3556 elem.clone().map(|e| Nim::OpenArray(Box::new(e))),
3557 );
3558 v.window = Some(Alias::Window {
3559 code: code.clone(),
3560 off: format!("({} + {})", base, kept),
3561 len: format!("({} - {})", len, kept),
3562 elem: elem.clone(),
3563 });
3564 return Ok(v);
3565 }
3566 return Err(
3567 "`.remainder()` is only defined for a `chunks_exact` iterator".into(),
3568 );
3569 }
3570 }
3571 return Err("`.remainder()` needs an iterator bound by `let`".into());
3572 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3573 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
3574 match name.as_str() {
3575 "len" => {
3576 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
3577 }
3578 "is_empty" => {
3579 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
3580 }
3581 other => {
3582 return Err(format!(
3583 "`.{other}()` on a slice window from `chunks_exact`/\
3584 `windows` is not implemented; only indexing and \
3585 `len()` are"
3586 ))
3587 }
3588 }
3589 }
3590 let recv = self.expr(&m.receiver)?;
3591 let rt0 = recv.ty.clone();
3592
3593// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
3594 // way to put a view in an object, so instead of materialising an
3595 // Option the view and its validity condition travel together until
3596 // an `ok_or`/`?`/`unwrap` resolves them.
3597 if matches!(name.as_str(), "get" | "get_mut")
3598 && matches!(m.args.first(), Some(Expr::Range(_)))
3599 {
3600 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 20h ago3601 let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3602 let lo = match &r.start {
3603 Some(e) => format!("int({})", self.expr(e)?.code),
3604 None => "0".into(),
3605 };
3606 let len = match (&r.end, r.limits) {
3607 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
3608 format!("(int({}) - {})", self.expr(e)?.code, lo)
3609 }
3610 (Some(e), syn::RangeLimits::Closed(_)) => {
3611 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
3612 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3613 (None, _) => format!("({} - {})", blen, lo),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3614 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3615 // Hoisted, so the bounds are computed once -- as Rust computes
3616 // them once -- and cannot be re-evaluated later in a scope where
3617 // the names they mention have been shadowed by a loop pattern.
3618 let off_t = self.fresh("Off");
3619 let len_t = self.fresh("Len");
3620 self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
3621 self.line(&format!("let {}: int = {}", len_t, len));
3622 let elem = belem
3623 .or_else(|| elem_of(&rt0))
3624 .ok_or("cannot infer the element type of this slice")?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3625 let mut v = Val::new(
3626 String::new(),
3627 Some(Nim::Named(
3628 "Option".into(),
3629 vec![Nim::OpenArray(Box::new(elem.clone()))],
3630 )),
3631 );
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3632 v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3633 v.window = Some(Alias::Window {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3634 code,
3635 off: off_t,
3636 len: len_t,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3637 elem: Some(elem),
3638 });
3639 return Ok(v);
3640 }
3641
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3642 // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
3643 // parameter type comes from the receiver, so they are handled before
3644 // the arguments are lowered. The closure is expanded inline, with its
3645 // parameter aliased to the payload: that keeps the whole thing an
3646 // expression and avoids handing a view to a generic proc.
3647 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
3648 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
3649 (recv.ty.clone(), &m.args[0])
3650 {
3651 if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
3652 {
3653 return self.map_closure(&name, &recv, &kind, &targs, c);
3654 }
3655 }
3656 }
3657
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3658 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
3659 // own type; `v.push(e)` takes the element type.
3660 let arg_want = match (name.as_str(), &recv.ty) {
3661 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
3662 (_, t) => t.clone(),
3663 };
3664 let mut args = Vec::new();
3665 for a in &m.args {
3666 args.push(self.expr_at(a, arg_want.as_ref())?);
3667 }
3668 let a0 = args.first().map(|a| a.code.clone());
3669 let rt = recv.ty.clone();
3670
3671 let (code, ty) = match name.as_str() {
3672 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
3673 // explicit so that a `usize` binding type-checks on the Nim side.
3674 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
3675 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
3676 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
3677 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
3678 | "into_iter" => (recv.code.clone(), rt.clone()),
3679 "unwrap" | "expect" => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3680 // Expanded inline rather than called as a generic proc: when
3681 // the payload is a view, Nim can only borrow from a path
3682 // expression, which a proc body containing the panic is not.
3683 let (kind, inner) = match &rt {
3684 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
3685 ("Option", a[0].clone())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3686 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3687 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
3688 ("Result", a[0].clone())
3689 }
3690 _ => {
3691 return Err(format!(
3692 "`.{name}()` needs a known `Option`/`Result` receiver type"
3693 ))
3694 }
3695 };
3696 if self.in_loop_cond {
3697 return Err(format!(
3698 "`.{name}()` in a loop condition is not implemented yet: the \
3699 check it expands to would run once, before the loop"
3700 ));
3701 }
3702 let tmp = self.fresh("Unwrap");
3703 let rty = rt.clone().unwrap();
3704 self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code));
3705 let (test, msg) = if kind == "Option" {
3706 (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value")
3707 } else {
3708 (format!("{}.ok", tmp), "called `Result::unwrap()` on an `Err` value")
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3709 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3710 let msg = if name == "expect" {
3711 args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg))
3712 } else {
3713 fmt::nim_str(msg)
3714 };
3715 self.line(&format!("if not {}:", test));
3716 self.line(&format!(" rsPanic({})", msg));
3717 // If the payload is a view, hand back an alias rather than a
3718 // value: Nim will not let a `let` borrow out of a local, and a
3719 // view is a reference anyway, so there is nothing to bind.
3720 // `{tmp}.val` is a plain field access, so substituting it at
3721 // each use re-evaluates nothing.
3722 if matches!(inner, Nim::OpenArray(_)) {
3723 let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone()));
3724 v.window = Some(Alias::Value {
3725 code: format!("{}.val", tmp),
3726 ty: Some(inner),
3727 });
3728 return Ok(v);
3729 }
3730 (format!("{}.val", tmp), Some(inner))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3731 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3732 "ok_or" if recv.guard.is_some() => {
3733 let e = args.first().ok_or("`ok_or` takes one argument")?;
3734 let ety = e.ty.clone();
3735 let mut v = recv.clone();
3736 v.guard_err = Some(e.code.clone());
3737 v.ty = match (&recv.ty, ety) {
3738 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
3739 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
3740 }
3741 _ => None,
3742 };
3743 return Ok(v);
3744 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago3745 "ok_or" => {
3746 let inner = match &rt {
3747 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
3748 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
3749 };
3750 let e = args.first().ok_or("`ok_or` takes one argument")?;
3751 let ety = e
3752 .ty
3753 .clone()
3754 .ok_or("`ok_or` needs a known error type for its argument")?;
3755 (
3756 format!(
3757 "rsOkOr[{}, {}]({}, {})",
3758 inner.render(),
3759 ety.render(),
3760 recv.code,
3761 e.code
3762 ),
3763 Some(Nim::Named("Result".into(), vec![inner, ety])),
3764 )
3765 }
3766 "unwrap_or" => {
3767 let inner = match &rt {
3768 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
3769 Some(a[0].clone())
3770 }
3771 _ => None,
3772 };
3773 (
3774 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
3775 inner,
3776 )
3777 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3778 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
3779 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
3780 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
3781 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
3782
3783 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
3784 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
3785 // Nim raises OverflowDefect, so the operation is routed through
3786 // the unsigned view of the same width, which is what Rust's
3787 // wrapping_* is defined to compute.
3788 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
3789 let op = match name.as_str() {
3790 "wrapping_add" => "+",
3791 "wrapping_sub" => "-",
3792 _ => "*",
3793 };
3794 let t = rt.clone().ok_or_else(|| {
3795 format!("`{name}` needs a known receiver type to pick the wrapping width")
3796 })?;
3797 if !t.is_integer() {
3798 return Err(format!("`{name}` on a non-integer type"));
3799 }
3800 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
3801 if t.is_unsigned() {
3802 (format!("({} {} {})", recv.code, op, arg), Some(t))
3803 } else {
3804 let u = unsigned_peer(&t)?;
3805 (
3806 format!(
3807 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
3808 t.render(), u, recv.code, op, u, arg
3809 ),
3810 Some(t),
3811 )
3812 }
3813 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3814 // Inside a formatting impl, a write through the `Formatter` *is*
3815 // 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 20h ago3816 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => {
3817 let a = args.first().ok_or("`write_str` takes one argument")?;
3818 // A `&str` argument is a character view, not a Nim string.
3819 let text = match &a.ty {
3820 Some(Nim::Prim(p)) if p == "string" => a.code.clone(),
3821 _ => format!("rsDisplay({})", a.code),
3822 };
3823 (format!("result.add({})", text), Some(Nim::Unit))
3824 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3825 "abs" => (format!("abs({})", recv.code), rt.clone()),
3826 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
3827 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
3828 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
3829 "as_bytes" | "into_bytes" => (
3830 format!("rsBytes({})", recv.code),
3831 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
3832 ),
3833
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3834 "into" => {
3835 // `.into()` resolves through the `impl From` declarations, and
3836 // needs the target type to pick one.
3837 let from = rt
3838 .clone()
3839 .ok_or("`.into()` needs a known receiver type")?;
3840 let to = expect
3841 .ok_or("`.into()` needs a known target type; annotate the binding")?;
3842 let key = (type_name(&from), type_name(to));
3843 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
3844 format!(
3845 "no `impl From<{}> for {}` in this file, so `.into()` has \
3846 no conversion to call",
3847 key.0, key.1
3848 )
3849 })?;
3850 (format!("{}({})", f, recv.code), Some(to.clone()))
3851 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3852 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3853 // A method defined in this file via `impl`, found by the
3854 // receiver's type rather than by name alone.
3855 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago3856 let sig = key
3857 .as_ref()
3858 .and_then(|k| self.methods.get(k))
3859 .map(|s| s.ret.clone());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3860 if let Some(ret) = sig {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago3861 // Use the name the proc was actually emitted under: an
3862 // inherent method is qualified by its module, a trait
3863 // method by its trait.
3864 let nim = key
3865 .and_then(|k| self.statics.get(&k).cloned())
3866 .unwrap_or_else(|| ident(&name));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3867 let mut all = vec![recv.code.clone()];
3868 all.extend(args.iter().map(|a| a.code.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago3869 (format!("{}({})", nim, all.join(", ")), Some(ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3870 } else {
3871 return Err(format!(
3872 "unsupported method `.{name}()`; it is neither defined in \
3873 this file nor part of the standard-library subset that \
3874 has a verified Nim equivalent"
3875 ));
3876 }
3877 }
3878 };
3879 Ok(Val::new(code, ty))
3880 }
3881
3882 // -------------------------------------------------------------- macros
3883
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago3884 /// The element type of a `vec![..]`, from its first element.
3885 fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
3886 let body = mac.tokens.to_string();
3887 if body.trim().is_empty() {
3888 return Ok(None);
3889 }
3890 let first: Option<Expr> = if body.contains(';') {
3891 // The whole body must be consumed or the parse fails, so the
3892 // length is parsed too even though only the element is wanted.
3893 mac.parse_body_with(|input: syn::parse::ParseStream| {
3894 let v: Expr = input.parse()?;
3895 input.parse::<syn::Token![;]>()?;
3896 let _len: Expr = input.parse()?;
3897 Ok(v)
3898 })
3899 .ok()
3900 } else {
3901 mac.parse_body_with(
3902 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3903 )
3904 .ok()
3905 .and_then(|p| p.into_iter().next())
3906 };
3907 match first {
3908 Some(e) => Ok(self.expr(&e)?.ty),
3909 None => Ok(None),
3910 }
3911 }
3912
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3913 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
3914 let name = path_name(&mac.path);
3915 match name.as_str() {
3916 "println" | "print" | "eprintln" | "eprint" => {
3917 let s = self.format_args(mac)?;
3918 let nl = name.ends_with("ln");
3919 Ok(match (name.starts_with('e'), nl) {
3920 (false, true) => format!("echo {s}"),
3921 (false, false) => format!("stdout.write({s})"),
3922 (true, true) => format!("stderr.writeLine({s})"),
3923 (true, false) => format!("stderr.write({s})"),
3924 })
3925 }
3926 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3927 "write" | "writeln" => {
3928 // `write!(f, "..", ..)` inside a formatting impl: the first
3929 // argument is the sink, the rest is an ordinary format call.
3930 let args: Vec<Expr> = mac
3931 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
3932 .map_err(|e| format!("write!: {e}"))?
3933 .into_iter()
3934 .collect();
3935 let sink = args.first().ok_or("`write!` needs a sink")?;
3936 if !self.is_fmt_param(sink) {
3937 return Err("`write!` to anything but the `Formatter` of the \
3938 enclosing formatting impl is not implemented"
3939 .into());
3940 }
3941 let s = self.format_pieces(&args[1..])?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3942 let s = if name == "writeln" {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3943 format!("({} & \"\\n\")", s)
3944 } else {
3945 s
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3946 };
3947 Ok(format!("result.add({})", s))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago3948 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago3949 "panic" => {
3950 let s = self.format_args(mac)?;
3951 Ok(format!("rsPanic({s})"))
3952 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3953 // `debug_assert*` fires in debug builds, which is the profile
3954 // this project models, so it lowers the same as `assert*`.
3955 "assert" | "debug_assert" => {
3956 let args: Vec<Expr> = mac
3957 .parse_body_with(
3958 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3959 )
3960 .map_err(|e| format!("{name}!: {e}"))?
3961 .into_iter()
3962 .collect();
3963 let cond = args.first().ok_or("`assert!` needs a condition")?;
3964 let v = self.expr(cond)?;
3965 let msg = if args.len() > 1 {
3966 self.format_pieces(&args[1..])?
3967 } else {
3968 fmt::nim_str("assertion failed")
3969 };
3970 Ok(format!("(if not ({}): rsPanic({}))", v.code, msg))
3971 }
3972 "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => {
3973 let args: Vec<Expr> = mac
3974 .parse_body_with(
3975 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3976 )
3977 .map_err(|e| format!("{name}!: {e}"))?
3978 .into_iter()
3979 .collect();
3980 if args.len() < 2 {
3981 return Err(format!("`{name}!` takes two operands"));
3982 }
3983 let a = self.expr(&args[0])?;
3984 let b = self.expr_at(&args[1], a.ty.as_ref())?;
3985 let ne = name.ends_with("_ne");
3986 let op = if ne { "!=" } else { "==" };
3987 // Rust's message shows both sides; reproducing it keeps a
3988 // failing assertion as informative as the original.
3989 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 22h ago3990 Ok(format!(
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago3991 "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))",
3992 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 22h ago3993 ))
3994 }
3995 "vec" => {
3996 let body = mac.tokens.to_string();
3997 if body.trim().is_empty() {
3998 return Ok("@[]".into());
3999 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago4000 // `vec![elem; n]` is the repeat form, not a list. The macro
4001 // body has no brackets, so it is parsed directly.
4002 if body.contains(';') {
4003 let (v, n) = mac
4004 .parse_body_with(|input: syn::parse::ParseStream| {
4005 let v: Expr = input.parse()?;
4006 input.parse::<syn::Token![;]>()?;
4007 let n: Expr = input.parse()?;
4008 Ok((v, n))
4009 })
4010 .map_err(|e| format!("vec![elem; n]: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago4011 let want = self.vec_expect.clone();
4012 let v = self.expr_at(&v, want.as_ref())?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago4013 let n = self.expr(&n)?;
4014 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
4015 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago4016 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
4017 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
4018 .map_err(|e| format!("vec!: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago4019 let want = self.vec_expect.clone();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago4020 let mut parts = Vec::new();
4021 for e in &elems {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago4022 parts.push(self.expr_at(e, want.as_ref())?.code);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago4023 }
4024 Ok(format!("@[{}]", parts.join(", ")))
4025 }
4026 other => Err(format!(
4027 "unsupported macro `{other}!`; a macro whose expansion is not \
4028 known cannot be lowered faithfully"
4029 )),
4030 }
4031 }
4032
4033 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
4034 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 21h ago4035 let args: Vec<Expr> = mac
4036 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
4037 .map_err(|e| format!("format arguments: {e}"))?
4038 .into_iter()
4039 .collect();
4040 self.format_pieces(&args)
4041 }
4042
4043 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
4044 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
4045 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 22h ago4046 if args.is_empty() {
4047 return Ok("\"\"".into());
4048 }
4049 return Err("the first argument must be a literal format string".into());
4050 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago4051 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago4052
4053 let pieces = fmt::parse(&s.value())?;
4054 let mut parts: Vec<String> = Vec::new();
4055 let mut next = 0usize;
4056 let mut used = vec![false; rest.len()];
4057 for p in &pieces {
4058 match p {
4059 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
4060 fmt::Piece::Arg { r#ref, spec } => {
4061 let v = match r#ref {
4062 fmt::Ref::Next => {
4063 let e = rest.get(next).ok_or("too few arguments for format string")?;
4064 used[next] = true;
4065 next += 1;
4066 self.expr(e)?
4067 }
4068 fmt::Ref::Index(i) => {
4069 let e = rest.get(*i).ok_or("format index out of range")?;
4070 used[*i] = true;
4071 self.expr(e)?
4072 }
4073 fmt::Ref::Named(n) => {
4074 let t = self.lookup(n).ok_or_else(|| {
4075 format!("`{{{n}}}` captures `{n}`, which is not in scope")
4076 })?;
4077 Val::new(ident(n), Some(t))
4078 }
4079 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 20h ago4080 let integer = v.ty.as_ref().is_some_and(|t| t.is_integer());
4081 if spec.radix.is_some() && !integer && v.ty.is_none() {
4082 return Err(
4083 "a radix format (`{:x}`, `{:b}`, ...) needs a known \
4084 argument type: on an integer it formats the bit \
4085 pattern, on anything else it calls that type's own \
4086 impl"
4087 .into(),
4088 );
4089 }
4090 parts.push(fmt::render_arg(&v.code, spec, integer));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago4091 }
4092 }
4093 }
4094 // Rust rejects an argument that no `{}` consumes; so do we, rather
4095 // than dropping it from the output.
4096 if let Some(i) = used.iter().position(|u| !u) {
4097 return Err(format!(
4098 "argument {} is never used by the format string",
4099 i + 1
4100 ));
4101 }
4102 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
4103 }
4104}
4105
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago4106/// Whether a pattern introduces a binding.
4107fn binds(p: &Pat) -> bool {
4108 match p {
4109 Pat::Ident(_) => true,
4110 Pat::Guard(g) => binds(&g.pat),
4111 Pat::Paren(x) => binds(&x.pat),
4112 Pat::Reference(r) => binds(&r.pat),
4113 Pat::Or(o) => o.cases.iter().any(binds),
4114 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
4115 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
4116 _ => false,
4117 }
4118}
4119
4120/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
4121fn destructures(p: &Pat) -> bool {
4122 matches!(
4123 p,
4124 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
4125 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
4126 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
4127 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
4128}
4129
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago4130/// Whether an expression has a direct Nim expression form.
4131///
4132/// Nim's `if` is an expression only when every arm is a single expression, and
4133/// its `case` is never one here. Anything else has to be lowered as statements
4134/// that assign into a target.
4135fn expressible(e: &Expr) -> bool {
4136 match e {
4137 Expr::If(i) => {
4138 let Some(then) = single_expr(&i.then_branch) else { return false };
4139 if !expressible(then) {
4140 return false;
4141 }
4142 match &i.else_branch {
4143 None => false,
4144 Some((_, els)) => match &**els {
4145 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
4146 other => expressible(other),
4147 },
4148 }
4149 }
4150 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
4151 _ => true,
4152 }
4153}
4154
4155/// The single expression a block consists of, if that is all it is. An `if`
4156/// can only be lowered as a Nim `if`-expression when both arms are this shape.
4157fn single_expr(b: &syn::Block) -> Option<&Expr> {
4158 match (b.stmts.len(), b.stmts.first()) {
4159 (1, Some(Stmt::Expr(e, None))) => Some(e),
4160 _ => None,
4161 }
4162}
4163
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago4164/// Substitute `params[i] -> args[i]` through a type. Enough of the type
4165/// grammar is covered to expand the aliases we accept; anything else is left
4166/// alone and will be reported by `ty::map` if it is unsupported.
4167fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
4168 use syn::Type;
4169 match t {
4170 Type::Path(p) => {
4171 if p.qself.is_none() && p.path.segments.len() == 1 {
4172 let seg = &p.path.segments[0];
4173 if seg.arguments.is_empty() {
4174 let name = seg.ident.to_string();
4175 if let Some(i) = params.iter().position(|x| *x == name) {
4176 return args[i].clone();
4177 }
4178 }
4179 }
4180 let mut p = p.clone();
4181 for seg in &mut p.path.segments {
4182 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
4183 for g in &mut a.args {
4184 if let syn::GenericArgument::Type(t) = g {
4185 *t = substitute(t, params, args);
4186 }
4187 }
4188 }
4189 }
4190 Type::Path(p)
4191 }
4192 Type::Reference(r) => {
4193 let mut r = r.clone();
4194 r.elem = Box::new(substitute(&r.elem, params, args));
4195 Type::Reference(r)
4196 }
4197 Type::Slice(sl) => {
4198 let mut sl = sl.clone();
4199 sl.elem = Box::new(substitute(&sl.elem, params, args));
4200 Type::Slice(sl)
4201 }
4202 Type::Array(a) => {
4203 let mut a = a.clone();
4204 a.elem = Box::new(substitute(&a.elem, params, args));
4205 Type::Array(a)
4206 }
4207 Type::Tuple(tp) => {
4208 let mut tp = tp.clone();
4209 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
4210 Type::Tuple(tp)
4211 }
4212 Type::Paren(p) => substitute(&p.elem, params, args),
4213 Type::Group(g) => substitute(&g.elem, params, args),
4214 other => other.clone(),
4215 }
4216}
4217
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago4218// --------------------------------------------------------------- utilities
4219
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago4220/// Whether a return type is a borrow of one of the arguments, which Nim
4221/// models with a view rather than with an owned copy.
4222fn returns_borrow(t: &syn::Type) -> bool {
4223 match t {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago4224 syn::Type::Reference(r) => match &*r.elem {
4225 syn::Type::Slice(_) => true,
4226 // `&str` is a borrow of someone else's bytes too, and returning it
4227 // means returning a view, not an owned string.
4228 syn::Type::Path(p) => p.path.is_ident("str"),
4229 _ => false,
4230 },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago4231 syn::Type::Paren(p) => returns_borrow(&p.elem),
4232 syn::Type::Group(g) => returns_borrow(&g.elem),
4233 _ => false,
4234 }
4235}
4236
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 20h ago4237/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
4238/// to the crate root, which is where a flattened module's items live unless
4239/// they came from one of the extra input files.
4240fn module_of(prefix: &[String]) -> String {
4241 match prefix.last() {
4242 Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
4243 _ => String::new(),
4244 }
4245}
4246
4247/// The first type argument of an `Option[T]` / `Result[T, E]`.
4248fn elem_arg(t: &Nim) -> Nim {
4249 match t {
4250 Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
4251 other => other.clone(),
4252 }
4253}
4254
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago4255/// The element type of a sequence-like Nim type.
4256fn elem_of(t: &Option<Nim>) -> Option<Nim> {
4257 match t {
4258 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
4259 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
4260 _ => None,
4261 }
4262}
4263
4264/// The short name a Nim type is known by, for keying method tables.
4265fn type_name(t: &Nim) -> String {
4266 match t {
4267 Nim::Named(n, _) => n.clone(),
4268 Nim::Prim(p) => p.clone(),
4269 other => other.render(),
4270 }
4271}
4272
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago4273/// `(trait, operator)` for every operator trait we dispatch.
4274const OPERATOR_TRAITS: &[(&str, &str)] = &[
4275 ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"),
4276 ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"),
4277 ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="),
4278 ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="),
4279 ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="),
4280 ("Neg", "neg"), ("Not", "not"),
4281];
4282
4283/// `(operator, trait method name)`.
4284const OP_METHOD: &[(&str, &str)] = &[
4285 ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"),
4286 ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"),
4287 ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"),
4288 ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"),
4289 ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"),
4290 (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"),
4291];
4292
4293fn op_method(op: &str) -> &'static str {
4294 OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("")
4295}
4296
4297/// The operator symbol a compound assignment applies.
4298fn compound_symbol(op: &BinOp) -> &'static str {
4299 match op {
4300 BinOp::AddAssign(_) => "+=",
4301 BinOp::SubAssign(_) => "-=",
4302 BinOp::MulAssign(_) => "*=",
4303 BinOp::DivAssign(_) => "/=",
4304 BinOp::RemAssign(_) => "%=",
4305 BinOp::BitAndAssign(_) => "&=",
4306 BinOp::BitOrAssign(_) => "|=",
4307 BinOp::BitXorAssign(_) => "^=",
4308 BinOp::ShlAssign(_) => "<<=",
4309 BinOp::ShrAssign(_) => ">>=",
4310 _ => "",
4311 }
4312}
4313
4314fn binary_symbol(op: &BinOp) -> &'static str {
4315 match op {
4316 BinOp::Add(_) => "+",
4317 BinOp::Sub(_) => "-",
4318 BinOp::Mul(_) => "*",
4319 BinOp::Div(_) => "/",
4320 BinOp::Rem(_) => "%",
4321 BinOp::BitAnd(_) => "&",
4322 BinOp::BitOr(_) => "|",
4323 BinOp::BitXor(_) => "^",
4324 BinOp::Shl(_) => "<<",
4325 BinOp::Shr(_) => ">>",
4326 _ => "",
4327 }
4328}
4329
4330/// The operator a trait overloads, if it is one of the operator traits.
4331fn operator_trait(t: &str) -> Option<&'static str> {
4332 Some(match t {
4333 "Add" => "+",
4334 "Sub" => "-",
4335 "Mul" => "*",
4336 "Div" => "/",
4337 "Rem" => "%",
4338 "BitAnd" => "&",
4339 "BitOr" => "|",
4340 "BitXor" => "^",
4341 "Shl" => "<<",
4342 "Shr" => ">>",
4343 "AddAssign" => "+=",
4344 "SubAssign" => "-=",
4345 "MulAssign" => "*=",
4346 "DivAssign" => "/=",
4347 "RemAssign" => "%=",
4348 "BitAndAssign" => "&=",
4349 "BitOrAssign" => "|=",
4350 "BitXorAssign" => "^=",
4351 "ShlAssign" => "<<=",
4352 "ShrAssign" => ">>=",
4353 "Neg" => "neg",
4354 "Not" => "not",
4355 _ => return None,
4356 })
4357}
4358
4359/// The Nim proc name for a trait method, qualified by trait and type so that
4360/// two traits declaring the same method name cannot collide.
4361fn trait_method_name(ty: &str, tr: &str, m: &str) -> String {
4362 format!("rs{}_{}_{}", tr, ty, m)
4363}
4364
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 21h ago4365fn is_fmt_trait(t: &str) -> bool {
4366 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
4367}
4368
4369/// The prelude proc a formatting trait's output is produced by.
4370fn fmt_proc(t: &str) -> &'static str {
4371 match t {
4372 "Display" => "rsDisplay",
4373 "Debug" => "rsDebug",
4374 "LowerHex" => "rsLowerHex",
4375 "UpperHex" => "rsUpperHex",
4376 "Binary" => "rsBinary",
4377 _ => "rsOctal",
4378 }
4379}
4380
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 19h ago4381/// Whether an expression is an iterator-producing chain rather than a value.
4382fn is_iterator_expr(e: &Expr) -> bool {
4383 match e {
4384 Expr::MethodCall(m) => matches!(
4385 m.method.to_string().as_str(),
4386 "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact"
4387 | "chunks_exact_mut" | "windows"
4388 ),
4389 Expr::Paren(p) => is_iterator_expr(&p.expr),
4390 _ => false,
4391 }
4392}
4393
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 20h ago4394/// Whether an expression denotes a place -- a variable, a field, or an index
4395/// or slice of one -- and so may be re-evaluated with no side effect.
4396fn is_pure_place(e: &Expr) -> bool {
4397 match e {
4398 Expr::Path(_) => true,
4399 Expr::Field(f) => is_pure_place(&f.base),
4400 Expr::Index(i) => {
4401 is_pure_place(&i.expr)
4402 && match &*i.index {
4403 Expr::Range(r) => {
4404 r.start.as_deref().map_or(true, is_pure_place)
4405 && r.end.as_deref().map_or(true, is_pure_place)
4406 }
4407 other => is_pure_place(other),
4408 }
4409 }
4410 Expr::Lit(_) => true,
4411 Expr::Reference(r) => is_pure_place(&r.expr),
4412 Expr::Paren(p) => is_pure_place(&p.expr),
4413 Expr::Group(g) => is_pure_place(&g.expr),
4414 // Arithmetic on places is still side-effect free, so a bound like
4415 // `..want - 1` does not stop the binding being an alias.
4416 Expr::Binary(b) if !is_compound(&b.op) => {
4417 is_pure_place(&b.left) && is_pure_place(&b.right)
4418 }
4419 Expr::Unary(u) => is_pure_place(&u.expr),
4420 Expr::Cast(c) => is_pure_place(&c.expr),
4421 _ => false,
4422 }
4423}
4424
4425/// Whether an expression is a `&mut` borrow, directly or through parens.
4426fn is_mut_borrow(e: &Expr) -> bool {
4427 match e {
4428 Expr::Reference(r) => r.mutability.is_some(),
4429 Expr::Paren(p) => is_mut_borrow(&p.expr),
4430 Expr::Group(g) => is_mut_borrow(&g.expr),
4431 _ => false,
4432 }
4433}
4434
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago4435fn takes_self(sig: &syn::Signature) -> bool {
4436 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
4437}
4438
4439fn path_name(p: &syn::Path) -> String {
4440 p.segments
4441 .last()
4442 .map(|s| s.ident.to_string())
4443 .unwrap_or_default()
4444}
4445
4446fn is_compound(op: &BinOp) -> bool {
4447 matches!(
4448 op,
4449 BinOp::AddAssign(_)
4450 | BinOp::SubAssign(_)
4451 | BinOp::MulAssign(_)
4452 | BinOp::DivAssign(_)
4453 | BinOp::RemAssign(_)
4454 | BinOp::BitAndAssign(_)
4455 | BinOp::BitOrAssign(_)
4456 | BinOp::BitXorAssign(_)
4457 | BinOp::ShlAssign(_)
4458 | BinOp::ShrAssign(_)
4459 )
4460}
4461
4462/// The Nim literal suffix for an integer type (`5'i32`).
4463fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
4464 let Nim::Prim(p) = t else {
4465 return Err("not a primitive integer".into());
4466 };
4467 Ok(match p.as_str() {
4468 "int8" => "i8",
4469 "int16" => "i16",
4470 "int32" => "i32",
4471 "int64" => "i64",
4472 "int" => "i",
4473 "uint8" => "u8",
4474 "uint16" => "u16",
4475 "uint32" => "u32",
4476 "uint64" => "u64",
4477 "uint" => "u",
4478 other => return Err(format!("no Nim literal suffix for `{other}`")),
4479 })
4480}
4481
4482/// The unsigned integer type of the same width, used to spell `wrapping_*`.
4483fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
4484 let Nim::Prim(p) = t else {
4485 return Err("not a primitive integer".into());
4486 };
4487 Ok(match p.as_str() {
4488 "int8" => "uint8",
4489 "int16" => "uint16",
4490 "int32" => "uint32",
4491 "int64" => "uint64",
4492 "int" => "uint",
4493 other => return Err(format!("`{other}` has no unsigned peer")),
4494 })
4495}
4496
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 21h ago4497fn quote_meta(m: &syn::Meta) -> String {
4498 match m {
4499 syn::Meta::Path(p) => path_name(p),
4500 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
4501 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
4502 }
4503}
4504
4505fn item_attrs(i: &Item) -> &[syn::Attribute] {
4506 match i {
4507 Item::Fn(f) => &f.attrs,
4508 Item::Struct(s) => &s.attrs,
4509 Item::Enum(e) => &e.attrs,
4510 Item::Impl(x) => &x.attrs,
4511 Item::Const(c) => &c.attrs,
4512 Item::Type(t) => &t.attrs,
4513 Item::Mod(m) => &m.attrs,
4514 Item::Use(u) => &u.attrs,
4515 Item::ExternCrate(e) => &e.attrs,
4516 Item::Static(s) => &s.attrs,
4517 _ => &[],
4518 }
4519}
4520
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 22h ago4521fn item_kind(i: &Item) -> &'static str {
4522 match i {
4523 Item::Trait(_) => "`trait`",
4524 Item::Static(_) => "`static`",
4525 Item::Macro(_) => "macro definition",
4526 Item::Union(_) => "`union`",
4527 Item::ForeignMod(_) => "`extern` block",
4528 _ => "item",
4529 }
4530}
4531
4532fn expr_kind(e: &Expr) -> &'static str {
4533 match e {
4534 Expr::Async(_) => "`async` block",
4535 Expr::Await(_) => "`.await`",
4536 Expr::Try(_) => "`?`",
4537 Expr::Range(_) => "range",
4538 Expr::Match(_) => "`match` (only statement position is implemented)",
4539 Expr::Let(_) => "`let` expression",
4540 Expr::Unsafe(_) => "`unsafe` block",
4541 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
4542 _ => "expression",
4543 }
4544}