nandi/rustnimpublic Fork 0
5fdd5be9905d7aab2a8941bef4846c2ffb79c765
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 · 4489 lines · 186.3 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h 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 11h 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 12h 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 11h 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 11h 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 11h 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 11h 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 11h 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 11h 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 11h 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 12h ago103 }
104}
105
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h 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 10h 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 11h ago117}
118
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h 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 11h 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 12h 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 11h 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 12h ago144 }
145 fn untyped(code: impl Into<String>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago146 Val::new(code, None)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h 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 11h 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 12h 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 11h 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 11h 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 10h 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 11h 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 12h 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 11h 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 11h 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 10h 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 11h 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 11h 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 11h 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 11h 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 11h 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>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago240 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
241 /// evaluated against these exactly as rustc would, so an item that is
242 /// dropped here is genuinely not part of the program being compiled.
243 pub features: Vec<String>,
244 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago245 /// Return type of the proc being lowered, so `return e` and a trailing
246 /// expression can type their literals the way Rust's inference would.
247 ret: Option<Nim>,
248 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
249 /// statement must assign their value to.
250 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago251 /// Set while lowering a `while` condition, which Nim re-evaluates each
252 /// iteration and so cannot have statements hoisted out of it.
253 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago254 tmp: usize,
255}
256
257impl Lowerer {
258 pub fn new() -> Self {
259 Lowerer {
260 out: String::new(),
261 indent: 0,
262 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago263 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago264 fns: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago265 cur_mod: String::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago266 self_ty: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago267 use_map: HashMap::new(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago268 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago269 enums: HashMap::new(),
270 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago271 methods: HashMap::new(),
272 fmt_impls: HashMap::new(),
273 from_impls: HashMap::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago274 op_impls: HashMap::new(),
275 statics: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago276 fmt_param: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago277 vec_expect: None,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago278 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago279 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago280 modules: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago281 features: Vec::new(),
282 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago283 ret: None,
284 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago285 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago286 tmp: 0,
287 }
288 }
289
290 // ------------------------------------------------------------ emission
291
292 fn line(&mut self, s: &str) {
293 for _ in 0..self.indent {
294 self.out.push_str(" ");
295 }
296 self.out.push_str(s);
297 self.out.push('\n');
298 }
299
300 fn blank(&mut self) {
301 self.out.push('\n');
302 }
303
304 fn fresh(&mut self, hint: &str) -> String {
305 self.tmp += 1;
306 format!("rsTmp{}{}", hint, self.tmp)
307 }
308
309 // --------------------------------------------------------------- scope
310
311 fn push_scope(&mut self) {
312 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago313 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago314 }
315 fn pop_scope(&mut self) {
316 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago317 self.alias_scopes.pop();
318 }
319 fn bind_alias(&mut self, name: &str, a: Alias) {
320 self.alias_scopes
321 .last_mut()
322 .unwrap()
323 .insert(name.to_string(), a);
324 }
325 fn lookup_alias(&self, name: &str) -> Option<Alias> {
326 self.alias_scopes
327 .iter()
328 .rev()
329 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago330 }
331 fn bind(&mut self, name: &str, t: Nim) {
332 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
333 }
334 fn lookup(&self, name: &str) -> Option<Nim> {
335 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
336 }
337
338 // ---------------------------------------------------------------- file
339
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago340 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 12h ago341 self.out.push_str(include_str!("prelude.nim"));
342 self.blank();
343
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago344 // Pass 0: type aliases. A signature in one file may use an alias
345 // declared in another, and inputs are given in whatever order suits
346 // 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 11h ago347 for (m, f) in files {
348 self.cur_mod = m.clone();
349 for item in &f.items {
350 self.collect_aliases(item)?;
351 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago352 }
353
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago354 // Pass 1: signatures and struct shapes, so that a call can be typed
355 // regardless of declaration order (Rust has no forward declarations).
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago356 for (m, f) in files {
357 self.cur_mod = m.clone();
358 for item in &f.items {
359 self.collect(item)?;
360 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago361 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago362 // Pass 2: type definitions, which every signature may mention.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago363 for (m, f) in files {
364 self.cur_mod = m.clone();
365 for item in &f.items {
366 self.item_types(item)?;
367 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago368 }
369
370 // Pass 3: forward declarations. Rust imposes no declaration order and
371 // Nim does, so everything is declared before any body is emitted;
372 // reordering the input would not handle mutual recursion anyway.
373 if !self.forwards.is_empty() {
374 for f in self.forwards.clone() {
375 self.line(&f);
376 }
377 self.blank();
378 }
379
380 // Pass 4: bodies.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago381 for (m, f) in files {
382 self.cur_mod = m.clone();
383 for item in &f.items {
384 self.item(item)?;
385 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago386 }
387
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago388 if self.fns.contains_key(&(String::new(), "main".to_string())) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago389 self.blank();
390 self.line("when isMainModule:");
391 self.indent += 1;
392 self.line("try:");
393 self.line(" main()");
394 // Rust's panic exits 101 with a message on stderr. Nim's Defects
395 // exit 1. Mapping them here is what keeps the differential runner's
396 // exit-status comparison meaningful for panicking programs.
397 self.line("except RustPanic as e:");
398 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
399 self.line(" quit(101)");
400 self.line("except Defect as e:");
401 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
402 self.line(" quit(101)");
403 self.indent -= 1;
404 }
405 Ok(std::mem::take(&mut self.out))
406 }
407
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago408 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
409 if !self.cfg_keeps(item_attrs(item))? {
410 return Ok(());
411 }
412 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago413 Item::Use(u) => self.collect_use(&u.tree, &[]),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago414 Item::Type(t) => {
415 let params: Vec<String> = t
416 .generics
417 .params
418 .iter()
419 .filter_map(|g| match g {
420 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
421 _ => None,
422 })
423 .collect();
424 self.aliases
425 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
426 }
427 Item::Mod(m) if m.content.is_some() => {
428 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
429 for i in &items {
430 self.collect_aliases(i)?;
431 }
432 }
433 _ => {}
434 }
435 Ok(())
436 }
437
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago438 /// Record what a `use` brings into scope, as `name -> module`.
439 fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
440 use syn::UseTree;
441 match t {
442 UseTree::Path(p) => {
443 let mut pre = prefix.to_vec();
444 pre.push(p.ident.to_string());
445 self.collect_use(&p.tree, &pre);
446 }
447 UseTree::Group(g) => {
448 for t in &g.items {
449 self.collect_use(t, prefix);
450 }
451 }
452 UseTree::Name(n) => {
453 let m = module_of(prefix);
454 self.use_map.insert(n.ident.to_string(), m);
455 }
456 UseTree::Rename(r) => {
457 let m = module_of(prefix);
458 self.use_map.insert(r.rename.to_string(), m);
459 }
460 // A glob brings in an unknown set of names; resolution falls back
461 // to the current module and the root, as it would without it.
462 UseTree::Glob(_) => {}
463 }
464 }
465
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago466 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago467 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
468 // silently would change what the program does; picking a feature set
469 // on the user's behalf would be a guess. So it is reported, except on
470 // items that carry no runtime meaning here anyway.
471 if !self.cfg_keeps(item_attrs(item))? {
472 self.dropped_by_cfg += 1;
473 return Ok(());
474 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago475 match item {
476 Item::Fn(f) => {
477 let (params, ret) = self.signature(&f.sig)?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago478 let name = f.sig.ident.to_string();
479 let nim = self.fn_name(&self.cur_mod, &name);
480 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
481 self.fns
482 .insert((self.cur_mod.clone(), name), Sig { params, ret });
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago483 }
484 Item::Struct(s) => {
485 let mut fields = Vec::new();
486 for (i, f) in s.fields.iter().enumerate() {
487 let name = match &f.ident {
488 Some(id) => id.to_string(),
489 None => format!("f{i}"), // tuple struct
490 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago491 // A field of `&[T]` / `&str` type is a borrow, and Nim's
492 // view types allow it as an object field, so it stays a
493 // view rather than being copied into a `seq`.
494 let t = self.map_ty(&f.ty)?;
495 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
496 fields.push((name, t));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago497 }
498 self.structs.insert(s.ident.to_string(), fields);
499 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago500 Item::Mod(m) if m.content.is_some() => {
501 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
502 for i in &items {
503 self.collect(i)?;
504 }
505 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago506 Item::Type(t) => {
507 let params: Vec<String> = t
508 .generics
509 .params
510 .iter()
511 .filter_map(|g| match g {
512 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
513 _ => None,
514 })
515 .collect();
516 self.aliases
517 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
518 }
519 Item::Enum(e) => {
520 let name = e.ident.to_string();
521 if e.generics.params.iter().any(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
522 return Err(format!("`enum {name}` is generic: not implemented yet"));
523 }
524 let mut variants = Vec::new();
525 for v in &e.variants {
526 let vname = v.ident.to_string();
527 if v.discriminant.is_some() {
528 return Err(format!(
529 "`{name}::{vname}` has an explicit discriminant; Rust's \
530 `as` on such an enum has a value this lowering does not \
531 yet preserve"
532 ));
533 }
534 let mut fields = Vec::new();
535 for (i, f) in v.fields.iter().enumerate() {
536 // Nim requires the branches of a variant object to have
537 // distinct field names, so each is prefixed.
538 let fname = match &f.ident {
539 Some(id) => format!("{vname}_{id}"),
540 None => format!("{vname}_f{i}"),
541 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago542 let t = self.map_ty(&f.ty)?;
543 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
544 fields.push((fname, t));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago545 }
546 variants.push(Variant { name: vname, fields });
547 }
548 let simple = variants.iter().all(|v| v.fields.is_empty());
549 for v in &variants {
550 self.variant_owner
551 .entry(v.name.clone())
552 .or_default()
553 .push(name.clone());
554 }
555 self.enums.insert(
556 name.clone(),
557 EnumDef { name, simple, variants },
558 );
559 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago560 Item::Impl(im) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago561 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago562 let outer_self = self.self_ty.replace(self_ty.clone());
563 let r = self.collect_impl(im, &self_ty);
564 self.self_ty = outer_self;
565 return r;
566 }
567 _ => {}
568 }
569 Ok(())
570 }
571
572 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
573 {
574 let self_ty = self_ty.clone();
575 let tyname = type_name(&self_ty);
576 if let Some((path, _)) = &im.trait_ {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago577 let tr = path_name(path);
578 if im.items.is_empty() {
579 // A marker trait with no items. We do not model trait
580 // resolution at all, so it generates nothing; any use
581 // that actually needed the trait (a `dyn`, a bound) is
582 // rejected where it appears.
583 return Ok(());
584 }
585 if is_fmt_trait(&tr) {
586 self.forwards.push(format!(
587 "proc {}*(self: {}): string",
588 fmt_proc(&tr),
589 self_ty.render()
590 ));
591 self.fmt_impls.insert((tyname, tr), ());
592 return Ok(());
593 }
594 if tr == "From" {
595 let syn::ImplItem::Fn(m) = &im.items[0] else {
596 return Err("`impl From` must contain `fn from`".into());
597 };
598 let (params, _) = self.signature(&m.sig)?;
599 let src = params
600 .first()
601 .ok_or("`fn from` takes one argument")?
602 .clone();
603 let name = format!("rsFrom{}{}", tyname, type_name(&src));
604 self.forwards.push(self.head_of(&name, &m.sig, None)?);
605 self.from_impls
606 .insert((type_name(&src), tyname), name);
607 return Ok(());
608 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago609 // Any other trait: its methods are emitted as procs on
610 // the type, named after the trait so two traits declaring
611 // the same method name do not collide. The *trait* is not
612 // modelled -- no dynamic dispatch, no bounds -- and a use
613 // that needs it is rejected where it appears.
614 if let Some(op) = operator_trait(&tr) {
615 self.op_impls.insert((tyname.clone(), op.to_string()), ());
616 }
617 for it in &im.items {
618 let syn::ImplItem::Fn(m) = it else {
619 return Err(format!("unsupported item in `impl {tr}`"));
620 };
621 let mname = m.sig.ident.to_string();
622 let (mut params, ret) = self.signature(&m.sig)?;
623 let recv = if takes_self(&m.sig) {
624 params.insert(0, self_ty.clone());
625 Some(self_ty.clone())
626 } else {
627 None
628 };
629 let nim = trait_method_name(&tyname, &tr, &mname);
630 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
631 self.methods
632 .insert((tyname.clone(), mname.clone()), Sig { params, ret });
633 self.statics.insert((tyname.clone(), mname), nim);
634 }
635 return Ok(());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago636 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago637 for it in &im.items {
638 if let syn::ImplItem::Fn(m) = it {
639 let (mut params, ret) = self.signature(&m.sig)?;
640 if takes_self(&m.sig) {
641 params.insert(0, self_ty.clone());
642 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago643 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 10h ago644 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
645 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 11h ago646 self.forwards.push(head);
647 self.methods
648 .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 10h ago649 self.statics
650 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago651 }
652 }
653 }
654 Ok(())
655 }
656
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago657 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
658 ///
659 /// This is evaluation, not approximation: rustc does the same thing, and
660 /// an item whose predicate is false is not part of the compiled program.
661 /// A predicate that cannot be evaluated is reported rather than assumed.
662 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
663 for a in attrs {
664 if a.path().is_ident("cfg") {
665 let pred: syn::Meta = a
666 .parse_args()
667 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
668 if !self.cfg_eval(&pred)? {
669 return Ok(false);
670 }
671 }
672 }
673 Ok(true)
674 }
675
676 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
677 match m {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago678 // Bare flags whose value is determined by the profile this project
679 // models: a normal (non-`--test`) debug build, not a docs build.
680 // Anything platform-specific stays rejected, since we would be
681 // picking a target on the user's behalf.
682 syn::Meta::Path(p) if p.is_ident("test") => Ok(false),
683 syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true),
684 syn::Meta::Path(p) if p.is_ident("docsrs") || p.is_ident("doc") => Ok(false),
685 // The generated Nim is compiled for the same machine, so the
686 // target's word size and endianness are known rather than
687 // guessed. This does mean the output is host-shaped: a crate that
688 // branches on pointer width has had that branch decided here.
689 syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => {
690 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
691 return Err("`target_pointer_width = ..` expects a string".into());
692 };
693 Ok(s.value() == (usize::BITS).to_string())
694 }
695 syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => {
696 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
697 return Err("`target_endian = ..` expects a string".into());
698 };
699 Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" })
700 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago701 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
702 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
703 return Err("`feature = ..` expects a string".into());
704 };
705 Ok(self.features.iter().any(|f| *f == s.value()))
706 }
707 syn::Meta::List(l) if l.path.is_ident("not") => {
708 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
709 Ok(!self.cfg_eval(&inner)?)
710 }
711 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
712 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
713 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
714 .map_err(|e| e.to_string())?;
715 let all = l.path.is_ident("all");
716 let mut acc = all;
717 for i in &items {
718 let v = self.cfg_eval(i)?;
719 acc = if all { acc && v } else { acc || v };
720 }
721 Ok(acc)
722 }
723 other => Err(format!(
724 "`#[cfg({})]` is not a predicate rustnim can evaluate; only \
725 `feature = \"..\"`, `not`, `all` and `any` are implemented",
726 quote_meta(other)
727 )),
728 }
729 }
730
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago731 /// 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 11h ago732 /// lowering goes through here rather than calling `ty::map` directly, so
733 /// an alias cannot be missed in one position and honoured in another.
734 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago735 let n = ty::map(&self.expand(t, 0)?)?;
736 Ok(self.subst_self(n))
737 }
738
739 /// `Self` inside an `impl` block names the type being implemented.
740 fn subst_self(&self, t: Nim) -> Nim {
741 let Some(me) = &self.self_ty else { return t };
742 match t {
743 Nim::Named(n, _) if n == "Self" => me.clone(),
744 Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))),
745 Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))),
746 Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))),
747 Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))),
748 Nim::Named(n, a) => {
749 Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect())
750 }
751 other => other,
752 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago753 }
754
755 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
756 if depth > 16 {
757 return Err("type alias expansion did not terminate; is it cyclic?".into());
758 }
759 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
760 // Only an unqualified name can be one of this file's aliases.
761 // `fmt::Result` and `core::result::Result` are different types that
762 // merely end in the same segment.
763 if p.path.segments.len() != 1 {
764 return Ok(t.clone());
765 }
766 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
767 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
768 return Ok(t.clone());
769 };
770 let args: Vec<syn::Type> = match &seg.arguments {
771 syn::PathArguments::AngleBracketed(a) => a
772 .args
773 .iter()
774 .filter_map(|g| match g {
775 GenericArgument::Type(t) => Some(t.clone()),
776 _ => None,
777 })
778 .collect(),
779 _ => vec![],
780 };
781 if args.len() != params.len() {
782 // Flattening several files into one module can bring a crate's own
783 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
784 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
785 // module; here they are told apart by arity, and a use that fits
786 // neither is left for `ty::map` to report.
787 return Ok(t.clone());
788 }
789 self.expand(&substitute(target, params, &args), depth + 1)
790 }
791
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago792 /// The Nim name for a function, qualified by its module.
793 fn fn_name(&self, module: &str, name: &str) -> String {
794 if module.is_empty() {
795 ident(name)
796 } else {
797 format!("{}_{}", module, ident(name))
798 }
799 }
800
801 /// Resolve a call path to the module and name it refers to: an explicit
802 /// `mixed::decode`, then the current module, then the crate root.
803 fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
804 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
805 let last = segs.last()?.clone();
806 if segs.len() >= 2 {
807 let q = &segs[segs.len() - 2];
808 if self.fns.contains_key(&(q.clone(), last.clone())) {
809 return Some((q.clone(), last));
810 }
811 }
812 let imported = self.use_map.get(&last).cloned();
813 for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
814 .into_iter()
815 .flatten()
816 {
817 if self.fns.contains_key(&(m.clone(), last.clone())) {
818 return Some((m, last));
819 }
820 }
821 None
822 }
823
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago824 /// The Nim `proc` head for a Rust signature, used both for the forward
825 /// declaration and for the definition, so the two cannot drift apart.
826 fn head_of(
827 &self,
828 name: &str,
829 sig: &syn::Signature,
830 recv: Option<&Nim>,
831 ) -> Result<String, String> {
832 let (ptys, ret) = self.signature(sig)?;
833 let mut parts = Vec::new();
834 if let Some(self_ty) = recv {
835 let mutable = matches!(
836 sig.inputs.first(),
837 Some(FnArg::Receiver(r))
838 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
839 );
840 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
841 parts.push(format!("self: {}", t.render()));
842 }
843 let typed: Vec<&syn::PatType> = sig
844 .inputs
845 .iter()
846 .filter_map(|a| match a {
847 FnArg::Typed(t) => Some(t),
848 _ => None,
849 })
850 .collect();
851 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
852 let pname = match &*p.pat {
853 Pat::Ident(id) => id.ident.to_string(),
854 Pat::Wild(_) => format!("unused{}", parts.len()),
855 _ => return Err("only plain identifier parameters are supported".into()),
856 };
857 let _ = i;
858 parts.push(format!("{}: {}", ident(&pname), t.render()));
859 }
860 Ok(if ret == Nim::Unit {
861 format!("proc {}*({})", ident(name), parts.join(", "))
862 } else {
863 format!("proc {}*({}): {}", ident(name), parts.join(", "), ret.render())
864 })
865 }
866
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago867 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 11h ago868 // `unsafe fn` marks a contract for callers; it does not change what
869 // the body means, so it lowers like any other proc.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago870 if sig.asyncness.is_some() {
871 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
872 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago873 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
874 // `fn encode<'a>(..)` is not generic for our purposes. Type and const
875 // parameters genuinely are, and are rejected.
876 if let Some(p) = sig.generics.params.iter().find(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
877 let what = match p {
878 syn::GenericParam::Const(_) => "const",
879 _ => "type",
880 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago881 return Err(format!(
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago882 "`fn {}` has a {what} parameter: generics are not implemented yet",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago883 sig.ident
884 ));
885 }
886 let mut params = Vec::new();
887 for a in &sig.inputs {
888 if let FnArg::Typed(t) = a {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago889 params.push(self.map_ty(&t.ty)?);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago890 }
891 }
892 let ret = match &sig.output {
893 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago894 // A returned `&[T]` is a borrow of the caller's buffer, so it
895 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
896 // a `seq`, which `owned()` would do to both.
897 ReturnType::Type(_, t) => {
898 let n = self.map_ty(t)?;
899 if returns_borrow(t) { n } else { n.owned() }
900 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago901 };
902 Ok((params, ret))
903 }
904
905 // --------------------------------------------------------------- items
906
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago907 /// Emit the type definitions only: they must precede every signature.
908 fn item_types(&mut self, item: &Item) -> Result<(), String> {
909 if !self.cfg_keeps(item_attrs(item))? {
910 return Ok(());
911 }
912 match item {
913 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
914 Item::Mod(m) if m.content.is_some() => {
915 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
916 for i in &items {
917 self.item_types(i)?;
918 }
919 Ok(())
920 }
921 _ => Ok(()),
922 }
923 }
924
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago925 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago926 if !self.cfg_keeps(item_attrs(item))? {
927 return Ok(());
928 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago929 // Types were emitted in their own pass.
930 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
931 return Ok(());
932 }
933 self.item_inner(item)
934 }
935
936 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago937 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago938 Item::Fn(f) => {
939 let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
940 self.func_named(&nim, &f.sig, &f.block, None)
941 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago942 Item::Struct(s) => {
943 let name = s.ident.to_string();
944 let fields = self.structs[&name].clone();
945 self.line(&format!("type {}* = object", ident(&name)));
946 self.indent += 1;
947 if fields.is_empty() {
948 self.line("discard");
949 }
950 for (fname, fty) in &fields {
951 self.line(&format!("{}*: {}", ident(fname), fty.render()));
952 }
953 self.indent -= 1;
954 self.blank();
955 Ok(())
956 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago957 Item::Type(_) => Ok(()), // expanded at every use site
958 Item::Enum(e) => {
959 let def = self.enums[&e.ident.to_string()].clone();
960 self.emit_enum(&def);
961 Ok(())
962 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago963 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago964 let t = self.map_ty(&c.ty)?.owned();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago965 // The annotation types the initialiser, exactly as it does for
966 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
967 let v = self.expr_at(&c.expr, Some(&t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago968 self.bind(&c.ident.to_string(), t.clone());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago969 // Only a top-level const is exported; `*` on a local is not
970 // Nim syntax.
971 let star = if self.indent == 0 { "*" } else { "" };
972 let line = format!(
973 "const {}{}: {} = {}",
974 ident(&c.ident.to_string()),
975 star,
976 t.render(),
977 v.code
978 );
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago979 self.line(&line);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago980 if self.indent == 0 {
981 self.blank();
982 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago983 Ok(())
984 }
985 Item::Impl(im) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago986 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago987 let outer = self.self_ty.replace(self_ty.clone());
988 let r = self.impl_body(im, &self_ty);
989 self.self_ty = outer;
990 r
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago991 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago992 // `use` and `extern crate` are resolution directives with no Nim
993 // analogue once everything is one module.
994 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
995 Item::Mod(m) if m.content.is_some() => {
996 // An inline `mod` is flattened; Nim has no nested modules in a
997 // single file.
998 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
999 for i in &items {
1000 self.item(i)?;
1001 }
1002 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1003 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1004 Item::Mod(m) => {
1005 // Satisfied if that file was passed in too; everything is one
1006 // Nim module, so the declaration itself emits nothing.
1007 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
1008 return Ok(());
1009 }
1010 Err(format!(
1011 "`mod {};` refers to another file that was not passed to \
1012 rustnim; add it to the input list",
1013 m.ident
1014 ))
1015 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1016 other => Err(format!("unsupported item: {}", item_kind(other))),
1017 }
1018 }
1019
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago1020 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
1021 fn none_of(&self, expect: Option<&Nim>) -> String {
1022 match expect {
1023 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
1024 format!("rsNone[{}]()", a[0].render())
1025 }
1026 _ => "rsNone()".to_string(),
1027 }
1028 }
1029
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago1030 fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
1031 if let Some((path, _)) = &im.trait_ {
1032 let tr = path_name(path);
1033 if im.items.is_empty() {
1034 return Ok(());
1035 }
1036 if is_fmt_trait(&tr) {
1037 let syn::ImplItem::Fn(m) = &im.items[0] else {
1038 return Err(format!("unsupported item in `impl {tr}`"));
1039 };
1040 return self.fmt_impl(&tr, self_ty, &m.sig, &m.block);
1041 }
1042 if tr == "From" {
1043 let syn::ImplItem::Fn(m) = &im.items[0] else {
1044 return Err("`impl From` must contain `fn from`".into());
1045 };
1046 let name = {
1047 let (params, _) = self.signature(&m.sig)?;
1048 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
1049 self.from_impls[&(type_name(&src), type_name(self_ty))].clone()
1050 };
1051 return self.func_named(&name, &m.sig, &m.block, None);
1052 }
1053 let tyname = type_name(self_ty);
1054 for it in &im.items {
1055 let syn::ImplItem::Fn(m) = it else {
1056 return Err(format!("unsupported item in `impl {tr}`"));
1057 };
1058 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1059 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
1060 self.func_named(&nim, &m.sig, &m.block, recv)?;
1061 }
1062 return Ok(());
1063 }
1064 for it in &im.items {
1065 match it {
1066 syn::ImplItem::Fn(m) => {
1067 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1068 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
1069 self.func_named(&nim, &m.sig, &m.block, recv)?;
1070 }
1071 _ => return Err("only `fn` items are supported inside `impl`".into()),
1072 }
1073 }
1074 Ok(())
1075 }
1076
1077 /// The type an operator impl declares for its right-hand operand.
1078 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
1079 let n = type_name(t.as_ref()?);
1080 let sig = self.methods.get(&(n, op_method(op).to_string()))?;
1081 sig.params.get(1).cloned().map(|t| t.unvar())
1082 }
1083
1084 /// The proc implementing `op` for a user type, if there is one.
1085 fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> {
1086 let n = type_name(t.as_ref()?);
1087 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
1088 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
1089 Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1))
1090 } else {
1091 None
1092 }
1093 }
1094
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago1095 fn emit_enum(&mut self, def: &EnumDef) {
1096 let name = ident(&def.name);
1097 if def.simple {
1098 // Every variant is a unit variant, so a plain Nim enum is an exact
1099 // fit: it compares, orders and `case`-checks like Rust's.
1100 self.line(&format!("type {name}* = enum"));
1101 self.indent += 1;
1102 for v in &def.variants {
1103 self.line(&format!("{}", ident(&v.name)));
1104 }
1105 self.indent -= 1;
1106 self.blank();
1107 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1108 self.indent += 1;
1109 self.line("case x");
1110 for v in &def.variants {
1111 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
1112 }
1113 self.indent -= 1;
1114 self.blank();
1115 return;
1116 }
1117
1118 // A data-carrying enum is a Nim object variant: one discriminant enum
1119 // plus a branch per variant. This is the same shape the prelude uses
1120 // for `Option` and `Result`.
1121 self.line("type");
1122 self.indent += 1;
1123 self.line(&format!("{}Kind* = enum", name));
1124 self.indent += 1;
1125 for v in &def.variants {
1126 self.line(&def.kind_ident(&v.name));
1127 }
1128 self.indent -= 1;
1129 self.blank();
1130 self.line(&format!("{}* = object", name));
1131 self.indent += 1;
1132 self.line(&format!("case kind*: {}Kind", name));
1133 for v in &def.variants {
1134 if v.fields.is_empty() {
1135 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
1136 } else {
1137 self.line(&format!("of {}:", def.kind_ident(&v.name)));
1138 self.indent += 1;
1139 for (f, t) in &v.fields {
1140 self.line(&format!("{}*: {}", ident(f), t.render()));
1141 }
1142 self.indent -= 1;
1143 }
1144 }
1145 self.indent -= 2;
1146 self.blank();
1147
1148 for v in &def.variants {
1149 let args: Vec<String> = v
1150 .fields
1151 .iter()
1152 .enumerate()
1153 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
1154 .collect();
1155 let inits: Vec<String> = v
1156 .fields
1157 .iter()
1158 .enumerate()
1159 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
1160 .collect();
1161 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1162 all.extend(inits);
1163 self.line(&format!(
1164 "proc {}*({}): {} = {}({})",
1165 def.ctor_ident(&v.name),
1166 args.join(", "),
1167 name,
1168 name,
1169 all.join(", ")
1170 ));
1171 }
1172 self.blank();
1173
1174 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1175 self.indent += 1;
1176 self.line("case x.kind");
1177 for v in &def.variants {
1178 if v.fields.is_empty() {
1179 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
1180 } else {
1181 let parts: Vec<String> = v
1182 .fields
1183 .iter()
1184 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
1185 .collect();
1186 self.line(&format!(
1187 "of {}: \"{}(\" & {} & \")\"",
1188 def.kind_ident(&v.name),
1189 v.name,
1190 parts.join(" & \", \" & ")
1191 ));
1192 }
1193 }
1194 self.indent -= 1;
1195 self.blank();
1196 }
1197
1198 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1199 /// to the enum that declares it.
1200 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
1201 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1202 let last = segs.last()?.clone();
1203 if segs.len() >= 2 {
1204 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
1205 if def.get(&last).is_some() {
1206 return Some((def.clone(), last));
1207 }
1208 }
1209 }
1210 // Unqualified: only unambiguous if exactly one enum declares it.
1211 match self.variant_owner.get(&last) {
1212 Some(owners) if owners.len() == 1 => {
1213 let def = self.enums.get(&owners[0])?;
1214 Some((def.clone(), last))
1215 }
1216 _ => None,
1217 }
1218 }
1219
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1220 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
1221 ///
1222 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
1223 /// observable result of `{}` is exactly the bytes written. So the method
1224 /// becomes `proc rsDisplay(self: T): string` and every write through the
1225 /// formatter produces that string. A `fmt` body that does anything else
1226 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
1227 /// because those affect the output and this model does not carry them.
1228 /// The window an expression names, if it names one.
1229 fn window_of(&self, e: &Expr) -> Option<Alias> {
1230 match e {
1231 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
1232 Some(a @ Alias::Window { .. }) => Some(a),
1233 _ => None,
1234 },
1235 Expr::Reference(r) => self.window_of(&r.expr),
1236 Expr::Paren(p) => self.window_of(&p.expr),
1237 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
1238 _ => None,
1239 }
1240 }
1241
1242 /// Whether an expression is the `Formatter` parameter of the formatting
1243 /// impl currently being lowered.
1244 fn is_fmt_param(&self, e: &Expr) -> bool {
1245 let Some(f) = &self.fmt_param else { return false };
1246 match e {
1247 Expr::Path(p) => path_name(&p.path) == *f,
1248 Expr::Reference(r) => self.is_fmt_param(&r.expr),
1249 Expr::Paren(p) => self.is_fmt_param(&p.expr),
1250 _ => false,
1251 }
1252 }
1253
1254 fn fmt_impl(
1255 &mut self,
1256 tr: &str,
1257 self_ty: &Nim,
1258 sig: &syn::Signature,
1259 body: &syn::Block,
1260 ) -> Result<(), String> {
1261 let proc_name = fmt_proc(tr);
1262 // The formatter is the parameter after `self`.
1263 let f = sig
1264 .inputs
1265 .iter()
1266 .filter_map(|a| match a {
1267 FnArg::Typed(t) => match &*t.pat {
1268 Pat::Ident(i) => Some(i.ident.to_string()),
1269 _ => None,
1270 },
1271 _ => None,
1272 })
1273 .next()
1274 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1275
1276 self.push_scope();
1277 self.bind("self", self_ty.clone());
1278 let saved = self.fmt_param.replace(f);
1279 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 10h ago1280 // No assignment target: a formatter write *appends*, because a `fmt`
1281 // body may write repeatedly -- `UpperHex` writes once per byte in a
1282 // loop -- and assigning would keep only the last one.
1283 let outer_target = self.target.take();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1284
1285 self.line(&format!(
1286 "proc {}*(self: {}): string =",
1287 proc_name,
1288 self_ty.render()
1289 ));
1290 self.indent += 1;
1291 let before = self.out.len();
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago1292 let tail = self.block_body(body)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1293 self.emit_tail(tail);
1294 if self.out.len() == before {
1295 self.line("discard");
1296 }
1297 self.indent -= 1;
1298
1299 self.target = outer_target;
1300 self.ret = outer_ret;
1301 self.fmt_param = saved;
1302 self.pop_scope();
1303 self.blank();
1304 Ok(())
1305 }
1306
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1307 fn func(
1308 &mut self,
1309 sig: &syn::Signature,
1310 body: &syn::Block,
1311 recv: Option<Nim>,
1312 ) -> Result<(), String> {
1313 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1314 self.func_named(&name.clone(), sig, body, recv)
1315 }
1316
1317 fn func_named(
1318 &mut self,
1319 name: &str,
1320 sig: &syn::Signature,
1321 body: &syn::Block,
1322 recv: Option<Nim>,
1323 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1324 let (ptys, ret) = self.signature(sig)?;
1325
1326 self.push_scope();
1327 let mut rendered: Vec<String> = Vec::new();
1328
1329 if let Some(self_ty) = recv {
1330 // `&mut self` and `mut self` both mean the body may mutate the
1331 // receiver; only the former is observable by the caller, and a Nim
1332 // `var` parameter is the faithful spelling of that.
1333 let mutable = matches!(
1334 sig.inputs.first(),
1335 Some(FnArg::Receiver(r))
1336 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1337 );
1338 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1339 rendered.push(format!("self: {}", t.render()));
1340 self.bind("self", self_ty);
1341 }
1342
1343 let typed: Vec<&syn::PatType> = sig
1344 .inputs
1345 .iter()
1346 .filter_map(|a| match a {
1347 FnArg::Typed(t) => Some(t),
1348 _ => None,
1349 })
1350 .collect();
1351 for (p, t) in typed.iter().zip(ptys.iter()) {
1352 let pname = match &*p.pat {
1353 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1354 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
1355 // still needs a name for it.
1356 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1357 _ => return Err("only plain identifier parameters are supported".into()),
1358 };
1359 rendered.push(format!("{}: {}", ident(&pname), t.render()));
1360 // Inside the body a `var T` parameter is used exactly like a `T`.
1361 self.bind(&pname, t.clone().owned());
1362 }
1363
1364 let head = if ret == Nim::Unit {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1365 format!("proc {}*({}) =", ident(name), rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1366 } else {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1367 format!("proc {}*({}): {} =", ident(name), rendered.join(", "), ret.render())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1368 };
1369 self.line(&head);
1370 self.indent += 1;
1371 let outer_ret = self.ret.replace(ret.clone());
1372
1373 // A Rust fn's trailing expression is its return value. Naming Nim's
1374 // implicit `result` as the target makes that true whether the tail is
1375 // a plain expression or an `if`/`match` with statement arms.
1376 let outer_target = if ret == Nim::Unit {
1377 self.target.take()
1378 } else {
1379 self.target.replace(("result".to_string(), Some(ret.clone())))
1380 };
1381 let before = self.out.len();
1382 let tail = self.block_body_at(body, Some(&ret))?;
1383 self.target = outer_target;
1384 match tail {
1385 Some(v) if ret != Nim::Unit => {
1386 let code = v.code.clone();
1387 self.line(&format!("result = {code}"));
1388 }
1389 Some(v) => {
1390 // A trailing expression in a `()`-returning fn is evaluated for
1391 // its effect; Nim requires an explicit discard.
1392 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1393 if needs_discard && !v.code.is_empty() {
1394 let code = v.code.clone();
1395 self.line(&format!("discard {code}"));
1396 }
1397 }
1398 None => {}
1399 }
1400 if self.out.len() == before {
1401 self.line("discard");
1402 }
1403
1404 self.indent -= 1;
1405 self.ret = outer_ret;
1406 self.pop_scope();
1407 self.blank();
1408 Ok(())
1409 }
1410
1411 // ---------------------------------------------------------- statements
1412
1413 /// Lower a block's statements. Returns the block's trailing expression,
1414 /// if it has one, *without* emitting it — the caller decides whether that
1415 /// value is a return value, a binding, or discarded.
1416 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
1417 self.block_body_at(b, None)
1418 }
1419
1420 fn block_body_at(
1421 &mut self,
1422 b: &syn::Block,
1423 expect: Option<&Nim>,
1424 ) -> Result<Option<Val>, String> {
1425 // An assignment target belongs to *this* block's trailing expression
1426 // only. A non-final `if` is a statement and must not assign anything.
1427 let target = self.target.take();
1428 let n = b.stmts.len();
1429 let mut tail = None;
1430 for (i, st) in b.stmts.iter().enumerate() {
1431 let last = i + 1 == n;
1432 match st {
1433 Stmt::Expr(e, None) if last && expressible(e) => {
1434 tail = Some(self.expr_at(e, expect)?)
1435 }
1436 Stmt::Expr(e, None) if last => {
1437 // A trailing `if`/`match` with statement arms, or a loop.
1438 // Lower it as statements; if this block's value is wanted,
1439 // each arm assigns it.
1440 match &target {
1441 Some((t, ty)) => {
1442 let (t, ty) = (t.clone(), ty.clone());
1443 self.assign_from(e, &t, ty.as_ref())?;
1444 }
1445 None => self.stmt(st)?,
1446 }
1447 }
1448 _ => self.stmt(st)?,
1449 }
1450 }
1451 self.target = target;
1452 Ok(tail)
1453 }
1454
1455 /// Lower a block in statement position (loop bodies, `if` arms).
1456 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
1457 self.push_scope();
1458 self.indent += 1;
1459 let before = self.out.len();
1460 let want = self.target.clone().and_then(|(_, t)| t);
1461 let tail = self.block_body_at(b, want.as_ref())?;
1462 self.emit_tail(tail);
1463 if self.out.len() == before {
1464 self.line("discard");
1465 }
1466 self.indent -= 1;
1467 self.pop_scope();
1468 Ok(())
1469 }
1470
1471 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
1472 match s {
1473 Stmt::Local(l) => self.local(l),
1474 Stmt::Expr(e, _) => {
1475 let v = self.expr_stmt(e)?;
1476 if let Some(v) = v {
1477 // A bare expression with a value must be discarded in Nim.
1478 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1479 let code = v.code.clone();
1480 if needs {
1481 self.line(&format!("discard {code}"));
1482 } else if !code.is_empty() {
1483 self.line(&code);
1484 }
1485 }
1486 Ok(())
1487 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago1488 // A `const` declared inside a function body is local to it, and
1489 // must be emitted here rather than skipped as an already-emitted
1490 // top-level type.
1491 Stmt::Item(i) => self.item_inner(i),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1492 Stmt::Macro(m) => {
1493 let line = self.macro_call(&m.mac)?;
1494 self.line(&line);
1495 Ok(())
1496 }
1497 }
1498 }
1499
1500 fn local(&mut self, l: &Local) -> Result<(), String> {
1501 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
1502 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
1503 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago1504 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 12h ago1505 _ => return Err("only `let <ident>` bindings are supported".into()),
1506 },
1507 Pat::Wild(_) => ("_".into(), false, None),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago1508 Pat::Tuple(t) => return self.local_tuple(l, t),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1509 _ => return Err("destructuring `let` is not implemented yet".into()),
1510 };
1511
1512 let Some(init) = &l.init else {
1513 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
1514 // not. Rust's own rules make reading it before assignment illegal,
1515 // so the two agree on every program rustc accepts.
1516 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
1517 let t = t.owned();
1518 self.line(&format!("var {}: {}", ident(&name), t.render()));
1519 self.bind(&name, t);
1520 return Ok(());
1521 };
1522 if init.diverge.is_some() {
1523 return Err("`let ... else` is not implemented yet".into());
1524 }
1525
1526 if !expressible(&init.expr) && name != "_" {
1527 // The initialiser is an `if`/`match` whose arms are statements.
1528 // Declare first, then let each arm assign into the binding.
1529 let t = ann
1530 .clone()
1531 .ok_or_else(|| {
1532 format!(
1533 "`let {name} = match/if ...` needs a type annotation: \
1534 its arms are statements, so the binding must be \
1535 declared before they run"
1536 )
1537 })?
1538 .owned();
1539 self.line(&format!("var {}: {}", ident(&name), t.render()));
1540 self.bind(&name, t.clone());
1541 let target = ident(&name);
1542 return self.assign_from(&init.expr, &target, Some(&t));
1543 }
1544
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago1545 // `let it = xs.chunks_exact(k)` binds an iterator, not a value.
1546 if is_iterator_expr(&init.expr) {
1547 let it = self.resolve_iter(&init.expr)?;
1548 self.bind_alias(&name, Alias::Iterator(Box::new(it)));
1549 return Ok(());
1550 }
1551
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1552 let v = self.expr_at(&init.expr, ann.as_ref())?;
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 10h ago1553
1554 // `let s = &buf[..n]` binds a view of a place that is already in
1555 // scope. Nim's borrow checker will not let a `let` borrow out of a
1556 // local, and there is nothing to materialise anyway -- a view is a
1557 // reference. Binding it as an alias substitutes the same expression at
1558 // each use, which re-evaluates nothing because the initialiser is a
1559 // place expression with no side effects.
1560 if v.window.is_none()
1561 && matches!(v.ty, Some(Nim::OpenArray(_)))
1562 && is_pure_place(&init.expr)
1563 {
1564 let t = v.ty.clone().unwrap();
1565 let elem = match &t {
1566 Nim::OpenArray(e) => Some((**e).clone()),
1567 _ => None,
1568 };
1569 self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) });
1570 let _ = elem;
1571 return Ok(());
1572 }
1573
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1574 if let Some(w) = v.window.clone() {
1575 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
1576 // view into the caller's buffer. Copying it into a `seq` would
1577 // still print the right bytes but would stop writes reaching the
1578 // caller, so it is bound as an alias.
1579 if v.guard.is_some() && v.guard_err.is_some() {
1580 return Err(format!(
1581 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
1582 which Nim cannot represent; apply `?` or `unwrap()` to it \
1583 in the same expression"
1584 ));
1585 }
1586 self.bind_alias(&name, w);
1587 return Ok(());
1588 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago1589 // A `let` binding a borrow keeps the view: `let res = encode(..)?`
1590 // names the caller's buffer, and copying it into a `seq` would still
1591 // print the right bytes while silently breaking the aliasing.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1592 let t = match (ann, &v.ty) {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago1593 (Some(a), _) => a.unvar(),
1594 (None, Some(t)) => t.clone().unvar(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1595 (None, None) => {
1596 return Err(format!(
1597 "cannot infer the type of `let {name}`; annotate it — \
1598 guessing here would change integer width, and with it the \
1599 meaning of any arithmetic on `{name}`"
1600 ))
1601 }
1602 };
1603
1604 if name == "_" {
1605 let code = v.code.clone();
1606 self.line(&format!("discard {code}"));
1607 return Ok(());
1608 }
1609 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
1610 // 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 10h ago1611 //
1612 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
1613 // Rust may write through it, and Nim only accepts a `var` where a
1614 // `var` parameter is wanted, so the binding has to be one.
1615 let mutable = mutable || is_mut_borrow(&init.expr);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1616 let kw = if mutable { "var" } else { "let" };
1617 let line = format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code);
1618 self.line(&line);
1619 self.bind(&name, t);
1620 Ok(())
1621 }
1622
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago1623 /// `let (a, b) = ..` — tuple destructuring.
1624 fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> {
1625 let Some(init) = &l.init else {
1626 return Err("a destructuring `let` needs an initialiser".into());
1627 };
1628 let names: Vec<(String, bool)> = t
1629 .elems
1630 .iter()
1631 .map(|p| match p {
1632 Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())),
1633 Pat::Wild(_) => Ok(("_".to_string(), false)),
1634 _ => Err("only plain identifiers are supported in a destructuring `let`"),
1635 })
1636 .collect::<Result<_, _>>()?;
1637
1638 // `split_at` hands back two *views* of the same slice. Nim has no
1639 // tuple of views, and there is nothing to materialise anyway, so each
1640 // name becomes a window into the original.
1641 if let Expr::MethodCall(m) = &*init.expr {
1642 let mname = m.method.to_string();
1643 if (mname == "split_at" || mname == "split_at_mut")
1644 && m.args.len() == 1
1645 && names.len() == 2
1646 {
1647 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
1648 let at = self.expr(&m.args[0])?;
1649 let cut = self.fresh("Cut");
1650 self.line(&format!("let {}: int = int({})", cut, at.code));
1651 self.bind_alias(
1652 &names[0].0,
1653 Alias::Window {
1654 code: code.clone(),
1655 off: base.clone(),
1656 len: cut.clone(),
1657 elem: elem.clone(),
1658 },
1659 );
1660 self.bind_alias(
1661 &names[1].0,
1662 Alias::Window {
1663 code,
1664 off: format!("({} + {})", base, cut),
1665 len: format!("({} - {})", len, cut),
1666 elem,
1667 },
1668 );
1669 return Ok(());
1670 }
1671 }
1672
1673 let v = self.expr(&init.expr)?;
1674 let tys = match &v.ty {
1675 Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(),
1676 _ => {
1677 return Err(format!(
1678 "cannot destructure this into {} bindings: its type is not a \
1679 tuple of that many elements",
1680 names.len()
1681 ))
1682 }
1683 };
1684 let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" };
1685 let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect();
1686 self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code));
1687 for ((n, _), t) in names.iter().zip(tys) {
1688 self.bind(n, t);
1689 }
1690 Ok(())
1691 }
1692
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1693 /// Expressions that are statements in Rust and statements in Nim too
1694 /// (control flow). Returns `None` when it emitted lines itself.
1695 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
1696 match e {
1697 Expr::If(_) => {
1698 self.if_stmt(e)?;
1699 Ok(None)
1700 }
1701 Expr::While(w) => {
1702 if w.label.is_some() {
1703 return Err("loop labels are not implemented yet".into());
1704 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago1705 self.in_loop_cond = true;
1706 let c = self.expr(&w.cond);
1707 self.in_loop_cond = false;
1708 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1709 self.line(&format!("while {}:", c.code));
1710 let saved = self.target.take();
1711 self.nested_block(&w.body)?;
1712 self.target = saved;
1713 Ok(None)
1714 }
1715 Expr::Loop(l) => {
1716 if l.label.is_some() {
1717 return Err("loop labels are not implemented yet".into());
1718 }
1719 self.line("while true:");
1720 let saved = self.target.take();
1721 self.nested_block(&l.body)?;
1722 self.target = saved;
1723 Ok(None)
1724 }
1725 Expr::ForLoop(f) => {
1726 self.for_loop(f)?;
1727 Ok(None)
1728 }
1729 Expr::Block(b) => {
1730 if b.label.is_some() {
1731 return Err("block labels are not implemented yet".into());
1732 }
1733 self.line("block:");
1734 self.nested_block(&b.block)?;
1735 Ok(None)
1736 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago1737 Expr::Unsafe(u) => {
1738 // Transparent in statement position too, for the same reason.
1739 self.nested_block_flat(&u.block)?;
1740 Ok(None)
1741 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1742 Expr::Match(_) => {
1743 self.match_stmt(e)?;
1744 Ok(None)
1745 }
1746 Expr::Return(r) => {
1747 match &r.expr {
1748 Some(e) => {
1749 let want = self.ret.clone();
1750 let v = self.expr_at(e, want.as_ref())?;
1751 self.line(&format!("return {}", v.code));
1752 }
1753 None => self.line("return"),
1754 }
1755 Ok(None)
1756 }
1757 Expr::Break(b) => {
1758 if b.expr.is_some() || b.label.is_some() {
1759 return Err("`break` with a value or a label is not implemented yet".into());
1760 }
1761 self.line("break");
1762 Ok(None)
1763 }
1764 Expr::Continue(c) => {
1765 if c.label.is_some() {
1766 return Err("labelled `continue` is not implemented yet".into());
1767 }
1768 self.line("continue");
1769 Ok(None)
1770 }
1771 Expr::Assign(a) => {
1772 let lhs = self.expr(&a.left)?;
1773 if !expressible(&a.right) {
1774 let target = lhs.code.clone();
1775 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
1776 }
1777 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
1778 self.line(&format!("{} = {}", lhs.code, rhs.code));
1779 Ok(None)
1780 }
1781 Expr::Binary(b) if is_compound(&b.op) => {
1782 let lhs = self.expr(&b.left)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago1783 // A compound assignment on a user type goes to that type's own
1784 // `impl OpAssign`, not to Nim's built-in operator.
1785 if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) {
1786 // The impl's own parameter type types the right operand,
1787 // so `b_vec *= 4` takes 4 at the width the impl declares.
1788 let want = self.op_param(&lhs.ty, compound_symbol(&b.op));
1789 let rhs = self.expr_at(&b.right, want.as_ref())?;
1790 self.line(&format!("{}({}, {})", f, lhs.code, rhs.code));
1791 return Ok(None);
1792 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1793 // `i += 1` must widen the literal to `i`'s type, not to the
1794 // i32 an unconstrained Rust literal would default to.
1795 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
1796 let op = self.bin_op(&b.op, &lhs, &rhs)?;
1797 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
1798 // both languages, so the expanded form is always correct.
1799 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
1800 Ok(None)
1801 }
1802 Expr::Macro(m) => {
1803 let line = self.macro_call(&m.mac)?;
1804 self.line(&line);
1805 Ok(None)
1806 }
1807 _ => Ok(Some(self.expr(e)?)),
1808 }
1809 }
1810
1811 /// Lower `e` in statement position, assigning each arm's value to
1812 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
1813 /// the trip when their arms are too big for a Nim `if`-expression.
1814 fn assign_from(
1815 &mut self,
1816 e: &Expr,
1817 target: &str,
1818 expect: Option<&Nim>,
1819 ) -> Result<(), String> {
1820 let saved = self.target.replace((target.to_string(), expect.cloned()));
1821 let r = match e {
1822 Expr::If(_) => self.if_stmt(e),
1823 Expr::Match(_) => self.match_stmt(e),
1824 other => {
1825 let v = self.expr_at(other, expect)?;
1826 self.line(&format!("{} = {}", target, v.code));
1827 Ok(())
1828 }
1829 };
1830 self.target = saved;
1831 r
1832 }
1833
1834 /// Emit a block's value into the active assignment target, if there is
1835 /// one, or discard it if there is not.
1836 fn emit_tail(&mut self, v: Option<Val>) {
1837 let Some(v) = v else { return };
1838 match self.target.clone() {
1839 Some((t, _)) => {
1840 let code = v.code.clone();
1841 self.line(&format!("{t} = {code}"));
1842 }
1843 None => {
1844 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1845 let code = v.code.clone();
1846 if needs {
1847 self.line(&format!("discard {code}"));
1848 } else if !code.is_empty() {
1849 self.line(&code);
1850 }
1851 }
1852 }
1853 }
1854
1855 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
1856 let Expr::If(i) = e else { unreachable!() };
1857 if let Expr::Let(_) = &*i.cond {
1858 return Err("`if let` is not implemented yet".into());
1859 }
1860 let c = self.expr(&i.cond)?;
1861 self.line(&format!("if {}:", c.code));
1862 self.nested_block(&i.then_branch)?;
1863 match &i.else_branch {
1864 None => {}
1865 Some((_, els)) => match &**els {
1866 Expr::If(_) => {
1867 // Nim needs `elif`; splice the nested `if` in as one.
1868 let mark = self.out.len();
1869 self.if_stmt(els)?;
1870 let tail = self.out.split_off(mark);
1871 let indent = " ".repeat(self.indent);
1872 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
1873 }
1874 Expr::Block(b) => {
1875 self.line("else:");
1876 self.nested_block(&b.block)?;
1877 }
1878 _ => return Err("unsupported `else` form".into()),
1879 },
1880 }
1881 Ok(())
1882 }
1883
1884 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
1885 if f.label.is_some() {
1886 return Err("loop labels are not implemented yet".into());
1887 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1888 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1889
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1890 // One index loop drives the whole chain. Rust's adaptors are lazy and
1891 // compose; resolving them to an index and binding each name to an
1892 // lvalue reproduces that without materialising anything.
1893 let i = self.fresh("Idx");
1894 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
1895 self.indent += 1;
1896 self.push_scope();
1897 let before = self.out.len();
1898
1899 self.bind_pattern(&f.pat, &it, &i)?;
1900
1901 let saved = self.target.take();
1902 if let Some(v) = self.block_body(&f.body)? {
1903 let code = v.code.clone();
1904 self.line(&format!("discard {code}"));
1905 }
1906 self.target = saved;
1907 if self.out.len() == before {
1908 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1909 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1910 self.pop_scope();
1911 self.indent -= 1;
1912 Ok(())
1913 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1914
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1915 /// Resolve a chain of iterator adaptors into a single `Iter`.
1916 ///
1917 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
1918 /// `filter`, `take_while` and friends are rejected rather than partially
1919 /// honoured: silently dropping an adaptor would change which elements the
1920 /// loop visits.
1921 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
1922 match e {
1923 Expr::Reference(r) => self.resolve_iter(&r.expr),
1924 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1925 Expr::Range(r) => {
1926 let lo = match &r.start {
1927 Some(e) => self.expr(e)?,
1928 None => return Err("a `for` over `..n` needs a start bound".into()),
1929 };
1930 let hi = match &r.end {
1931 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1932 None => {
1933 return Err("a `for` over an unbounded range would not terminate".into())
1934 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1935 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1936 let ty = lo.ty.clone().or(hi.ty.clone());
1937 Ok(Iter::Range {
1938 lo: lo.code,
1939 hi: hi.code,
1940 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
1941 ty,
1942 })
1943 }
1944 Expr::MethodCall(m) => {
1945 let name = m.method.to_string();
1946 match name.as_str() {
1947 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
1948 let mut it = self.resolve_iter(&m.receiver)?;
1949 if name == "iter_mut" {
1950 if let Iter::Elems { mutable, .. } = &mut it {
1951 *mutable = true;
1952 }
1953 }
1954 Ok(it)
1955 }
1956 "enumerate" if m.args.is_empty() => {
1957 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
1958 }
1959 "zip" if m.args.len() == 1 => {
1960 let a = self.resolve_iter(&m.receiver)?;
1961 let b = self.resolve_iter(&m.args[0])?;
1962 Ok(Iter::Zip(Box::new(a), Box::new(b)))
1963 }
1964 "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 11h ago1965 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1966 let k = self.expr(&m.args[0])?;
1967 Ok(Iter::Chunks {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago1968 code,
1969 base,
1970 len,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1971 k: k.code,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago1972 elem,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1973 mutable: name.ends_with("_mut"),
1974 })
1975 }
1976 "windows" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago1977 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1978 let k = self.expr(&m.args[0])?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago1979 Ok(Iter::Windows { code, base, len, k: k.code, elem })
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago1980 }
1981 other => Err(format!(
1982 "iterator adaptor `.{other}()` is not implemented; it has \
1983 no index-loop equivalent here, and dropping it would \
1984 change which elements the loop visits"
1985 )),
1986 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago1987 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago1988 Expr::Path(p) => {
1989 let n = path_name(&p.path);
1990 if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) {
1991 return Ok((*it).clone());
1992 }
1993 if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) {
1994 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
1995 }
1996 let v = self.expr(e)?;
1997 Ok(Iter::Elems {
1998 len: format!("{}.len", v.code),
1999 elem: elem_of(&v.ty),
2000 code: v.code,
2001 off: "0".into(),
2002 mutable: false,
2003 })
2004 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2005 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2006 // A `for` binding that is itself a window iterates that window,
2007 // not the whole container it points into.
2008 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 11h ago2009 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2010 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2011 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2012 Ok(Iter::Elems {
2013 len: format!("{}.len", v.code),
2014 elem: elem_of(&v.ty),
2015 code: v.code,
2016 off: "0".into(),
2017 mutable: false,
2018 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2019 }
2020 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2021 }
2022
2023 /// Bind a `for` pattern against a resolved iterator at index `i`.
2024 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
2025 match (p, it) {
2026 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
2027 self.bind_pattern(&t.elems[0], a, i)?;
2028 self.bind_pattern(&t.elems[1], b, i)
2029 }
2030 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
2031 if let Pat::Ident(id) = &t.elems[0] {
2032 let n = id.ident.to_string();
2033 // Rust's `enumerate` counts in `usize`.
2034 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
2035 self.bind(&n, Nim::Prim("uint".into()));
2036 }
2037 self.bind_pattern(&t.elems[1], inner, i)
2038 }
2039 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
2040 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
2041 ),
2042 (Pat::Wild(_), _) => Ok(()),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago2043 // `for &byte in xs` — the `&` destructures the reference, which in
2044 // Nim is already the value.
2045 (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
2046 (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2047 (Pat::Ident(id), _) => {
2048 let name = id.ident.to_string();
2049 match it {
2050 Iter::Range { lo, ty, .. } => {
2051 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
2052 // The loop counts from zero; the range's own start is
2053 // added back so the binding has Rust's value and type.
2054 self.line(&format!(
2055 "let {}: {} = {}({}) + {}",
2056 ident(&name),
2057 t.render(),
2058 t.render(),
2059 i,
2060 lo
2061 ));
2062 self.bind(&name, t);
2063 Ok(())
2064 }
2065 Iter::Elems { code, off, elem, mutable, .. } => {
2066 let access = if off == "0" {
2067 format!("{}[{}]", code, i)
2068 } else {
2069 format!("{}[{} + {}]", code, off, i)
2070 };
2071 if *mutable {
2072 // An alias, not a copy: assigning through the
2073 // binding must reach the original element.
2074 self.bind_alias(
2075 &name,
2076 Alias::Value { code: access, ty: elem.clone() },
2077 );
2078 } else {
2079 let t = elem
2080 .clone()
2081 .ok_or("cannot infer the element type of this `for`")?;
2082 self.line(&format!(
2083 "let {}: {} = {}",
2084 ident(&name),
2085 t.render(),
2086 access
2087 ));
2088 self.bind(&name, t);
2089 }
2090 Ok(())
2091 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago2092 Iter::Chunks { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2093 self.bind_alias(
2094 &name,
2095 Alias::Window {
2096 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago2097 off: format!("({} + {} * int({}))", base, i, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2098 len: format!("int({})", k),
2099 elem: elem.clone(),
2100 },
2101 );
2102 Ok(())
2103 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago2104 Iter::Windows { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2105 self.bind_alias(
2106 &name,
2107 Alias::Window {
2108 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago2109 off: format!("({} + {})", base, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2110 len: format!("int({})", k),
2111 elem: elem.clone(),
2112 },
2113 );
2114 Ok(())
2115 }
2116 // Handled above: a zip or enumerate needs a tuple pattern,
2117 // and binding one name to the pair is not supported.
2118 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
2119 }
2120 }
2121 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2122 }
2123 }
2124
2125 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
2126 let Expr::Match(m) = e else { unreachable!() };
2127 let scrut = self.expr(&m.expr)?;
2128 let t = scrut
2129 .ty
2130 .clone()
2131 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2132 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2133 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2134
2135 // A `match` whose arms neither bind nor guard is a Nim `case`, which
2136 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
2137 // an if/elif chain, because Nim's `case` cannot destructure.
2138 let plain = m.arms.iter().all(|a| {
2139 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
2140 });
2141 if plain {
2142 self.match_case(m, &name, &t)
2143 } else {
2144 self.match_chain(m, &name, &t)
2145 }
2146 }
2147
2148 fn match_case(
2149 &mut self,
2150 m: &syn::ExprMatch,
2151 name: &str,
2152 t: &Nim,
2153 ) -> Result<(), String> {
2154 // A variant object is discriminated by its `kind` field.
2155 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
2156 self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2157
2158 let mut saw_wild = false;
2159 for arm in &m.arms {
2160 match &arm.pat {
2161 Pat::Wild(_) => {
2162 saw_wild = true;
2163 self.line("else:");
2164 }
2165 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2166 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2167 self.line(&format!("of {}:", labels.join(", ")));
2168 }
2169 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2170 self.arm_body(&arm.body)?;
2171 }
2172 if !saw_wild && !self.case_is_total(t, m) {
2173 // Rust checked exhaustiveness already, but Nim cannot always see
2174 // it -- an integer `case` needs every value covered -- so make the
2175 // unreachable arm explicit rather than leave a compile error.
2176 self.line("else:");
2177 self.line(" rsPanic(\"unreachable match arm\")");
2178 }
2179 Ok(())
2180 }
2181
2182 /// Whether a Nim `case` over this type is already total, in which case
2183 /// adding an `else` would be a compile error rather than a safety net.
2184 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
2185 let Nim::Named(n, _) = t else { return false };
2186 let Some(def) = self.enums.get(n) else { return false };
2187 def.variants.len() == m.arms.len()
2188 }
2189
2190 /// The if/elif form, for arms that bind or destructure.
2191 fn match_chain(
2192 &mut self,
2193 m: &syn::ExprMatch,
2194 name: &str,
2195 t: &Nim,
2196 ) -> Result<(), String> {
2197 let mut first = true;
2198 let mut closed = false;
2199 for arm in &m.arms {
2200 let (pat, guard) = match &arm.pat {
2201 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
2202 p => (p, None),
2203 };
2204 if guard.is_some() && binds(pat) {
2205 return Err("a `match` guard on a binding pattern is not \
2206 implemented yet"
2207 .into());
2208 }
2209 let test = self.pat_test(pat, name, t)?;
2210 let test = match (test, guard) {
2211 (Some(t), Some(g)) => {
2212 let g = self.expr(g)?;
2213 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2214 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2215 (None, Some(g)) => Some(self.expr(g)?.code),
2216 (t, None) => t,
2217 };
2218 match test {
2219 Some(test) => {
2220 self.line(&format!(
2221 "{} {}:",
2222 if first { "if" } else { "elif" },
2223 test
2224 ));
2225 first = false;
2226 }
2227 None => {
2228 // An irrefutable pattern: everything left falls here.
2229 if first {
2230 self.line("block:");
2231 } else {
2232 self.line("else:");
2233 }
2234 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2235 }
2236 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2237 self.indent += 1;
2238 self.push_scope();
2239 let before = self.out.len();
2240 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2241 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2242 self.arm_body_at(&arm.body, before)?;
2243 self.pop_scope();
2244 if closed {
2245 break;
2246 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2247 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2248 if !closed {
2249 // Rust proved this unreachable; Nim cannot see that, and leaving
2250 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2251 self.line("else:");
2252 self.line(" rsPanic(\"unreachable match arm\")");
2253 }
2254 Ok(())
2255 }
2256
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2257 /// The condition that selects this arm, or `None` if it always matches.
2258 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
2259 Ok(match p {
2260 Pat::Wild(_) => None,
2261 Pat::Ident(i) if i.subpat.is_none() => None,
2262 Pat::Or(o) => {
2263 let mut parts = Vec::new();
2264 for c in &o.cases {
2265 match self.pat_test(c, name, t)? {
2266 Some(x) => parts.push(x),
2267 None => return Ok(None),
2268 }
2269 }
2270 Some(format!("({})", parts.join(" or ")))
2271 }
2272 Pat::Lit(_) | Pat::Range(_) => {
2273 let labels = self.pat_labels(p, Some(t))?;
2274 Some(match p {
2275 Pat::Range(_) => format!("({} in {})", name, labels[0]),
2276 _ => format!("({} == {})", name, labels[0]),
2277 })
2278 }
2279 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
2280 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
2281 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
2282 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
2283 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
2284 _ => return Err("unsupported `match` pattern".into()),
2285 })
2286 }
2287
2288 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
2289 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
2290 let last = path_name(path);
2291 match last.as_str() {
2292 "Ok" => return Ok(format!("{name}.ok")),
2293 "Err" => return Ok(format!("(not {name}.ok)")),
2294 "Some" => return Ok(format!("{name}.has")),
2295 "None" => return Ok(format!("(not {name}.has)")),
2296 _ => {}
2297 }
2298 let Some((def, v)) = self.resolve_variant(path) else {
2299 return Err(format!(
2300 "`{last}` in a pattern is not a known enum variant; if it names \
2301 an enum declared in another module, that is not implemented yet"
2302 ));
2303 };
2304 if let Nim::Named(n, _) = t {
2305 if *n != def.name {
2306 return Err(format!(
2307 "pattern `{}::{}` does not match the scrutinee type `{}`",
2308 def.name, v, n
2309 ));
2310 }
2311 }
2312 Ok(if def.simple {
2313 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
2314 } else {
2315 format!("({}.kind == {})", name, def.kind_ident(&v))
2316 })
2317 }
2318
2319 /// Emit the `let`s that a pattern's bindings introduce.
2320 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
2321 match p {
2322 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
2323 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
2324 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
2325 Pat::Ident(i) if i.subpat.is_none() => {
2326 let b = i.ident.to_string();
2327 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
2328 self.bind(&b, t.clone());
2329 Ok(())
2330 }
2331 Pat::TupleStruct(ts) => {
2332 let fields = self.variant_fields(&ts.path, t)?;
2333 for (i, sub) in ts.elems.iter().enumerate() {
2334 let Some((fname, fty)) = fields.get(i) else {
2335 return Err(format!(
2336 "pattern binds {} field(s) but the variant has {}",
2337 ts.elems.len(),
2338 fields.len()
2339 ));
2340 };
2341 let access = format!("{}.{}", name, ident(fname));
2342 self.pat_bind(sub, &access, fty)?;
2343 }
2344 Ok(())
2345 }
2346 Pat::Struct(st) => {
2347 let fields = self.variant_fields(&st.path, t)?;
2348 for f in &st.fields {
2349 let syn::Member::Named(m) = &f.member else {
2350 return Err("unsupported struct pattern field".into());
2351 };
2352 let m = m.to_string();
2353 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
2354 return Err(format!("unknown field `{m}` in pattern"));
2355 };
2356 let access = format!("{}.{}", name, ident(fname));
2357 self.pat_bind(&f.pat, &access, fty)?;
2358 }
2359 Ok(())
2360 }
2361 _ => Err("unsupported `match` pattern".into()),
2362 }
2363 }
2364
2365 /// The payload fields a variant pattern destructures.
2366 fn variant_fields(
2367 &self,
2368 path: &syn::Path,
2369 t: &Nim,
2370 ) -> Result<Vec<(String, Nim)>, String> {
2371 let last = path_name(path);
2372 // `Ok`/`Err`/`Some` read the prelude's own field names.
2373 if let Nim::Named(n, a) = t {
2374 match (n.as_str(), last.as_str()) {
2375 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
2376 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
2377 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
2378 _ => {}
2379 }
2380 }
2381 let Some((def, v)) = self.resolve_variant(path) else {
2382 return Err(format!("`{last}` is not a known enum variant"));
2383 };
2384 Ok(def.get(&v).map(|v| v.fields.clone()).unwrap_or_default())
2385 }
2386
2387 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
2388 self.indent += 1;
2389 let before = self.out.len();
2390 self.indent -= 1;
2391 self.arm_body_at(body, before)
2392 }
2393
2394 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
2395 match body {
2396 Expr::Block(b) => self.nested_block(&b.block)?,
2397 other => {
2398 self.indent += 1;
2399 // An arm's value is the `match`'s value, so it is typed by
2400 // whatever the `match` is being assigned to -- without which
2401 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
2402 let want = self.target.clone().and_then(|(_, t)| t);
2403 let v = match (want, expressible(other)) {
2404 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
2405 _ => self.expr_stmt(other)?,
2406 };
2407 self.emit_tail(v);
2408 self.indent -= 1;
2409 }
2410 }
2411 if self.out.len() == before {
2412 self.indent += 1;
2413 self.line("discard");
2414 self.indent -= 1;
2415 }
2416 Ok(())
2417 }
2418
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2419 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
2420 match p {
2421 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
2422 Pat::Or(o) => {
2423 let mut out = Vec::new();
2424 for p in &o.cases {
2425 out.extend(self.pat_labels(p, expect)?);
2426 }
2427 Ok(out)
2428 }
2429 Pat::Range(r) => {
2430 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
2431 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
2432 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
2433 let op = match r.limits {
2434 syn::RangeLimits::HalfOpen(_) => "..<",
2435 syn::RangeLimits::Closed(_) => "..",
2436 };
2437 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
2438 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2439 Pat::Path(pp) => {
2440 if let Some((def, v)) = self.resolve_variant(&pp.path) {
2441 return Ok(vec![if def.simple {
2442 format!("{}.{}", ident(&def.name), ident(&v))
2443 } else {
2444 def.kind_ident(&v)
2445 }]);
2446 }
2447 Ok(vec![ident(&path_name(&pp.path))])
2448 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2449 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2450 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2451 .into()),
2452 }
2453 }
2454
2455 // --------------------------------------------------------- expressions
2456
2457 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
2458 self.expr_at(e, None)
2459 }
2460
2461 /// Lower `e`, with the type the surrounding code expects of it.
2462 ///
2463 /// Rust infers an unsuffixed integer literal's type from its context and
2464 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
2465 /// expected type down to the literal is what makes `let x: u8 = 255` and
2466 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
2467 /// widths silently diverge, which is exactly the class of bug this
2468 /// project refuses to ship.
2469 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
2470 match e {
2471 Expr::Lit(l) => self.lit_at(&l.lit, expect),
2472 Expr::Path(p) => {
2473 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2474 if name == "None" {
2475 return Ok(Val::new(self.none_of(expect), expect.cloned()));
2476 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2477 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
2478 // declared here. In Nim that is a constructor call.
2479 if p.path.segments.len() > 1 {
2480 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
2481 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
2482 if n == "FmtError" {
2483 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
2484 }
2485 }
2486 }
2487 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
2488 return Ok(Val::new(
2489 format!("{}()", ident(&name)),
2490 Some(Nim::Named(name.clone(), vec![])),
2491 ));
2492 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2493 // A unit enum variant used as a value: `Error::InvalidLength`.
2494 if let Some((def, v)) = self.resolve_variant(&p.path) {
2495 let ty = Some(Nim::Named(def.name.clone(), vec![]));
2496 return Ok(if def.simple {
2497 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), ty)
2498 } else {
2499 Val::new(format!("{}()", def.ctor_ident(&v)), ty)
2500 });
2501 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2502 // A `for` binding that stands for an element of the container
2503 // it came from: using it must read (and assigning through it
2504 // must write) that element, not a copy.
2505 if let Some(a) = self.lookup_alias(&name) {
2506 return Ok(match a {
2507 Alias::Value { code, ty } => Val::new(code, ty),
2508 // A window *is* a slice; as a value it is the view it
2509 // denotes, which is what Rust's `&[T]` means too.
2510 Alias::Window { code, off, len, elem } => Val::new(
2511 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
2512 elem.map(|e| Nim::OpenArray(Box::new(e))),
2513 ),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago2514 // An iterator is not a value here: it is consumed by a
2515 // `for`, or asked for its `.remainder()`.
2516 Alias::Iterator(_) => {
2517 return Err(format!(
2518 "`{name}` is an iterator; it can be iterated or asked \
2519 for its `remainder()`, but not used as a value"
2520 ))
2521 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2522 });
2523 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2524 if let Some(t) = self.lookup(&name) {
2525 return Ok(Val::new(ident(&name), Some(t)));
2526 }
2527 // A top-level function used as a value, e.g. passed to a
2528 // parameter of `impl Fn(..)` type.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago2529 if let Some(k) = self.resolve_fn(&p.path) {
2530 let sig = &self.fns[&k];
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2531 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 11h ago2532 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 12h ago2533 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2534 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2535 }
2536 Expr::Paren(p) => {
2537 let v = self.expr_at(&p.expr, expect)?;
2538 Ok(Val::new(format!("({})", v.code), v.ty))
2539 }
2540 Expr::Group(g) => self.expr_at(&g.expr, expect),
2541 // `&x` is a value in Nim; `&mut x` in an argument position binds to
2542 // a `var` parameter, which is also just `x` at the call site.
2543 Expr::Reference(r) => self.expr_at(&r.expr, expect),
2544 Expr::Unary(u) => self.unary(u, expect),
2545 Expr::Binary(b) => self.binary(b, expect),
2546 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2547 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
2548 let Expr::Range(r) = &*i.index else { unreachable!() };
2549 let base = self.expr(&i.expr)?;
2550 let lo = match &r.start {
2551 Some(e) => format!("int({})", self.expr(e)?.code),
2552 None => "0".into(),
2553 };
2554 // Nim's `toOpenArray` takes an inclusive upper bound.
2555 let hi = match (&r.end, r.limits) {
2556 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2557 format!("int({}) - 1", self.expr(e)?.code)
2558 }
2559 (Some(e), syn::RangeLimits::Closed(_)) => {
2560 format!("int({})", self.expr(e)?.code)
2561 }
2562 (None, _) => format!("{}.len - 1", base.code),
2563 };
2564 let elem = elem_of(&base.ty)
2565 .ok_or("cannot infer the element type of this slice")?;
2566 Ok(Val::new(
2567 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
2568 Some(Nim::OpenArray(Box::new(elem))),
2569 ))
2570 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2571 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2572 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
2573 let idx = self.expr(&i.index)?;
2574 return Ok(Val::new(
2575 format!("{}[{} + int({})]", code, off, idx.code),
2576 elem,
2577 ));
2578 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2579 let base = self.expr(&i.expr)?;
2580 let idx = self.expr(&i.index)?;
2581 // Rust indexes with usize; Nim wants an `int`, and a `uint`
2582 // index is a type error there rather than a silent conversion.
2583 let idx_code = match &idx.ty {
2584 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
2585 _ => idx.code.clone(),
2586 };
2587 let elem = match base.ty.clone() {
2588 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
2589 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
2590 _ => None,
2591 };
2592 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
2593 }
2594 Expr::Field(f) => {
2595 let base = self.expr(&f.base)?;
2596 let name = match &f.member {
2597 syn::Member::Named(n) => n.to_string(),
2598 syn::Member::Unnamed(i) => format!("f{}", i.index),
2599 };
2600 let t = match &base.ty {
2601 Some(Nim::Named(s, _)) => self
2602 .structs
2603 .get(s)
2604 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
2605 .map(|(_, t)| t.clone()),
2606 _ => None,
2607 };
2608 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
2609 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago2610 // `unsafe` is a permission marker, not a semantic change: it does
2611 // not alter what the enclosed operations mean. So the block is
2612 // transparent here, and each operation inside still goes through
2613 // the ordinary lowering -- and is still rejected if it has no
2614 // faithful mapping.
2615 Expr::Unsafe(u) => match single_expr(&u.block) {
2616 Some(e) => self.expr_at(e, expect),
2617 None => Err("an `unsafe` block used as a value must be a single \
2618 expression"
2619 .into()),
2620 },
2621 Expr::Closure(c) => self.closure(c, expect),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2622 Expr::Try(t) => self.try_op(t),
2623 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago2624 Expr::MethodCall(m) => self.method(m, expect),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago2625 Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
2626 // `vec![..]`'s elements take their type from the annotation on
2627 // the binding, exactly as Rust's would.
2628 let want = match expect {
2629 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
2630 _ => None,
2631 };
2632 let saved = std::mem::replace(&mut self.vec_expect, want.clone());
2633 let code = self.macro_call(&m.mac);
2634 self.vec_expect = saved;
2635 let code = code?;
2636 let ty = match want {
2637 Some(e) => Some(Nim::Seq(Box::new(e))),
2638 None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
2639 };
2640 Ok(Val::new(code, ty))
2641 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2642 Expr::Macro(m) => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago2643 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 12h ago2644 let code = self.macro_call(&m.mac)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago2645 // A formatter write is a statement that appends, not a value.
2646 let ty = if is_write { Some(Nim::Unit) } else { None };
2647 Ok(Val::new(code, ty))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2648 }
2649 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2650 if s.rest.is_some() {
2651 return Err("struct update syntax `..rest` is not implemented yet".into());
2652 }
2653 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
2654 // which is constructed positionally in Nim.
2655 if let Some((def, v)) = self.resolve_variant(&s.path) {
2656 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2657 let mut args = vec![String::new(); fields.len()];
2658 for f in &s.fields {
2659 let syn::Member::Named(m) = &f.member else {
2660 return Err("unsupported enum variant field".into());
2661 };
2662 let want = format!("{}_{}", v, m);
2663 let i = fields
2664 .iter()
2665 .position(|(n, _)| *n == want)
2666 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
2667 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
2668 }
2669 if let Some(i) = args.iter().position(|a| a.is_empty()) {
2670 return Err(format!(
2671 "`{}::{}` is missing field `{}`",
2672 def.name, v, fields[i].0
2673 ));
2674 }
2675 return Ok(Val::new(
2676 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
2677 Some(Nim::Named(def.name.clone(), vec![])),
2678 ));
2679 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2680 let name = path_name(&s.path);
2681 let mut parts = Vec::new();
2682 for f in &s.fields {
2683 let fname = match &f.member {
2684 syn::Member::Named(n) => n.to_string(),
2685 syn::Member::Unnamed(i) => format!("f{}", i.index),
2686 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2687 let want = self
2688 .structs
2689 .get(&name)
2690 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
2691 .map(|(_, t)| t.clone());
2692 let v = self.expr_at(&f.expr, want.as_ref())?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2693 parts.push(format!("{}: {}", ident(&fname), v.code));
2694 }
2695 Ok(Val::new(
2696 format!("{}({})", ident(&name), parts.join(", ")),
2697 Some(Nim::Named(name, vec![])),
2698 ))
2699 }
2700 Expr::Array(a) => {
2701 let mut parts = Vec::new();
2702 let mut elem = match expect {
2703 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
2704 Some((**t).clone())
2705 }
2706 _ => None,
2707 };
2708 for e in &a.elems {
2709 let want = elem.clone();
2710 let v = self.expr_at(e, want.as_ref())?;
2711 elem = elem.or(v.ty.clone());
2712 parts.push(v.code);
2713 }
2714 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
2715 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
2716 }
2717 Expr::Repeat(r) => {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago2718 // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size
2719 // array from a `seq`, so the expected type decides which, and
2720 // an array needs its elements written out.
2721 let want_elem = match expect {
2722 Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => {
2723 Some((**e).clone())
2724 }
2725 _ => None,
2726 };
2727 let v = self.expr_at(&r.expr, want_elem.as_ref())?;
2728 if let Some(Nim::Array(n, _)) = expect {
2729 let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect();
2730 let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t)));
2731 return Ok(Val::new(format!("[{}]", elems.join(", ")), t));
2732 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2733 let n = self.expr(&r.len)?;
2734 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
2735 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
2736 }
2737 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
2738 Expr::Tuple(t) => {
2739 let mut parts = Vec::new();
2740 let mut tys = Vec::new();
2741 for e in &t.elems {
2742 let v = self.expr(e)?;
2743 tys.push(v.ty.clone());
2744 parts.push(v.code);
2745 }
2746 let ty = tys
2747 .iter()
2748 .cloned()
2749 .collect::<Option<Vec<_>>>()
2750 .map(Nim::Tuple);
2751 Ok(Val::new(format!("({})", parts.join(", ")), ty))
2752 }
2753 // `if` and `match` are expressions in both languages, but only
2754 // when every arm is itself a single expression.
2755 Expr::If(i) => self.if_expr(i, expect),
2756 Expr::Block(b) if b.block.stmts.len() == 1 => {
2757 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
2758 self.expr_at(e, expect)
2759 } else {
2760 Err("block expression with statements in value position is not implemented yet".into())
2761 }
2762 }
2763 other => Err(format!(
2764 "unsupported expression in value position: {}",
2765 expr_kind(other)
2766 )),
2767 }
2768 }
2769
2770 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
2771 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
2772 return Err(
2773 "an `if` used as a value must have an `else` and single-expression arms".into(),
2774 );
2775 };
2776 let c = self.expr(&i.cond)?;
2777 let t = self.expr_at(then, expect)?;
2778 let want = expect.cloned().or_else(|| t.ty.clone());
2779 let e = match &**els {
2780 Expr::Block(b) => match single_expr(&b.block) {
2781 Some(x) => self.expr_at(x, want.as_ref())?,
2782 None => return Err("an `if` used as a value must have single-expression arms".into()),
2783 },
2784 other => self.expr_at(other, want.as_ref())?,
2785 };
2786 let ty = t.ty.clone().or(e.ty.clone());
2787 Ok(Val::new(
2788 format!("(if {}: {} else: {})", c.code, t.code, e.code),
2789 ty,
2790 ))
2791 }
2792
2793 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
2794 match l {
2795 Lit::Int(i) => {
2796 let suffix = i.suffix();
2797 if let Some(why) = ty::rejected(suffix) {
2798 return Err(format!("integer literal `{}`: {}", i, why));
2799 }
2800 let digits = i.base10_digits().to_string();
2801 // Rust's default for an unconstrained integer literal is i32.
2802 // Nim's is `int` (64-bit). Making the width explicit is what
2803 // keeps overflow behaviour the same on both sides.
2804 let t = if suffix.is_empty() {
2805 match expect {
2806 Some(t) if t.is_integer() => t.clone(),
2807 // Rust's fallback for an otherwise-unconstrained
2808 // integer literal.
2809 _ => Nim::Prim("int32".into()),
2810 }
2811 } else {
2812 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
2813 };
2814 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
2815 }
2816 Lit::Float(f) => {
2817 let t = match f.suffix() {
2818 "" => match expect {
2819 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
2820 _ => Nim::Prim("float64".into()),
2821 },
2822 "f64" => Nim::Prim("float64".into()),
2823 "f32" => Nim::Prim("float32".into()),
2824 s => return Err(format!("unknown float suffix `{s}`")),
2825 };
2826 let d = f.base10_digits();
2827 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
2828 Ok(Val::new(d, Some(t)))
2829 }
2830 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
2831 Lit::Str(s) => Ok(Val::new(
2832 fmt::nim_str(&s.value()),
2833 Some(Nim::Prim("string".into())),
2834 )),
2835 Lit::Char(c) => Ok(Val::new(
2836 format!("Rune({})", c.value() as u32),
2837 Some(Nim::Prim("Rune".into())),
2838 )),
2839 Lit::Byte(b) => Ok(Val::new(
2840 format!("{}'u8", b.value()),
2841 Some(Nim::Prim("uint8".into())),
2842 )),
2843 Lit::ByteStr(b) => {
2844 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
2845 Ok(Val::new(
2846 format!("@[{}]", bytes.join(", ")),
2847 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
2848 ))
2849 }
2850 other => Err(format!("unsupported literal: {other:?}")),
2851 }
2852 }
2853
2854 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
2855 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
2856 // the positive half of the range before the negation runs. Folding the
2857 // sign into the literal keeps `i8::MIN` and friends expressible.
2858 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
2859 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
2860 let v = self.lit_at(&l.lit, expect)?;
2861 return Ok(Val::new(format!("-{}", v.code), v.ty));
2862 }
2863 }
2864 let v = self.expr_at(&u.expr, expect)?;
2865 match u.op {
2866 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
2867 // Rust's `!` is logical on bool and bitwise-complement on integers.
2868 // Nim spells those `not` and `not` as well, so one mapping covers
2869 // both — but only because Nim overloads `not` the same way.
2870 UnOp::Not(_) => Ok(Val::new(format!("(not {})", v.code), v.ty)),
2871 UnOp::Deref(_) => Ok(v),
2872 _ => Err("unsupported unary operator".into()),
2873 }
2874 }
2875
2876 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
2877 // A comparison's operands are unrelated to the `bool` it produces, so
2878 // the outer expectation is not passed through to them.
2879 let down = match b.op {
2880 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
2881 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
2882 _ => expect,
2883 };
2884 let mut l = self.expr_at(&b.left, down)?;
2885 // Rust unifies the two operand types; propagating whichever side is
2886 // known to the other reproduces that, and disagreement then surfaces
2887 // as a Nim type error rather than as a silent width change.
2888 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
2889 if l.ty.is_none() && r.ty.is_some() {
2890 l = self.expr_at(&b.left, r.ty.as_ref())?;
2891 }
2892 let r = std::mem::replace(&mut r, Val::untyped(""));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago2893 // A binary operator on a user type goes to that type's own impl.
2894 if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) {
2895 let want = self.op_param(&l.ty, binary_symbol(&b.op));
2896 let r = self.expr_at(&b.right, want.as_ref())?;
2897 let ret = self
2898 .methods
2899 .get(&(
2900 type_name(l.ty.as_ref().unwrap()),
2901 op_method(binary_symbol(&b.op)).to_string(),
2902 ))
2903 .map(|s| s.ret.clone());
2904 return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret));
2905 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2906 let op = self.bin_op(&b.op, &l, &r)?;
2907 let ty = match b.op {
2908 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
2909 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
2910 // Rust's shift takes its result type from the *left* operand, and
2911 // the right may be a different width entirely.
2912 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
2913 _ => l.ty.clone().or(r.ty.clone()),
2914 };
2915 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
2916 }
2917
2918 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
2919 Ok(match op {
2920 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
2921 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
2922 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
2923 BinOp::Div(_) | BinOp::DivAssign(_) => {
2924 // Nim spells integer division `div`. Both languages truncate
2925 // toward zero, so once the right operator is chosen the
2926 // semantics match, including for negative operands.
2927 let t = l.ty.clone().or(r.ty.clone()).ok_or(
2928 "cannot tell integer from float division here; annotate the operands",
2929 )?;
2930 if t.is_integer() { "div" } else { "/" }
2931 }
2932 BinOp::Rem(_) | BinOp::RemAssign(_) => {
2933 let t = l.ty.clone().or(r.ty.clone()).ok_or(
2934 "cannot tell integer from float remainder here; annotate the operands",
2935 )?;
2936 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
2937 }
2938 BinOp::And(_) => "and",
2939 BinOp::Or(_) => "or",
2940 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
2941 // bools, exactly as Rust's `&`/`|`/`^` are.
2942 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
2943 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
2944 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
2945 // Settled empirically: Nim's `shr` on a signed integer is
2946 // arithmetic, matching Rust. See DESIGN.md.
2947 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
2948 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
2949 BinOp::Eq(_) => "==",
2950 BinOp::Ne(_) => "!=",
2951 BinOp::Lt(_) => "<",
2952 BinOp::Le(_) => "<=",
2953 BinOp::Gt(_) => ">",
2954 BinOp::Ge(_) => ">=",
2955 other => return Err(format!("unsupported binary operator {other:?}")),
2956 })
2957 }
2958
2959 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
2960 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago2961 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2962 let from = v.ty.clone().ok_or_else(|| {
2963 format!(
2964 "cannot lower `as {}`: the source type is unknown, and `as` \
2965 truncates, so the source width decides the result",
2966 to.render()
2967 )
2968 })?;
2969
2970 let code = match (&from, &to) {
2971 (f, t) if f.is_integer() && t.is_integer() => {
2972 // Rust's `as` between integers is a pure bit-width truncation
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 10h ago2973 // or sign-extension, never a range check. `cast` says exactly
2974 // that. (Nim's `T(x)` turns out to truncate here as well --
2975 // see DESIGN.md item 5 -- but `cast` is the spelling that
2976 // means it rather than the one that happens to agree.)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago2977 format!("cast[{}]({})", t.render(), v.code)
2978 }
2979 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
2980 format!("{}({})", p, v.code)
2981 }
2982 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
2983 format!("{}(ord({}))", t.render(), v.code)
2984 }
2985 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
2986 format!("cast[{}](int32({}))", t.render(), v.code)
2987 }
2988 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
2989 format!("Rune(int32({}))", v.code)
2990 }
2991 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
2992 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
2993 // Rust saturates float->int casts; Nim rounds and range-errors.
2994 // Not the same operation, so it is refused rather than mapped.
2995 return Err(format!(
2996 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
2997 no faithful mapping is implemented",
2998 t.render()
2999 ));
3000 }
3001 (f, t) => {
3002 return Err(format!(
3003 "unsupported cast from `{}` to `{}`",
3004 f.render(),
3005 t.render()
3006 ))
3007 }
3008 };
3009 Ok(Val::new(code, Some(to)))
3010 }
3011
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3012 /// Rust's `?`: return early on the error branch, otherwise yield the value.
3013 ///
3014 /// The early return is statements, not an expression, so they are emitted
3015 /// ahead of the line being built. Every caller lowers its sub-expressions
3016 /// 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 11h ago3017 /// The container, start offset, length and element type an expression
3018 /// denotes as a slice. A window alias contributes its own offset, so
3019 /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
3020 /// into the original buffer rather than through a rebuilt view.
3021 fn slice_parts(
3022 &mut self,
3023 e: &Expr,
3024 ) -> Result<(String, String, String, Option<Nim>), String> {
3025 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
3026 return Ok((code, off, len, elem));
3027 }
3028 let v = self.expr(e)?;
3029 let len = format!("{}.len", v.code);
3030 Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
3031 }
3032
3033 /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
3034 fn map_closure(
3035 &mut self,
3036 what: &str,
3037 recv: &Val,
3038 kind: &str,
3039 targs: &[Nim],
3040 c: &syn::ExprClosure,
3041 ) -> Result<Val, String> {
3042 if c.capture.is_some() {
3043 return Err("a `move` closure captures by value; Nim's closures \
3044 capture by reference, and the two are not the same"
3045 .into());
3046 }
3047 if c.inputs.len() != 1 {
3048 return Err(format!("`.{what}()` takes a one-argument closure"));
3049 }
3050 let pname = match &c.inputs[0] {
3051 Pat::Ident(i) => i.ident.to_string(),
3052 Pat::Wild(_) => "unused0".into(),
3053 _ => return Err("only plain identifier closure parameters are supported".into()),
3054 };
3055
3056 let is_opt = kind == "Option";
3057 let tmp = self.fresh("Map");
3058 let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
3059 self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
3060
3061 let body = match &*c.body {
3062 Expr::Block(b) => single_expr(&b.block)
3063 .ok_or("a closure body with statements is not implemented yet")?,
3064 other => other,
3065 };
3066 self.push_scope();
3067 // The parameter names the payload itself, so a view stays a view.
3068 self.bind_alias(
3069 &pname,
3070 Alias::Value {
3071 code: format!("{}.val", tmp),
3072 ty: Some(targs[0].clone()),
3073 },
3074 );
3075 let v = self.expr(body)?;
3076 self.pop_scope();
3077
3078 let inner = v
3079 .ty
3080 .clone()
3081 .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
3082 // `and_then`'s closure already returns the wrapped type; `map`'s does
3083 // not and has to be re-wrapped.
3084 let (test, some_branch, none_branch, out_ty) = if is_opt {
3085 let out = if what == "map" {
3086 Nim::Named("Option".into(), vec![inner.clone()])
3087 } else {
3088 inner.clone()
3089 };
3090 let body_code = if what == "map" {
3091 format!("rsSome[{}]({})", inner.render(), v.code)
3092 } else {
3093 v.code.clone()
3094 };
3095 (
3096 format!("{}.has", tmp),
3097 body_code,
3098 format!("rsNone[{}]()", elem_arg(&out).render()),
3099 out,
3100 )
3101 } else {
3102 let e = targs[1].clone();
3103 let out = if what == "map" {
3104 Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
3105 } else {
3106 inner.clone()
3107 };
3108 let ok_ty = elem_arg(&out);
3109 let body_code = if what == "map" {
3110 format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
3111 } else {
3112 v.code.clone()
3113 };
3114 (
3115 format!("{}.ok", tmp),
3116 body_code,
3117 format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
3118 out,
3119 )
3120 };
3121 Ok(Val::new(
3122 format!("(if {}: {} else: {})", test, some_branch, none_branch),
3123 Some(out_ty),
3124 ))
3125 }
3126
3127 /// `|x| x + 1` -> a Nim anonymous proc.
3128 ///
3129 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
3130 /// A `move` closure captures by value, which is a different thing, so it
3131 /// is rejected rather than lowered to the same construct.
3132 fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
3133 if c.capture.is_some() {
3134 return Err("a `move` closure captures by value; Nim's closures \
3135 capture by reference, and the two are not the same"
3136 .into());
3137 }
3138 let want: Option<&Vec<Nim>> = match expect {
3139 Some(Nim::Proc(a, _)) => Some(a),
3140 _ => None,
3141 };
3142
3143 self.push_scope();
3144 let mut parts = Vec::new();
3145 let mut ptys = Vec::new();
3146 for (i, p) in c.inputs.iter().enumerate() {
3147 let (name, ann) = match p {
3148 Pat::Ident(id) => (id.ident.to_string(), None),
3149 Pat::Type(t) => match &*t.pat {
3150 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
3151 _ => return Err("only plain identifier closure parameters are supported".into()),
3152 },
3153 Pat::Wild(_) => (format!("unused{i}"), None),
3154 _ => return Err("only plain identifier closure parameters are supported".into()),
3155 };
3156 let t = ann
3157 .or_else(|| want.and_then(|w| w.get(i).cloned()))
3158 .ok_or_else(|| {
3159 format!(
3160 "cannot infer the type of closure parameter `{name}`; \
3161 annotate it"
3162 )
3163 })?;
3164 parts.push(format!("{}: {}", ident(&name), t.render()));
3165 self.bind(&name, t.clone());
3166 ptys.push(t);
3167 }
3168
3169 let ret_ann = match &c.output {
3170 ReturnType::Default => None,
3171 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
3172 };
3173 let body = match &*c.body {
3174 Expr::Block(b) => single_expr(&b.block)
3175 .ok_or("a closure body with statements is not implemented yet")?,
3176 other => other,
3177 };
3178 let v = self.expr_at(body, ret_ann.as_ref())?;
3179 self.pop_scope();
3180
3181 let ret = ret_ann
3182 .or_else(|| v.ty.clone())
3183 .ok_or("cannot infer a closure's return type; annotate it")?;
3184 Ok(Val::new(
3185 format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
3186 Some(Nim::Proc(ptys, Box::new(ret))),
3187 ))
3188 }
3189
3190 /// Lower a block's statements at the current indentation, without opening
3191 /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
3192 /// of its own in the generated code.
3193 fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
3194 self.push_scope();
3195 let tail = self.block_body(b)?;
3196 self.emit_tail(tail);
3197 self.pop_scope();
3198 Ok(())
3199 }
3200
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3201 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
3202 if self.in_loop_cond {
3203 return Err("`?` in a loop condition is not implemented yet: the \
3204 early-return it expands to would be evaluated once, \
3205 before the loop, rather than on each iteration"
3206 .into());
3207 }
3208 let v = self.expr(&t.expr)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3209 if self.fmt_param.is_some() {
3210 // Writing into a string cannot fail, so `?` on a formatter write
3211 // is a no-op. `?` on anything else can fail, and `format!` panics
3212 // when a formatting impl returns an error -- so that is what the
3213 // error branch does here, with std's own message.
3214 if v.ty.as_ref() == Some(&Nim::Unit) {
3215 return Ok(v);
3216 }
3217 if let Some(Nim::Named(n, a)) = v.ty.clone() {
3218 if n == "Result" && a.len() == 2 {
3219 let tmp = self.fresh("Fmt");
3220 self.line(&format!(
3221 "let {}: {} = {}",
3222 tmp,
3223 Nim::Named(n, a.clone()).render(),
3224 v.code
3225 ));
3226 self.line(&format!("if not {}.ok:", tmp));
3227 self.line(
3228 " rsPanic(\"a formatting trait implementation returned an error\")",
3229 );
3230 return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone())));
3231 }
3232 }
3233 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3234 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
3235 // An `Option`/`Result` of a view: the check is emitted here and the
3236 // view itself survives as an alias, since it has no value form.
3237 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
3238 let err = v.guard_err.clone().ok_or(
3239 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
3240 )?;
3241 let Nim::Named(n, ra) = &ret else {
3242 return Err(format!("`?` in a function returning `{}`", ret.render()));
3243 };
3244 if n != "Result" || ra.len() != 2 {
3245 return Err(format!("`?` in a function returning `{}`", ret.render()));
3246 }
3247 self.line(&format!("if not {}:", guard));
3248 self.line(&format!(
3249 " return rsErr[{}, {}]({})",
3250 ra[0].render(),
3251 ra[1].render(),
3252 err
3253 ));
3254 let mut out = Val::new(String::new(), None);
3255 out.window = Some(w);
3256 return Ok(out);
3257 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3258 let vt = v.ty.clone().ok_or(
3259 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
3260 )?;
3261 let ret = self
3262 .ret
3263 .clone()
3264 .ok_or("`?` outside a function with a return type")?;
3265 let tmp = self.fresh("Try");
3266 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
3267
3268 match (&vt, &ret) {
3269 (Nim::Named(a, ai), Nim::Named(b, bi))
3270 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
3271 {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3272 // Rust inserts a `From::from` on the error here. Where the
3273 // types differ we call the crate's own `impl From`; we never
3274 // assume the conversion is the identity.
3275 let err = if ai[1] == bi[1] {
3276 format!("{}.err", tmp)
3277 } else {
3278 let key = (type_name(&ai[1]), type_name(&bi[1]));
3279 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
3280 format!(
3281 "`?` needs `From<{}> for {}` to convert the error, and \
3282 no such `impl` is in scope; assuming the conversion is \
3283 the identity would be a guess",
3284 key.0, key.1
3285 )
3286 })?;
3287 format!("{}({}.err)", f, tmp)
3288 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3289 self.line(&format!("if not {}.ok:", tmp));
3290 self.line(&format!(
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3291 " return rsErr[{}, {}]({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3292 bi[0].render(),
3293 bi[1].render(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3294 err
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3295 ));
3296 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3297 }
3298 (Nim::Named(a, ai), Nim::Named(b, bi))
3299 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
3300 {
3301 self.line(&format!("if not {}.has:", tmp));
3302 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
3303 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3304 }
3305 _ => Err(format!(
3306 "`?` on `{}` in a function returning `{}` is not a supported \
3307 combination",
3308 vt.render(),
3309 ret.render()
3310 )),
3311 }
3312 }
3313
3314 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 12h ago3315 let Expr::Path(p) = &*c.func else {
3316 return Err("only calls to named functions are supported".into());
3317 };
3318 let name = path_name(&p.path);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3319 let target = self.resolve_fn(&p.path);
3320 let ptys: Vec<Nim> = target
3321 .as_ref()
3322 .and_then(|k| self.fns.get(k))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3323 .map(|s| s.params.clone())
3324 .unwrap_or_default();
3325 let mut args = Vec::new();
3326 for (i, a) in c.args.iter().enumerate() {
3327 let want = ptys.get(i).cloned();
3328 args.push(self.expr_at(a, want.as_ref())?);
3329 }
3330 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
3331
3332 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3333 // `Ok`/`Err` must name the *whole* Result type, not just the half
3334 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
3335 match name.as_str() {
3336 "Some" => {
3337 let inner = match expect {
3338 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
3339 _ => {
3340 return Err("`Some(..)` needs a known `Option<T>` type here; \
3341 annotate the binding or the return type"
3342 .into())
3343 }
3344 };
3345 return Ok(Val::new(
3346 format!("rsSome[{}]({})", inner, codes.join(", ")),
3347 expect.cloned(),
3348 ));
3349 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3350 "Ok" if self.fmt_param.is_some()
3351 && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) =>
3352 {
3353 // `Ok(())` ends a `fmt` body: nothing more is written.
3354 return Ok(Val::new(String::new(), Some(Nim::Unit)));
3355 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3356 "Ok" | "Err" => {
3357 let (t, e) = match expect {
3358 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
3359 (a[0].render(), a[1].render())
3360 }
3361 _ => {
3362 return Err(format!(
3363 "`{name}(..)` needs a known `Result<T, E>` type here; \
3364 annotate the binding or the return type"
3365 ))
3366 }
3367 };
3368 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
3369 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
3370 return Ok(Val::new(
3371 format!("{}[{}, {}]({})", ctor, t, e, arg),
3372 expect.cloned(),
3373 ));
3374 }
3375 _ => {}
3376 }
3377
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3378 // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's
3379 // object constructor names its fields even when Rust's does not.
3380 if let Some(fields) = self.structs.get(&name).cloned() {
3381 if fields.len() == c.args.len() {
3382 let mut parts = Vec::new();
3383 for (i, a) in c.args.iter().enumerate() {
3384 let v = self.expr_at(a, Some(&fields[i].1))?;
3385 parts.push(format!("{}: {}", ident(&fields[i].0), v.code));
3386 }
3387 return Ok(Val::new(
3388 format!("{}({})", ident(&name), parts.join(", ")),
3389 Some(Nim::Named(name.clone(), vec![])),
3390 ));
3391 }
3392 }
3393
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago3394 // `u32::from(b)`: `From` between primitives is lossless by definition
3395 // -- it is the widening direction only -- so a plain Nim conversion is
3396 // exact. (The truncating direction is `as`, which is `cast`.)
3397 if name == "from" && codes.len() == 1 {
3398 if let Some(q) = p.path.segments.iter().rev().nth(1) {
3399 if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) {
3400 return Ok(Val::new(
3401 format!("{}({})", t, codes[0]),
3402 Some(Nim::Prim(t)),
3403 ));
3404 }
3405 }
3406 }
3407
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3408 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
3409 // string view; no copy, no validation, same memory.
3410 if name == "from_utf8_unchecked" && codes.len() == 1 {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3411 // `String::from_utf8_unchecked(v)` takes ownership and yields an
3412 // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields
3413 // a view. Same name, different operations -- the qualifier says
3414 // which, and an unqualified call is ambiguous.
3415 let q = p
3416 .path
3417 .segments
3418 .iter()
3419 .rev()
3420 .nth(1)
3421 .map(|s| s.ident.to_string());
3422 return match q.as_deref() {
3423 Some("String") => Ok(Val::new(
3424 format!("rsStringOf({})", codes[0]),
3425 Some(Nim::Prim("string".into())),
3426 )),
3427 Some("str") => Ok(Val::new(
3428 format!("rsStrView({})", codes[0]),
3429 Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
3430 )),
3431 _ => Err(
3432 "`from_utf8_unchecked` must be written as `str::..` (a \
3433 borrowed view) or `String::..` (an owned string); the two \
3434 are different operations"
3435 .into(),
3436 ),
3437 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3438 }
3439
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3440 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
3441 if let Some((def, v)) = self.resolve_variant(&p.path) {
3442 return Ok(Val::new(
3443 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
3444 Some(Nim::Named(def.name.clone(), vec![])),
3445 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3446 }
3447
3448 // A bare path that names a primitive type is Rust's tuple-struct-like
3449 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3450 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
3451 // is invoked.
3452 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
3453 return Ok(Val::new(
3454 format!("{}({})", ident(&name), codes.join(", ")),
3455 Some((*ret).clone()),
3456 ));
3457 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago3458 // `Adler32::new()` / `Adler32::default()`: a method called through
3459 // its type rather than through a receiver.
3460 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3461 // `Self::new()` inside an `impl` names the type being implemented.
3462 let q = if q == "Self" {
3463 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3464 } else {
3465 q
3466 };
3467 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
3468 let ret = sig.ret.clone();
3469 let nim = self
3470 .statics
3471 .get(&(q.clone(), name.clone()))
3472 .cloned()
3473 .unwrap_or_else(|| ident(&name));
3474 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
3475 }
3476 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3477 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 11h ago3478 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 12h ago3479 return Err(format!(
3480 "call to unknown function `{name}`; only functions defined in \
3481 this file and the supported standard-library subset can be lowered"
3482 ));
3483 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3484 let nim = match &target {
3485 Some((m, n)) => self.fn_name(m, n),
3486 None => ident(&name),
3487 };
3488 Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3489 }
3490
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3491 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 12h ago3492 let name = m.method.to_string();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago3493 // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield.
3494 if name == "remainder" && m.args.is_empty() {
3495 if let Expr::Path(p) = &*m.receiver {
3496 if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) {
3497 if let Iter::Chunks { code, base, len, k, elem, .. } = &*it {
3498 let kept = format!("(({} div int({})) * int({}))", len, k, k);
3499 let mut v = Val::new(
3500 String::new(),
3501 elem.clone().map(|e| Nim::OpenArray(Box::new(e))),
3502 );
3503 v.window = Some(Alias::Window {
3504 code: code.clone(),
3505 off: format!("({} + {})", base, kept),
3506 len: format!("({} - {})", len, kept),
3507 elem: elem.clone(),
3508 });
3509 return Ok(v);
3510 }
3511 return Err(
3512 "`.remainder()` is only defined for a `chunks_exact` iterator".into(),
3513 );
3514 }
3515 }
3516 return Err("`.remainder()` needs an iterator bound by `let`".into());
3517 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3518 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
3519 match name.as_str() {
3520 "len" => {
3521 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
3522 }
3523 "is_empty" => {
3524 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
3525 }
3526 other => {
3527 return Err(format!(
3528 "`.{other}()` on a slice window from `chunks_exact`/\
3529 `windows` is not implemented; only indexing and \
3530 `len()` are"
3531 ))
3532 }
3533 }
3534 }
3535 let recv = self.expr(&m.receiver)?;
3536 let rt0 = recv.ty.clone();
3537
3538// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
3539 // way to put a view in an object, so instead of materialising an
3540 // Option the view and its validity condition travel together until
3541 // an `ok_or`/`?`/`unwrap` resolves them.
3542 if matches!(name.as_str(), "get" | "get_mut")
3543 && matches!(m.args.first(), Some(Expr::Range(_)))
3544 {
3545 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 11h ago3546 let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3547 let lo = match &r.start {
3548 Some(e) => format!("int({})", self.expr(e)?.code),
3549 None => "0".into(),
3550 };
3551 let len = match (&r.end, r.limits) {
3552 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
3553 format!("(int({}) - {})", self.expr(e)?.code, lo)
3554 }
3555 (Some(e), syn::RangeLimits::Closed(_)) => {
3556 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
3557 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3558 (None, _) => format!("({} - {})", blen, lo),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3559 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3560 // Hoisted, so the bounds are computed once -- as Rust computes
3561 // them once -- and cannot be re-evaluated later in a scope where
3562 // the names they mention have been shadowed by a loop pattern.
3563 let off_t = self.fresh("Off");
3564 let len_t = self.fresh("Len");
3565 self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
3566 self.line(&format!("let {}: int = {}", len_t, len));
3567 let elem = belem
3568 .or_else(|| elem_of(&rt0))
3569 .ok_or("cannot infer the element type of this slice")?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3570 let mut v = Val::new(
3571 String::new(),
3572 Some(Nim::Named(
3573 "Option".into(),
3574 vec![Nim::OpenArray(Box::new(elem.clone()))],
3575 )),
3576 );
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3577 v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3578 v.window = Some(Alias::Window {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3579 code,
3580 off: off_t,
3581 len: len_t,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3582 elem: Some(elem),
3583 });
3584 return Ok(v);
3585 }
3586
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3587 // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
3588 // parameter type comes from the receiver, so they are handled before
3589 // the arguments are lowered. The closure is expanded inline, with its
3590 // parameter aliased to the payload: that keeps the whole thing an
3591 // expression and avoids handing a view to a generic proc.
3592 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
3593 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
3594 (recv.ty.clone(), &m.args[0])
3595 {
3596 if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
3597 {
3598 return self.map_closure(&name, &recv, &kind, &targs, c);
3599 }
3600 }
3601 }
3602
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3603 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
3604 // own type; `v.push(e)` takes the element type.
3605 let arg_want = match (name.as_str(), &recv.ty) {
3606 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
3607 (_, t) => t.clone(),
3608 };
3609 let mut args = Vec::new();
3610 for a in &m.args {
3611 args.push(self.expr_at(a, arg_want.as_ref())?);
3612 }
3613 let a0 = args.first().map(|a| a.code.clone());
3614 let rt = recv.ty.clone();
3615
3616 let (code, ty) = match name.as_str() {
3617 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
3618 // explicit so that a `usize` binding type-checks on the Nim side.
3619 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
3620 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
3621 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
3622 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
3623 | "into_iter" => (recv.code.clone(), rt.clone()),
3624 "unwrap" | "expect" => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3625 // Expanded inline rather than called as a generic proc: when
3626 // the payload is a view, Nim can only borrow from a path
3627 // expression, which a proc body containing the panic is not.
3628 let (kind, inner) = match &rt {
3629 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
3630 ("Option", a[0].clone())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3631 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3632 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
3633 ("Result", a[0].clone())
3634 }
3635 _ => {
3636 return Err(format!(
3637 "`.{name}()` needs a known `Option`/`Result` receiver type"
3638 ))
3639 }
3640 };
3641 if self.in_loop_cond {
3642 return Err(format!(
3643 "`.{name}()` in a loop condition is not implemented yet: the \
3644 check it expands to would run once, before the loop"
3645 ));
3646 }
3647 let tmp = self.fresh("Unwrap");
3648 let rty = rt.clone().unwrap();
3649 self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code));
3650 let (test, msg) = if kind == "Option" {
3651 (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value")
3652 } else {
3653 (format!("{}.ok", tmp), "called `Result::unwrap()` on an `Err` value")
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3654 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3655 let msg = if name == "expect" {
3656 args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg))
3657 } else {
3658 fmt::nim_str(msg)
3659 };
3660 self.line(&format!("if not {}:", test));
3661 self.line(&format!(" rsPanic({})", msg));
3662 // If the payload is a view, hand back an alias rather than a
3663 // value: Nim will not let a `let` borrow out of a local, and a
3664 // view is a reference anyway, so there is nothing to bind.
3665 // `{tmp}.val` is a plain field access, so substituting it at
3666 // each use re-evaluates nothing.
3667 if matches!(inner, Nim::OpenArray(_)) {
3668 let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone()));
3669 v.window = Some(Alias::Value {
3670 code: format!("{}.val", tmp),
3671 ty: Some(inner),
3672 });
3673 return Ok(v);
3674 }
3675 (format!("{}.val", tmp), Some(inner))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3676 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3677 "ok_or" if recv.guard.is_some() => {
3678 let e = args.first().ok_or("`ok_or` takes one argument")?;
3679 let ety = e.ty.clone();
3680 let mut v = recv.clone();
3681 v.guard_err = Some(e.code.clone());
3682 v.ty = match (&recv.ty, ety) {
3683 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
3684 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
3685 }
3686 _ => None,
3687 };
3688 return Ok(v);
3689 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3690 "ok_or" => {
3691 let inner = match &rt {
3692 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
3693 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
3694 };
3695 let e = args.first().ok_or("`ok_or` takes one argument")?;
3696 let ety = e
3697 .ty
3698 .clone()
3699 .ok_or("`ok_or` needs a known error type for its argument")?;
3700 (
3701 format!(
3702 "rsOkOr[{}, {}]({}, {})",
3703 inner.render(),
3704 ety.render(),
3705 recv.code,
3706 e.code
3707 ),
3708 Some(Nim::Named("Result".into(), vec![inner, ety])),
3709 )
3710 }
3711 "unwrap_or" => {
3712 let inner = match &rt {
3713 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
3714 Some(a[0].clone())
3715 }
3716 _ => None,
3717 };
3718 (
3719 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
3720 inner,
3721 )
3722 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3723 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
3724 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
3725 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
3726 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
3727
3728 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
3729 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
3730 // Nim raises OverflowDefect, so the operation is routed through
3731 // the unsigned view of the same width, which is what Rust's
3732 // wrapping_* is defined to compute.
3733 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
3734 let op = match name.as_str() {
3735 "wrapping_add" => "+",
3736 "wrapping_sub" => "-",
3737 _ => "*",
3738 };
3739 let t = rt.clone().ok_or_else(|| {
3740 format!("`{name}` needs a known receiver type to pick the wrapping width")
3741 })?;
3742 if !t.is_integer() {
3743 return Err(format!("`{name}` on a non-integer type"));
3744 }
3745 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
3746 if t.is_unsigned() {
3747 (format!("({} {} {})", recv.code, op, arg), Some(t))
3748 } else {
3749 let u = unsigned_peer(&t)?;
3750 (
3751 format!(
3752 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
3753 t.render(), u, recv.code, op, u, arg
3754 ),
3755 Some(t),
3756 )
3757 }
3758 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3759 // Inside a formatting impl, a write through the `Formatter` *is*
3760 // 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 10h ago3761 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => {
3762 let a = args.first().ok_or("`write_str` takes one argument")?;
3763 // A `&str` argument is a character view, not a Nim string.
3764 let text = match &a.ty {
3765 Some(Nim::Prim(p)) if p == "string" => a.code.clone(),
3766 _ => format!("rsDisplay({})", a.code),
3767 };
3768 (format!("result.add({})", text), Some(Nim::Unit))
3769 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3770 "abs" => (format!("abs({})", recv.code), rt.clone()),
3771 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
3772 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
3773 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
3774 "as_bytes" | "into_bytes" => (
3775 format!("rsBytes({})", recv.code),
3776 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
3777 ),
3778
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3779 "into" => {
3780 // `.into()` resolves through the `impl From` declarations, and
3781 // needs the target type to pick one.
3782 let from = rt
3783 .clone()
3784 .ok_or("`.into()` needs a known receiver type")?;
3785 let to = expect
3786 .ok_or("`.into()` needs a known target type; annotate the binding")?;
3787 let key = (type_name(&from), type_name(to));
3788 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
3789 format!(
3790 "no `impl From<{}> for {}` in this file, so `.into()` has \
3791 no conversion to call",
3792 key.0, key.1
3793 )
3794 })?;
3795 (format!("{}({})", f, recv.code), Some(to.clone()))
3796 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3797 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3798 // A method defined in this file via `impl`, found by the
3799 // receiver's type rather than by name alone.
3800 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 10h ago3801 let sig = key
3802 .as_ref()
3803 .and_then(|k| self.methods.get(k))
3804 .map(|s| s.ret.clone());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3805 if let Some(ret) = sig {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago3806 // Use the name the proc was actually emitted under: an
3807 // inherent method is qualified by its module, a trait
3808 // method by its trait.
3809 let nim = key
3810 .and_then(|k| self.statics.get(&k).cloned())
3811 .unwrap_or_else(|| ident(&name));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3812 let mut all = vec![recv.code.clone()];
3813 all.extend(args.iter().map(|a| a.code.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago3814 (format!("{}({})", nim, all.join(", ")), Some(ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3815 } else {
3816 return Err(format!(
3817 "unsupported method `.{name}()`; it is neither defined in \
3818 this file nor part of the standard-library subset that \
3819 has a verified Nim equivalent"
3820 ));
3821 }
3822 }
3823 };
3824 Ok(Val::new(code, ty))
3825 }
3826
3827 // -------------------------------------------------------------- macros
3828
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3829 /// The element type of a `vec![..]`, from its first element.
3830 fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
3831 let body = mac.tokens.to_string();
3832 if body.trim().is_empty() {
3833 return Ok(None);
3834 }
3835 let first: Option<Expr> = if body.contains(';') {
3836 // The whole body must be consumed or the parse fails, so the
3837 // length is parsed too even though only the element is wanted.
3838 mac.parse_body_with(|input: syn::parse::ParseStream| {
3839 let v: Expr = input.parse()?;
3840 input.parse::<syn::Token![;]>()?;
3841 let _len: Expr = input.parse()?;
3842 Ok(v)
3843 })
3844 .ok()
3845 } else {
3846 mac.parse_body_with(
3847 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3848 )
3849 .ok()
3850 .and_then(|p| p.into_iter().next())
3851 };
3852 match first {
3853 Some(e) => Ok(self.expr(&e)?.ty),
3854 None => Ok(None),
3855 }
3856 }
3857
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3858 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
3859 let name = path_name(&mac.path);
3860 match name.as_str() {
3861 "println" | "print" | "eprintln" | "eprint" => {
3862 let s = self.format_args(mac)?;
3863 let nl = name.ends_with("ln");
3864 Ok(match (name.starts_with('e'), nl) {
3865 (false, true) => format!("echo {s}"),
3866 (false, false) => format!("stdout.write({s})"),
3867 (true, true) => format!("stderr.writeLine({s})"),
3868 (true, false) => format!("stderr.write({s})"),
3869 })
3870 }
3871 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3872 "write" | "writeln" => {
3873 // `write!(f, "..", ..)` inside a formatting impl: the first
3874 // argument is the sink, the rest is an ordinary format call.
3875 let args: Vec<Expr> = mac
3876 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
3877 .map_err(|e| format!("write!: {e}"))?
3878 .into_iter()
3879 .collect();
3880 let sink = args.first().ok_or("`write!` needs a sink")?;
3881 if !self.is_fmt_param(sink) {
3882 return Err("`write!` to anything but the `Formatter` of the \
3883 enclosing formatting impl is not implemented"
3884 .into());
3885 }
3886 let s = self.format_pieces(&args[1..])?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3887 let s = if name == "writeln" {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3888 format!("({} & \"\\n\")", s)
3889 } else {
3890 s
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3891 };
3892 Ok(format!("result.add({})", s))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3893 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3894 "panic" => {
3895 let s = self.format_args(mac)?;
3896 Ok(format!("rsPanic({s})"))
3897 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3898 // `debug_assert*` fires in debug builds, which is the profile
3899 // this project models, so it lowers the same as `assert*`.
3900 "assert" | "debug_assert" => {
3901 let args: Vec<Expr> = mac
3902 .parse_body_with(
3903 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3904 )
3905 .map_err(|e| format!("{name}!: {e}"))?
3906 .into_iter()
3907 .collect();
3908 let cond = args.first().ok_or("`assert!` needs a condition")?;
3909 let v = self.expr(cond)?;
3910 let msg = if args.len() > 1 {
3911 self.format_pieces(&args[1..])?
3912 } else {
3913 fmt::nim_str("assertion failed")
3914 };
3915 Ok(format!("(if not ({}): rsPanic({}))", v.code, msg))
3916 }
3917 "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => {
3918 let args: Vec<Expr> = mac
3919 .parse_body_with(
3920 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3921 )
3922 .map_err(|e| format!("{name}!: {e}"))?
3923 .into_iter()
3924 .collect();
3925 if args.len() < 2 {
3926 return Err(format!("`{name}!` takes two operands"));
3927 }
3928 let a = self.expr(&args[0])?;
3929 let b = self.expr_at(&args[1], a.ty.as_ref())?;
3930 let ne = name.ends_with("_ne");
3931 let op = if ne { "!=" } else { "==" };
3932 // Rust's message shows both sides; reproducing it keeps a
3933 // failing assertion as informative as the original.
3934 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 12h ago3935 Ok(format!(
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago3936 "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))",
3937 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 12h ago3938 ))
3939 }
3940 "vec" => {
3941 let body = mac.tokens.to_string();
3942 if body.trim().is_empty() {
3943 return Ok("@[]".into());
3944 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3945 // `vec![elem; n]` is the repeat form, not a list. The macro
3946 // body has no brackets, so it is parsed directly.
3947 if body.contains(';') {
3948 let (v, n) = mac
3949 .parse_body_with(|input: syn::parse::ParseStream| {
3950 let v: Expr = input.parse()?;
3951 input.parse::<syn::Token![;]>()?;
3952 let n: Expr = input.parse()?;
3953 Ok((v, n))
3954 })
3955 .map_err(|e| format!("vec![elem; n]: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3956 let want = self.vec_expect.clone();
3957 let v = self.expr_at(&v, want.as_ref())?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago3958 let n = self.expr(&n)?;
3959 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
3960 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3961 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
3962 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
3963 .map_err(|e| format!("vec!: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3964 let want = self.vec_expect.clone();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3965 let mut parts = Vec::new();
3966 for e in &elems {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago3967 parts.push(self.expr_at(e, want.as_ref())?.code);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3968 }
3969 Ok(format!("@[{}]", parts.join(", ")))
3970 }
3971 other => Err(format!(
3972 "unsupported macro `{other}!`; a macro whose expansion is not \
3973 known cannot be lowered faithfully"
3974 )),
3975 }
3976 }
3977
3978 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
3979 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 11h ago3980 let args: Vec<Expr> = mac
3981 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
3982 .map_err(|e| format!("format arguments: {e}"))?
3983 .into_iter()
3984 .collect();
3985 self.format_pieces(&args)
3986 }
3987
3988 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
3989 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
3990 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 12h ago3991 if args.is_empty() {
3992 return Ok("\"\"".into());
3993 }
3994 return Err("the first argument must be a literal format string".into());
3995 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago3996 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago3997
3998 let pieces = fmt::parse(&s.value())?;
3999 let mut parts: Vec<String> = Vec::new();
4000 let mut next = 0usize;
4001 let mut used = vec![false; rest.len()];
4002 for p in &pieces {
4003 match p {
4004 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
4005 fmt::Piece::Arg { r#ref, spec } => {
4006 let v = match r#ref {
4007 fmt::Ref::Next => {
4008 let e = rest.get(next).ok_or("too few arguments for format string")?;
4009 used[next] = true;
4010 next += 1;
4011 self.expr(e)?
4012 }
4013 fmt::Ref::Index(i) => {
4014 let e = rest.get(*i).ok_or("format index out of range")?;
4015 used[*i] = true;
4016 self.expr(e)?
4017 }
4018 fmt::Ref::Named(n) => {
4019 let t = self.lookup(n).ok_or_else(|| {
4020 format!("`{{{n}}}` captures `{n}`, which is not in scope")
4021 })?;
4022 Val::new(ident(n), Some(t))
4023 }
4024 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 10h ago4025 let integer = v.ty.as_ref().is_some_and(|t| t.is_integer());
4026 if spec.radix.is_some() && !integer && v.ty.is_none() {
4027 return Err(
4028 "a radix format (`{:x}`, `{:b}`, ...) needs a known \
4029 argument type: on an integer it formats the bit \
4030 pattern, on anything else it calls that type's own \
4031 impl"
4032 .into(),
4033 );
4034 }
4035 parts.push(fmt::render_arg(&v.code, spec, integer));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago4036 }
4037 }
4038 }
4039 // Rust rejects an argument that no `{}` consumes; so do we, rather
4040 // than dropping it from the output.
4041 if let Some(i) = used.iter().position(|u| !u) {
4042 return Err(format!(
4043 "argument {} is never used by the format string",
4044 i + 1
4045 ));
4046 }
4047 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
4048 }
4049}
4050
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago4051/// Whether a pattern introduces a binding.
4052fn binds(p: &Pat) -> bool {
4053 match p {
4054 Pat::Ident(_) => true,
4055 Pat::Guard(g) => binds(&g.pat),
4056 Pat::Paren(x) => binds(&x.pat),
4057 Pat::Reference(r) => binds(&r.pat),
4058 Pat::Or(o) => o.cases.iter().any(binds),
4059 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
4060 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
4061 _ => false,
4062 }
4063}
4064
4065/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
4066fn destructures(p: &Pat) -> bool {
4067 matches!(
4068 p,
4069 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
4070 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
4071 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
4072 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
4073}
4074
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago4075/// Whether an expression has a direct Nim expression form.
4076///
4077/// Nim's `if` is an expression only when every arm is a single expression, and
4078/// its `case` is never one here. Anything else has to be lowered as statements
4079/// that assign into a target.
4080fn expressible(e: &Expr) -> bool {
4081 match e {
4082 Expr::If(i) => {
4083 let Some(then) = single_expr(&i.then_branch) else { return false };
4084 if !expressible(then) {
4085 return false;
4086 }
4087 match &i.else_branch {
4088 None => false,
4089 Some((_, els)) => match &**els {
4090 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
4091 other => expressible(other),
4092 },
4093 }
4094 }
4095 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
4096 _ => true,
4097 }
4098}
4099
4100/// The single expression a block consists of, if that is all it is. An `if`
4101/// can only be lowered as a Nim `if`-expression when both arms are this shape.
4102fn single_expr(b: &syn::Block) -> Option<&Expr> {
4103 match (b.stmts.len(), b.stmts.first()) {
4104 (1, Some(Stmt::Expr(e, None))) => Some(e),
4105 _ => None,
4106 }
4107}
4108
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago4109/// Substitute `params[i] -> args[i]` through a type. Enough of the type
4110/// grammar is covered to expand the aliases we accept; anything else is left
4111/// alone and will be reported by `ty::map` if it is unsupported.
4112fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
4113 use syn::Type;
4114 match t {
4115 Type::Path(p) => {
4116 if p.qself.is_none() && p.path.segments.len() == 1 {
4117 let seg = &p.path.segments[0];
4118 if seg.arguments.is_empty() {
4119 let name = seg.ident.to_string();
4120 if let Some(i) = params.iter().position(|x| *x == name) {
4121 return args[i].clone();
4122 }
4123 }
4124 }
4125 let mut p = p.clone();
4126 for seg in &mut p.path.segments {
4127 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
4128 for g in &mut a.args {
4129 if let syn::GenericArgument::Type(t) = g {
4130 *t = substitute(t, params, args);
4131 }
4132 }
4133 }
4134 }
4135 Type::Path(p)
4136 }
4137 Type::Reference(r) => {
4138 let mut r = r.clone();
4139 r.elem = Box::new(substitute(&r.elem, params, args));
4140 Type::Reference(r)
4141 }
4142 Type::Slice(sl) => {
4143 let mut sl = sl.clone();
4144 sl.elem = Box::new(substitute(&sl.elem, params, args));
4145 Type::Slice(sl)
4146 }
4147 Type::Array(a) => {
4148 let mut a = a.clone();
4149 a.elem = Box::new(substitute(&a.elem, params, args));
4150 Type::Array(a)
4151 }
4152 Type::Tuple(tp) => {
4153 let mut tp = tp.clone();
4154 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
4155 Type::Tuple(tp)
4156 }
4157 Type::Paren(p) => substitute(&p.elem, params, args),
4158 Type::Group(g) => substitute(&g.elem, params, args),
4159 other => other.clone(),
4160 }
4161}
4162
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago4163// --------------------------------------------------------------- utilities
4164
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago4165/// Whether a return type is a borrow of one of the arguments, which Nim
4166/// models with a view rather than with an owned copy.
4167fn returns_borrow(t: &syn::Type) -> bool {
4168 match t {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago4169 syn::Type::Reference(r) => match &*r.elem {
4170 syn::Type::Slice(_) => true,
4171 // `&str` is a borrow of someone else's bytes too, and returning it
4172 // means returning a view, not an owned string.
4173 syn::Type::Path(p) => p.path.is_ident("str"),
4174 _ => false,
4175 },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago4176 syn::Type::Paren(p) => returns_borrow(&p.elem),
4177 syn::Type::Group(g) => returns_borrow(&g.elem),
4178 _ => false,
4179 }
4180}
4181
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 11h ago4182/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
4183/// to the crate root, which is where a flattened module's items live unless
4184/// they came from one of the extra input files.
4185fn module_of(prefix: &[String]) -> String {
4186 match prefix.last() {
4187 Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
4188 _ => String::new(),
4189 }
4190}
4191
4192/// The first type argument of an `Option[T]` / `Result[T, E]`.
4193fn elem_arg(t: &Nim) -> Nim {
4194 match t {
4195 Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
4196 other => other.clone(),
4197 }
4198}
4199
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago4200/// The element type of a sequence-like Nim type.
4201fn elem_of(t: &Option<Nim>) -> Option<Nim> {
4202 match t {
4203 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
4204 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
4205 _ => None,
4206 }
4207}
4208
4209/// The short name a Nim type is known by, for keying method tables.
4210fn type_name(t: &Nim) -> String {
4211 match t {
4212 Nim::Named(n, _) => n.clone(),
4213 Nim::Prim(p) => p.clone(),
4214 other => other.render(),
4215 }
4216}
4217
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago4218/// `(trait, operator)` for every operator trait we dispatch.
4219const OPERATOR_TRAITS: &[(&str, &str)] = &[
4220 ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"),
4221 ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"),
4222 ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="),
4223 ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="),
4224 ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="),
4225 ("Neg", "neg"), ("Not", "not"),
4226];
4227
4228/// `(operator, trait method name)`.
4229const OP_METHOD: &[(&str, &str)] = &[
4230 ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"),
4231 ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"),
4232 ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"),
4233 ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"),
4234 ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"),
4235 (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"),
4236];
4237
4238fn op_method(op: &str) -> &'static str {
4239 OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("")
4240}
4241
4242/// The operator symbol a compound assignment applies.
4243fn compound_symbol(op: &BinOp) -> &'static str {
4244 match op {
4245 BinOp::AddAssign(_) => "+=",
4246 BinOp::SubAssign(_) => "-=",
4247 BinOp::MulAssign(_) => "*=",
4248 BinOp::DivAssign(_) => "/=",
4249 BinOp::RemAssign(_) => "%=",
4250 BinOp::BitAndAssign(_) => "&=",
4251 BinOp::BitOrAssign(_) => "|=",
4252 BinOp::BitXorAssign(_) => "^=",
4253 BinOp::ShlAssign(_) => "<<=",
4254 BinOp::ShrAssign(_) => ">>=",
4255 _ => "",
4256 }
4257}
4258
4259fn binary_symbol(op: &BinOp) -> &'static str {
4260 match op {
4261 BinOp::Add(_) => "+",
4262 BinOp::Sub(_) => "-",
4263 BinOp::Mul(_) => "*",
4264 BinOp::Div(_) => "/",
4265 BinOp::Rem(_) => "%",
4266 BinOp::BitAnd(_) => "&",
4267 BinOp::BitOr(_) => "|",
4268 BinOp::BitXor(_) => "^",
4269 BinOp::Shl(_) => "<<",
4270 BinOp::Shr(_) => ">>",
4271 _ => "",
4272 }
4273}
4274
4275/// The operator a trait overloads, if it is one of the operator traits.
4276fn operator_trait(t: &str) -> Option<&'static str> {
4277 Some(match t {
4278 "Add" => "+",
4279 "Sub" => "-",
4280 "Mul" => "*",
4281 "Div" => "/",
4282 "Rem" => "%",
4283 "BitAnd" => "&",
4284 "BitOr" => "|",
4285 "BitXor" => "^",
4286 "Shl" => "<<",
4287 "Shr" => ">>",
4288 "AddAssign" => "+=",
4289 "SubAssign" => "-=",
4290 "MulAssign" => "*=",
4291 "DivAssign" => "/=",
4292 "RemAssign" => "%=",
4293 "BitAndAssign" => "&=",
4294 "BitOrAssign" => "|=",
4295 "BitXorAssign" => "^=",
4296 "ShlAssign" => "<<=",
4297 "ShrAssign" => ">>=",
4298 "Neg" => "neg",
4299 "Not" => "not",
4300 _ => return None,
4301 })
4302}
4303
4304/// The Nim proc name for a trait method, qualified by trait and type so that
4305/// two traits declaring the same method name cannot collide.
4306fn trait_method_name(ty: &str, tr: &str, m: &str) -> String {
4307 format!("rs{}_{}_{}", tr, ty, m)
4308}
4309
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 11h ago4310fn is_fmt_trait(t: &str) -> bool {
4311 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
4312}
4313
4314/// The prelude proc a formatting trait's output is produced by.
4315fn fmt_proc(t: &str) -> &'static str {
4316 match t {
4317 "Display" => "rsDisplay",
4318 "Debug" => "rsDebug",
4319 "LowerHex" => "rsLowerHex",
4320 "UpperHex" => "rsUpperHex",
4321 "Binary" => "rsBinary",
4322 _ => "rsOctal",
4323 }
4324}
4325
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 10h ago4326/// Whether an expression is an iterator-producing chain rather than a value.
4327fn is_iterator_expr(e: &Expr) -> bool {
4328 match e {
4329 Expr::MethodCall(m) => matches!(
4330 m.method.to_string().as_str(),
4331 "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact"
4332 | "chunks_exact_mut" | "windows"
4333 ),
4334 Expr::Paren(p) => is_iterator_expr(&p.expr),
4335 _ => false,
4336 }
4337}
4338
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 10h ago4339/// Whether an expression denotes a place -- a variable, a field, or an index
4340/// or slice of one -- and so may be re-evaluated with no side effect.
4341fn is_pure_place(e: &Expr) -> bool {
4342 match e {
4343 Expr::Path(_) => true,
4344 Expr::Field(f) => is_pure_place(&f.base),
4345 Expr::Index(i) => {
4346 is_pure_place(&i.expr)
4347 && match &*i.index {
4348 Expr::Range(r) => {
4349 r.start.as_deref().map_or(true, is_pure_place)
4350 && r.end.as_deref().map_or(true, is_pure_place)
4351 }
4352 other => is_pure_place(other),
4353 }
4354 }
4355 Expr::Lit(_) => true,
4356 Expr::Reference(r) => is_pure_place(&r.expr),
4357 Expr::Paren(p) => is_pure_place(&p.expr),
4358 Expr::Group(g) => is_pure_place(&g.expr),
4359 // Arithmetic on places is still side-effect free, so a bound like
4360 // `..want - 1` does not stop the binding being an alias.
4361 Expr::Binary(b) if !is_compound(&b.op) => {
4362 is_pure_place(&b.left) && is_pure_place(&b.right)
4363 }
4364 Expr::Unary(u) => is_pure_place(&u.expr),
4365 Expr::Cast(c) => is_pure_place(&c.expr),
4366 _ => false,
4367 }
4368}
4369
4370/// Whether an expression is a `&mut` borrow, directly or through parens.
4371fn is_mut_borrow(e: &Expr) -> bool {
4372 match e {
4373 Expr::Reference(r) => r.mutability.is_some(),
4374 Expr::Paren(p) => is_mut_borrow(&p.expr),
4375 Expr::Group(g) => is_mut_borrow(&g.expr),
4376 _ => false,
4377 }
4378}
4379
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago4380fn takes_self(sig: &syn::Signature) -> bool {
4381 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
4382}
4383
4384fn path_name(p: &syn::Path) -> String {
4385 p.segments
4386 .last()
4387 .map(|s| s.ident.to_string())
4388 .unwrap_or_default()
4389}
4390
4391fn is_compound(op: &BinOp) -> bool {
4392 matches!(
4393 op,
4394 BinOp::AddAssign(_)
4395 | BinOp::SubAssign(_)
4396 | BinOp::MulAssign(_)
4397 | BinOp::DivAssign(_)
4398 | BinOp::RemAssign(_)
4399 | BinOp::BitAndAssign(_)
4400 | BinOp::BitOrAssign(_)
4401 | BinOp::BitXorAssign(_)
4402 | BinOp::ShlAssign(_)
4403 | BinOp::ShrAssign(_)
4404 )
4405}
4406
4407/// The Nim literal suffix for an integer type (`5'i32`).
4408fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
4409 let Nim::Prim(p) = t else {
4410 return Err("not a primitive integer".into());
4411 };
4412 Ok(match p.as_str() {
4413 "int8" => "i8",
4414 "int16" => "i16",
4415 "int32" => "i32",
4416 "int64" => "i64",
4417 "int" => "i",
4418 "uint8" => "u8",
4419 "uint16" => "u16",
4420 "uint32" => "u32",
4421 "uint64" => "u64",
4422 "uint" => "u",
4423 other => return Err(format!("no Nim literal suffix for `{other}`")),
4424 })
4425}
4426
4427/// The unsigned integer type of the same width, used to spell `wrapping_*`.
4428fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
4429 let Nim::Prim(p) = t else {
4430 return Err("not a primitive integer".into());
4431 };
4432 Ok(match p.as_str() {
4433 "int8" => "uint8",
4434 "int16" => "uint16",
4435 "int32" => "uint32",
4436 "int64" => "uint64",
4437 "int" => "uint",
4438 other => return Err(format!("`{other}` has no unsigned peer")),
4439 })
4440}
4441
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 11h ago4442fn quote_meta(m: &syn::Meta) -> String {
4443 match m {
4444 syn::Meta::Path(p) => path_name(p),
4445 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
4446 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
4447 }
4448}
4449
4450fn item_attrs(i: &Item) -> &[syn::Attribute] {
4451 match i {
4452 Item::Fn(f) => &f.attrs,
4453 Item::Struct(s) => &s.attrs,
4454 Item::Enum(e) => &e.attrs,
4455 Item::Impl(x) => &x.attrs,
4456 Item::Const(c) => &c.attrs,
4457 Item::Type(t) => &t.attrs,
4458 Item::Mod(m) => &m.attrs,
4459 Item::Use(u) => &u.attrs,
4460 Item::ExternCrate(e) => &e.attrs,
4461 Item::Static(s) => &s.attrs,
4462 _ => &[],
4463 }
4464}
4465
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 12h ago4466fn item_kind(i: &Item) -> &'static str {
4467 match i {
4468 Item::Trait(_) => "`trait`",
4469 Item::Static(_) => "`static`",
4470 Item::Macro(_) => "macro definition",
4471 Item::Union(_) => "`union`",
4472 Item::ForeignMod(_) => "`extern` block",
4473 _ => "item",
4474 }
4475}
4476
4477fn expr_kind(e: &Expr) -> &'static str {
4478 match e {
4479 Expr::Async(_) => "`async` block",
4480 Expr::Await(_) => "`.await`",
4481 Expr::Try(_) => "`?`",
4482 Expr::Range(_) => "range",
4483 Expr::Match(_) => "`match` (only statement position is implemented)",
4484 Expr::Let(_) => "`let` expression",
4485 Expr::Unsafe(_) => "`unsafe` block",
4486 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
4487 _ => "expression",
4488 }
4489}