nandi/rustnimpublic Fork 0
ff34e1b3229df6e21b0c5d77053bb9b7364db5cd
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 · 4885 lines · 204.2 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h 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 10h 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 11h 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 10h 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 9h 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 10h 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 9h 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 10h 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 9h 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 10h 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 11h ago103 }
104}
105
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h 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 8h 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 10h ago117}
118
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h 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 10h 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 11h 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 10h 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 11h ago144 }
145 fn untyped(code: impl Into<String>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago146 Val::new(code, None)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago147 }
148}
149
150struct Sig {
151 params: Vec<Nim>,
152 ret: Nim,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago153 /// Type parameters this signature is generic in, so a call site can bind
154 /// them from its argument types.
155 generics: Vec<String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago156}
157
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago158/// One variant of a Rust enum.
159#[derive(Clone)]
160struct Variant {
161 name: String,
162 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
163 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
164 /// Nim requires the branches of a variant object to have distinct fields.
165 fields: Vec<(String, Nim)>,
166}
167
168#[derive(Clone)]
169struct EnumDef {
170 name: String,
171 /// True when every variant is a unit variant, which Nim represents as a
172 /// plain `enum` rather than an object variant.
173 simple: bool,
174 variants: Vec<Variant>,
175}
176
177impl EnumDef {
178 fn kind_ident(&self, v: &str) -> String {
179 format!("k{}{}", self.name, v)
180 }
181 fn ctor_ident(&self, v: &str) -> String {
182 format!("{}{}", self.name, v)
183 }
184 fn get(&self, v: &str) -> Option<&Variant> {
185 self.variants.iter().find(|x| x.name == v)
186 }
187}
188
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago189pub struct Lowerer {
190 out: String,
191 indent: usize,
192 scopes: Vec<HashMap<String, Nim>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago193 /// Names introduced by a `for` pattern that stand for an lvalue or a
194 /// window into a container, rather than for a variable of their own.
195 alias_scopes: Vec<HashMap<String, Alias>>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago196 /// `(module, name) -> signature`. Rust keeps `lower::decode` and
197 /// `mixed::decode` apart by module; flattening into one Nim module would
198 /// merge them, so the module is part of the key and of the emitted name.
199 fns: HashMap<(String, String), Sig>,
200 /// Module being lowered: the file stem, or empty for the crate root.
201 cur_mod: String,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago202 /// The type of the `impl` block being lowered, which `Self` names.
203 self_ty: Option<Nim>,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago204 /// Type parameters of the enclosing `impl`, which its methods share.
205 impl_generics: Vec<String>,
206 /// Type parameters of the proc being lowered, impl's included.
207 fn_generics: Vec<String>,
208 /// Type parameters declared by each generic struct or enum.
209 type_generics: HashMap<String, Vec<String>>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago210 /// `use` brings a name into scope from another module. Flattening loses
211 /// the module structure, so the mapping is recorded and consulted when a
212 /// bare call is resolved.
213 use_map: HashMap<String, String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago214 /// struct name -> (field, type)
215 structs: HashMap<String, Vec<(String, Nim)>>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago216 enums: HashMap<String, EnumDef>,
217 /// variant name -> enums declaring it. A variant named by more than one
218 /// enum must be written qualified, or it is rejected as ambiguous.
219 variant_owner: HashMap<String, Vec<String>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago220 /// `(receiver type, method) -> signature`. Keyed by type because two
221 /// types may define the same method name, and Nim tells them apart by
222 /// overload resolution on the first parameter.
223 methods: HashMap<(String, String), Sig>,
224 /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
225 /// on a user type can be checked rather than assumed.
226 fmt_impls: HashMap<(String, String), ()>,
227 /// `(from, to)` conversions declared by `impl From<A> for B`.
228 from_impls: HashMap<(String, String), String>,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago229 /// Operator traits implemented for a type, so `a += b` on a user type can
230 /// be dispatched to the impl rather than to Nim's built-in operator.
231 op_impls: HashMap<(String, String), ()>,
232 /// `(type, method) -> nim name`, for calls written as `Type::method(..)`.
233 statics: HashMap<(String, String), String>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago234 /// Forward declarations, emitted between the type definitions and the
235 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
236 /// proc is declared up front rather than the input being reordered --
237 /// which would not work for mutual recursion anyway.
238 forwards: Vec<String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago239 /// Element type a `vec![..]` should build, from the binding's annotation.
240 vec_expect: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago241 /// While lowering a formatting impl: the `Formatter` parameter's name.
242 /// Writes through it produce the proc's string result.
243 fmt_param: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago244 /// `type X<T> = ...`, expanded before any type is mapped.
245 aliases: HashMap<String, (Vec<String>, syn::Type)>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago246 /// Module names supplied as separate input files. A `mod x;` naming one
247 /// of these is satisfied by that file having been passed in.
248 pub modules: Vec<String>,
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 8h ago249 /// How many items were actually translated. If this is zero the input
250 /// produced nothing but the prelude, and reporting success for that is
251 /// the precise failure this project exists to avoid -- see `findings/`.
252 emitted: usize,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago253 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
254 /// evaluated against these exactly as rustc would, so an item that is
255 /// dropped here is genuinely not part of the program being compiled.
256 pub features: Vec<String>,
257 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago258 /// Return type of the proc being lowered, so `return e` and a trailing
259 /// expression can type their literals the way Rust's inference would.
260 ret: Option<Nim>,
261 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
262 /// statement must assign their value to.
263 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago264 /// Set while lowering a `while` condition, which Nim re-evaluates each
265 /// iteration and so cannot have statements hoisted out of it.
266 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago267 tmp: usize,
268}
269
270impl Lowerer {
271 pub fn new() -> Self {
272 Lowerer {
273 out: String::new(),
274 indent: 0,
275 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago276 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago277 fns: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago278 cur_mod: String::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago279 self_ty: None,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago280 impl_generics: Vec::new(),
281 fn_generics: Vec::new(),
282 type_generics: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago283 use_map: HashMap::new(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago284 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago285 enums: HashMap::new(),
286 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago287 methods: HashMap::new(),
288 fmt_impls: HashMap::new(),
289 from_impls: HashMap::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago290 op_impls: HashMap::new(),
291 statics: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago292 fmt_param: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago293 vec_expect: None,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago294 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago295 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago296 modules: Vec::new(),
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 8h ago297 emitted: 0,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago298 features: Vec::new(),
299 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago300 ret: None,
301 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago302 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago303 tmp: 0,
304 }
305 }
306
307 // ------------------------------------------------------------ emission
308
309 fn line(&mut self, s: &str) {
310 for _ in 0..self.indent {
311 self.out.push_str(" ");
312 }
313 self.out.push_str(s);
314 self.out.push('\n');
315 }
316
317 fn blank(&mut self) {
318 self.out.push('\n');
319 }
320
321 fn fresh(&mut self, hint: &str) -> String {
322 self.tmp += 1;
323 format!("rsTmp{}{}", hint, self.tmp)
324 }
325
326 // --------------------------------------------------------------- scope
327
328 fn push_scope(&mut self) {
329 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago330 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago331 }
332 fn pop_scope(&mut self) {
333 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago334 self.alias_scopes.pop();
335 }
336 fn bind_alias(&mut self, name: &str, a: Alias) {
337 self.alias_scopes
338 .last_mut()
339 .unwrap()
340 .insert(name.to_string(), a);
341 }
342 fn lookup_alias(&self, name: &str) -> Option<Alias> {
343 self.alias_scopes
344 .iter()
345 .rev()
346 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago347 }
348 fn bind(&mut self, name: &str, t: Nim) {
349 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
350 }
351 fn lookup(&self, name: &str) -> Option<Nim> {
352 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
353 }
354
355 // ---------------------------------------------------------------- file
356
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago357 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 11h ago358 self.out.push_str(include_str!("prelude.nim"));
359 self.blank();
360
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago361 // Pass 0: type aliases. A signature in one file may use an alias
362 // declared in another, and inputs are given in whatever order suits
363 // 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 9h ago364 for (m, f) in files {
365 self.cur_mod = m.clone();
366 for item in &f.items {
367 self.collect_aliases(item)?;
368 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago369 }
370
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago371 // Pass 1: signatures and struct shapes, so that a call can be typed
372 // regardless of declaration order (Rust has no forward declarations).
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago373 for (m, f) in files {
374 self.cur_mod = m.clone();
375 for item in &f.items {
376 self.collect(item)?;
377 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago378 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago379 // Pass 2: type definitions, which every signature may mention.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago380 for (m, f) in files {
381 self.cur_mod = m.clone();
382 for item in &f.items {
383 self.item_types(item)?;
384 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago385 }
386
387 // Pass 3: forward declarations. Rust imposes no declaration order and
388 // Nim does, so everything is declared before any body is emitted;
389 // reordering the input would not handle mutual recursion anyway.
390 if !self.forwards.is_empty() {
391 for f in self.forwards.clone() {
392 self.line(&f);
393 }
394 self.blank();
395 }
396
397 // Pass 4: bodies.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago398 for (m, f) in files {
399 self.cur_mod = m.clone();
400 for item in &f.items {
401 self.item(item)?;
402 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago403 }
404
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 8h ago405 // An input that translates to nothing is a failure, however plausible
406 // the output file looks. The prelude alone is not a translation.
407 if self.emitted == 0 {
408 return Err(format!(
409 "nothing was translated: the input has no items this lowering \
410 emits{}. Writing a file containing only the prelude would \
411 report success for work that was not done",
412 if self.dropped_by_cfg > 0 {
413 format!(
414 " ({} item(s) were dropped by `#[cfg]`; enable them with \
415 `--cfg feature=<name>`)",
416 self.dropped_by_cfg
417 )
418 } else {
419 String::new()
420 }
421 ));
422 }
423
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago424 if self.fns.contains_key(&(String::new(), "main".to_string())) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago425 self.blank();
426 self.line("when isMainModule:");
427 self.indent += 1;
428 self.line("try:");
429 self.line(" main()");
430 // Rust's panic exits 101 with a message on stderr. Nim's Defects
431 // exit 1. Mapping them here is what keeps the differential runner's
432 // exit-status comparison meaningful for panicking programs.
433 self.line("except RustPanic as e:");
434 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
435 self.line(" quit(101)");
436 self.line("except Defect as e:");
437 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
438 self.line(" quit(101)");
439 self.indent -= 1;
440 }
441 Ok(std::mem::take(&mut self.out))
442 }
443
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago444 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
445 if !self.cfg_keeps(item_attrs(item))? {
446 return Ok(());
447 }
448 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago449 Item::Use(u) => self.collect_use(&u.tree, &[]),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago450 Item::Type(t) => {
451 let params: Vec<String> = t
452 .generics
453 .params
454 .iter()
455 .filter_map(|g| match g {
456 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
457 _ => None,
458 })
459 .collect();
460 self.aliases
461 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
462 }
463 Item::Mod(m) if m.content.is_some() => {
464 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
465 for i in &items {
466 self.collect_aliases(i)?;
467 }
468 }
469 _ => {}
470 }
471 Ok(())
472 }
473
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago474 /// Record what a `use` brings into scope, as `name -> module`.
475 fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
476 use syn::UseTree;
477 match t {
478 UseTree::Path(p) => {
479 let mut pre = prefix.to_vec();
480 pre.push(p.ident.to_string());
481 self.collect_use(&p.tree, &pre);
482 }
483 UseTree::Group(g) => {
484 for t in &g.items {
485 self.collect_use(t, prefix);
486 }
487 }
488 UseTree::Name(n) => {
489 let m = module_of(prefix);
490 self.use_map.insert(n.ident.to_string(), m);
491 }
492 UseTree::Rename(r) => {
493 let m = module_of(prefix);
494 self.use_map.insert(r.rename.to_string(), m);
495 }
496 // A glob brings in an unknown set of names; resolution falls back
497 // to the current module and the root, as it would without it.
498 UseTree::Glob(_) => {}
499 }
500 }
501
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago502 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago503 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
504 // silently would change what the program does; picking a feature set
505 // on the user's behalf would be a guess. So it is reported, except on
506 // items that carry no runtime meaning here anyway.
507 if !self.cfg_keeps(item_attrs(item))? {
508 self.dropped_by_cfg += 1;
509 return Ok(());
510 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago511 match item {
512 Item::Fn(f) => {
513 let (params, ret) = self.signature(&f.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago514 let gen_names = Self::generics_of(&f.sig.generics);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago515 let name = f.sig.ident.to_string();
516 let nim = self.fn_name(&self.cur_mod, &name);
517 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
518 self.fns
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago519 .insert((self.cur_mod.clone(), name), Sig { params, ret, generics: gen_names.clone() });
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago520 }
521 Item::Struct(s) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago522 if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
523 return Err(format!(
524 "`struct {}` has a const generic parameter, which Nim has \
525 no equivalent for",
526 s.ident
527 ));
528 }
529 let g = Self::generics_of(&s.generics);
530 // The parameters must be in scope while the field types are
531 // mapped, so that `T` resolves to itself rather than to an
532 // unknown named type.
533 self.type_generics.insert(s.ident.to_string(), g);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago534 let mut fields = Vec::new();
535 for (i, f) in s.fields.iter().enumerate() {
536 let name = match &f.ident {
537 Some(id) => id.to_string(),
538 None => format!("f{i}"), // tuple struct
539 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago540 // A field of `&[T]` / `&str` type is a borrow, and Nim's
541 // view types allow it as an object field, so it stays a
542 // view rather than being copied into a `seq`.
543 let t = self.map_ty(&f.ty)?;
544 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
545 fields.push((name, t));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago546 }
547 self.structs.insert(s.ident.to_string(), fields);
548 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago549 Item::Mod(m) if m.content.is_some() => {
550 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
551 for i in &items {
552 self.collect(i)?;
553 }
554 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago555 Item::Type(t) => {
556 let params: Vec<String> = t
557 .generics
558 .params
559 .iter()
560 .filter_map(|g| match g {
561 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
562 _ => None,
563 })
564 .collect();
565 self.aliases
566 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
567 }
568 Item::Enum(e) => {
569 let name = e.ident.to_string();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago570 if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
571 return Err(format!(
572 "`enum {name}` has a const generic parameter, which Nim \
573 has no equivalent for"
574 ));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago575 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago576 self.type_generics
577 .insert(name.clone(), Self::generics_of(&e.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago578 let mut variants = Vec::new();
579 for v in &e.variants {
580 let vname = v.ident.to_string();
581 if v.discriminant.is_some() {
582 return Err(format!(
583 "`{name}::{vname}` has an explicit discriminant; Rust's \
584 `as` on such an enum has a value this lowering does not \
585 yet preserve"
586 ));
587 }
588 let mut fields = Vec::new();
589 for (i, f) in v.fields.iter().enumerate() {
590 // Nim requires the branches of a variant object to have
591 // distinct field names, so each is prefixed.
592 let fname = match &f.ident {
593 Some(id) => format!("{vname}_{id}"),
594 None => format!("{vname}_f{i}"),
595 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago596 let t = self.map_ty(&f.ty)?;
597 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
598 fields.push((fname, t));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago599 }
600 variants.push(Variant { name: vname, fields });
601 }
602 let simple = variants.iter().all(|v| v.fields.is_empty());
603 for v in &variants {
604 self.variant_owner
605 .entry(v.name.clone())
606 .or_default()
607 .push(name.clone());
608 }
609 self.enums.insert(
610 name.clone(),
611 EnumDef { name, simple, variants },
612 );
613 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago614 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago615 let outer_g =
616 std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago617 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago618 let outer_self = self.self_ty.replace(self_ty.clone());
619 let r = self.collect_impl(im, &self_ty);
620 self.self_ty = outer_self;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago621 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago622 return r;
623 }
624 _ => {}
625 }
626 Ok(())
627 }
628
629 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
630 {
631 let self_ty = self_ty.clone();
632 let tyname = type_name(&self_ty);
633 if let Some((path, _)) = &im.trait_ {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago634 let tr = path_name(path);
635 if im.items.is_empty() {
636 // A marker trait with no items. We do not model trait
637 // resolution at all, so it generates nothing; any use
638 // that actually needed the trait (a `dyn`, a bound) is
639 // rejected where it appears.
640 return Ok(());
641 }
642 if is_fmt_trait(&tr) {
643 self.forwards.push(format!(
644 "proc {}*(self: {}): string",
645 fmt_proc(&tr),
646 self_ty.render()
647 ));
648 self.fmt_impls.insert((tyname, tr), ());
649 return Ok(());
650 }
651 if tr == "From" {
652 let syn::ImplItem::Fn(m) = &im.items[0] else {
653 return Err("`impl From` must contain `fn from`".into());
654 };
655 let (params, _) = self.signature(&m.sig)?;
656 let src = params
657 .first()
658 .ok_or("`fn from` takes one argument")?
659 .clone();
660 let name = format!("rsFrom{}{}", tyname, type_name(&src));
661 self.forwards.push(self.head_of(&name, &m.sig, None)?);
662 self.from_impls
663 .insert((type_name(&src), tyname), name);
664 return Ok(());
665 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago666 // Any other trait: its methods are emitted as procs on
667 // the type, named after the trait so two traits declaring
668 // the same method name do not collide. The *trait* is not
669 // modelled -- no dynamic dispatch, no bounds -- and a use
670 // that needs it is rejected where it appears.
671 if let Some(op) = operator_trait(&tr) {
672 self.op_impls.insert((tyname.clone(), op.to_string()), ());
673 }
674 for it in &im.items {
675 let syn::ImplItem::Fn(m) = it else {
676 return Err(format!("unsupported item in `impl {tr}`"));
677 };
678 let mname = m.sig.ident.to_string();
679 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago680 let mut gen_names = self.impl_generics.clone();
681 gen_names.extend(Self::generics_of(&m.sig.generics));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago682 let recv = if takes_self(&m.sig) {
683 params.insert(0, self_ty.clone());
684 Some(self_ty.clone())
685 } else {
686 None
687 };
688 let nim = trait_method_name(&tyname, &tr, &mname);
689 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
690 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago691 .insert((tyname.clone(), mname.clone()), Sig { params, ret, generics: gen_names.clone() });
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago692 self.statics.insert((tyname.clone(), mname), nim);
693 }
694 return Ok(());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago695 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago696 for it in &im.items {
697 if let syn::ImplItem::Fn(m) = it {
698 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago699 let mut gen_names = self.impl_generics.clone();
700 gen_names.extend(Self::generics_of(&m.sig.generics));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago701 if takes_self(&m.sig) {
702 params.insert(0, self_ty.clone());
703 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago704 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 8h ago705 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
706 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 10h ago707 self.forwards.push(head);
708 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago709 .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret, generics: gen_names.clone() });
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago710 self.statics
711 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago712 }
713 }
714 }
715 Ok(())
716 }
717
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago718 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
719 ///
720 /// This is evaluation, not approximation: rustc does the same thing, and
721 /// an item whose predicate is false is not part of the compiled program.
722 /// A predicate that cannot be evaluated is reported rather than assumed.
723 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
724 for a in attrs {
725 if a.path().is_ident("cfg") {
726 let pred: syn::Meta = a
727 .parse_args()
728 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
729 if !self.cfg_eval(&pred)? {
730 return Ok(false);
731 }
732 }
733 }
734 Ok(true)
735 }
736
737 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
738 match m {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago739 // Bare flags whose value is determined by the profile this project
740 // models: a normal (non-`--test`) debug build, not a docs build.
741 // Anything platform-specific stays rejected, since we would be
742 // picking a target on the user's behalf.
743 syn::Meta::Path(p) if p.is_ident("test") => Ok(false),
744 syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true),
745 syn::Meta::Path(p) if p.is_ident("docsrs") || p.is_ident("doc") => Ok(false),
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 8h ago746 syn::Meta::Path(p) if p.is_ident("doctest") || p.is_ident("miri") => Ok(false),
747 // Host facts. The generated Nim is compiled for this machine, so
748 // these are known rather than chosen. See DESIGN.md item 10: it
749 // does make the output host-shaped.
750 syn::Meta::Path(p) if p.is_ident("unix") => Ok(cfg!(unix)),
751 syn::Meta::Path(p) if p.is_ident("windows") => Ok(cfg!(windows)),
752 syn::Meta::NameValue(nv)
753 if nv.path.is_ident("target_os")
754 || nv.path.is_ident("target_arch")
755 || nv.path.is_ident("target_family")
756 || nv.path.is_ident("target_vendor") =>
757 {
758 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
759 return Err("this `cfg` key expects a string".into());
760 };
761 let key = nv.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
762 Ok(s.value()
763 == match key.as_str() {
764 "target_os" => std::env::consts::OS,
765 "target_arch" => std::env::consts::ARCH,
766 "target_family" => std::env::consts::FAMILY,
767 _ => "unknown",
768 })
769 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago770 // The generated Nim is compiled for the same machine, so the
771 // target's word size and endianness are known rather than
772 // guessed. This does mean the output is host-shaped: a crate that
773 // branches on pointer width has had that branch decided here.
774 syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => {
775 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
776 return Err("`target_pointer_width = ..` expects a string".into());
777 };
778 Ok(s.value() == (usize::BITS).to_string())
779 }
780 syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => {
781 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
782 return Err("`target_endian = ..` expects a string".into());
783 };
784 Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" })
785 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago786 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
787 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
788 return Err("`feature = ..` expects a string".into());
789 };
790 Ok(self.features.iter().any(|f| *f == s.value()))
791 }
792 syn::Meta::List(l) if l.path.is_ident("not") => {
793 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
794 Ok(!self.cfg_eval(&inner)?)
795 }
796 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
797 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
798 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
799 .map_err(|e| e.to_string())?;
800 let all = l.path.is_ident("all");
801 let mut acc = all;
802 for i in &items {
803 let v = self.cfg_eval(i)?;
804 acc = if all { acc && v } else { acc || v };
805 }
806 Ok(acc)
807 }
808 other => Err(format!(
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 8h ago809 "`#[cfg({})]` is not a predicate rustnim can evaluate. \
810 Features (`--cfg feature=..`), host facts (`unix`, `windows`, \
811 `target_os`, `target_arch`, `target_family`, \
812 `target_pointer_width`, `target_endian`), `doc`/`doctest`/\
813 `miri`, and `not`/`all`/`any` over those are. A custom or \
814 build-script `cfg` has no value we could know",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago815 quote_meta(other)
816 )),
817 }
818 }
819
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago820 /// 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 10h ago821 /// lowering goes through here rather than calling `ty::map` directly, so
822 /// an alias cannot be missed in one position and honoured in another.
823 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago824 let n = ty::map(&self.expand(t, 0)?)?;
825 Ok(self.subst_self(n))
826 }
827
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago828 /// Substitute a generic type's parameters with the arguments the use site
829 /// supplies: a field of `Holder<T>` read through a `Holder<i32>` is `i32`.
830 fn subst_type_args(&self, name: &str, used_as: &Nim, field: Nim) -> Nim {
831 let Some(params) = self.type_generics.get(name) else { return field };
832 if params.is_empty() {
833 return field;
834 }
835 let Nim::Named(n, args) = used_as else { return field };
836 if n != name || args.len() != params.len() {
837 return field;
838 }
839 let map: HashMap<String, Nim> =
840 params.iter().cloned().zip(args.iter().cloned()).collect();
841 Self::subst(&field, &map)
842 }
843
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago844 /// `Self` inside an `impl` block names the type being implemented.
845 fn subst_self(&self, t: Nim) -> Nim {
846 let Some(me) = &self.self_ty else { return t };
847 match t {
848 Nim::Named(n, _) if n == "Self" => me.clone(),
849 Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))),
850 Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))),
851 Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))),
852 Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))),
853 Nim::Named(n, a) => {
854 Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect())
855 }
856 other => other,
857 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago858 }
859
860 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
861 if depth > 16 {
862 return Err("type alias expansion did not terminate; is it cyclic?".into());
863 }
864 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
865 // Only an unqualified name can be one of this file's aliases.
866 // `fmt::Result` and `core::result::Result` are different types that
867 // merely end in the same segment.
868 if p.path.segments.len() != 1 {
869 return Ok(t.clone());
870 }
871 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
872 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
873 return Ok(t.clone());
874 };
875 let args: Vec<syn::Type> = match &seg.arguments {
876 syn::PathArguments::AngleBracketed(a) => a
877 .args
878 .iter()
879 .filter_map(|g| match g {
880 GenericArgument::Type(t) => Some(t.clone()),
881 _ => None,
882 })
883 .collect(),
884 _ => vec![],
885 };
886 if args.len() != params.len() {
887 // Flattening several files into one module can bring a crate's own
888 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
889 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
890 // module; here they are told apart by arity, and a use that fits
891 // neither is left for `ty::map` to report.
892 return Ok(t.clone());
893 }
894 self.expand(&substitute(target, params, &args), depth + 1)
895 }
896
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago897 /// The type parameters a generic item declares.
898 ///
899 /// Trait bounds and `where` clauses are dropped. Nim instantiates a
900 /// generic structurally: an operation the bound would have permitted
901 /// either exists for the instantiated type or is a compile error at the
902 /// instantiation site. So dropping a bound cannot make an accepted
903 /// program mean something different — it only makes rustnim accept some
904 /// programs rustc would have rejected, which does not matter when the
905 /// input is known-good Rust.
906 fn generics_of(g: &syn::Generics) -> Vec<String> {
907 g.params
908 .iter()
909 .filter_map(|p| match p {
910 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
911 _ => None,
912 })
913 .collect()
914 }
915
916 /// Bind a signature's type parameters by matching its declared parameter
917 /// types against the actual argument types, then substitute into `ret`.
918 ///
919 /// This is the small amount of inference a call site needs: Nim will
920 /// resolve the instantiation itself, but the *binding* still has to be
921 /// annotated with a concrete type, and `T` is not one.
922 fn instantiate(sig: &Sig, args: &[Option<Nim>]) -> Nim {
923 if sig.generics.is_empty() {
924 return sig.ret.clone();
925 }
926 let mut bound: HashMap<String, Nim> = HashMap::new();
927 for (decl, actual) in sig.params.iter().zip(args) {
928 if let Some(a) = actual {
929 Self::unify(decl, a, &sig.generics, &mut bound);
930 }
931 }
932 Self::subst(&sig.ret, &bound)
933 }
934
935 fn unify(decl: &Nim, actual: &Nim, params: &[String], out: &mut HashMap<String, Nim>) {
936 match (decl, actual) {
937 (Nim::Named(n, da), _) if params.iter().any(|p| p == n) && da.is_empty() => {
938 out.entry(n.clone()).or_insert_with(|| actual.clone());
939 }
940 (Nim::Named(_, da), Nim::Named(_, aa)) if da.len() == aa.len() => {
941 for (d, a) in da.iter().zip(aa) {
942 Self::unify(d, a, params, out);
943 }
944 }
945 (Nim::Seq(d), Nim::Seq(a))
946 | (Nim::OpenArray(d), Nim::OpenArray(a))
947 | (Nim::Seq(d), Nim::OpenArray(a))
948 | (Nim::OpenArray(d), Nim::Seq(a))
949 | (Nim::Var(d), Nim::Var(a))
950 | (Nim::Array(_, d), Nim::Array(_, a)) => Self::unify(d, a, params, out),
951 (Nim::Var(d), a) => Self::unify(d, a, params, out),
952 (d, Nim::Var(a)) => Self::unify(d, a, params, out),
953 (Nim::Tuple(d), Nim::Tuple(a)) if d.len() == a.len() => {
954 for (d, a) in d.iter().zip(a) {
955 Self::unify(d, a, params, out);
956 }
957 }
958 _ => {}
959 }
960 }
961
962 fn subst(t: &Nim, m: &HashMap<String, Nim>) -> Nim {
963 match t {
964 Nim::Named(n, a) if a.is_empty() => m.get(n).cloned().unwrap_or_else(|| t.clone()),
965 Nim::Named(n, a) => {
966 Nim::Named(n.clone(), a.iter().map(|x| Self::subst(x, m)).collect())
967 }
968 Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))),
969 Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))),
970 Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))),
971 Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))),
972 Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()),
973 other => other.clone(),
974 }
975 }
976
977 /// Whether a type mentions a type parameter that is in scope here. Such a
978 /// type cannot be used as a Nim annotation at an instantiation site: Nim
979 /// infers it, and writing `T` would name something that is not bound.
980 fn mentions_type_param(&self, t: &Nim) -> bool {
981 match t {
982 Nim::Named(n, a) => {
983 self.fn_generics.iter().any(|g| g == n)
984 || a.iter().any(|x| self.mentions_type_param(x))
985 }
986 Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) => {
987 self.mentions_type_param(e)
988 }
989 Nim::Tuple(ts) => ts.iter().any(|x| self.mentions_type_param(x)),
990 Nim::Proc(a, r) => {
991 a.iter().any(|x| self.mentions_type_param(x)) || self.mentions_type_param(r)
992 }
993 _ => false,
994 }
995 }
996
997 /// `[T, U]`, or empty.
998 fn gen_list(params: &[String]) -> String {
999 if params.is_empty() {
1000 String::new()
1001 } else {
1002 format!("[{}]", params.join(", "))
1003 }
1004 }
1005
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago1006 /// The Nim name for a function, qualified by its module.
1007 fn fn_name(&self, module: &str, name: &str) -> String {
1008 if module.is_empty() {
1009 ident(name)
1010 } else {
1011 format!("{}_{}", module, ident(name))
1012 }
1013 }
1014
1015 /// Resolve a call path to the module and name it refers to: an explicit
1016 /// `mixed::decode`, then the current module, then the crate root.
1017 fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
1018 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1019 let last = segs.last()?.clone();
1020 if segs.len() >= 2 {
1021 let q = &segs[segs.len() - 2];
1022 if self.fns.contains_key(&(q.clone(), last.clone())) {
1023 return Some((q.clone(), last));
1024 }
1025 }
1026 let imported = self.use_map.get(&last).cloned();
1027 for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
1028 .into_iter()
1029 .flatten()
1030 {
1031 if self.fns.contains_key(&(m.clone(), last.clone())) {
1032 return Some((m, last));
1033 }
1034 }
1035 None
1036 }
1037
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1038 /// The Nim `proc` head for a Rust signature, used both for the forward
1039 /// declaration and for the definition, so the two cannot drift apart.
1040 fn head_of(
1041 &self,
1042 name: &str,
1043 sig: &syn::Signature,
1044 recv: Option<&Nim>,
1045 ) -> Result<String, String> {
1046 let (ptys, ret) = self.signature(sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1047 // A method inside `impl<T> Foo<T>` is generic in the impl's
1048 // parameters as well as its own.
1049 let mut params = self.impl_generics.clone();
1050 for g in Self::generics_of(&sig.generics) {
1051 if !params.contains(&g) {
1052 params.push(g);
1053 }
1054 }
1055 let gens = Self::gen_list(&params);
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1056 let mut parts = Vec::new();
1057 if let Some(self_ty) = recv {
1058 let mutable = matches!(
1059 sig.inputs.first(),
1060 Some(FnArg::Receiver(r))
1061 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1062 );
1063 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1064 parts.push(format!("self: {}", t.render()));
1065 }
1066 let typed: Vec<&syn::PatType> = sig
1067 .inputs
1068 .iter()
1069 .filter_map(|a| match a {
1070 FnArg::Typed(t) => Some(t),
1071 _ => None,
1072 })
1073 .collect();
1074 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
1075 let pname = match &*p.pat {
1076 Pat::Ident(id) => id.ident.to_string(),
1077 Pat::Wild(_) => format!("unused{}", parts.len()),
1078 _ => return Err("only plain identifier parameters are supported".into()),
1079 };
1080 let _ = i;
1081 parts.push(format!("{}: {}", ident(&pname), t.render()));
1082 }
1083 Ok(if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1084 format!("proc {}*{}({})", ident(name), gens, parts.join(", "))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1085 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1086 format!(
1087 "proc {}*{}({}): {}",
1088 ident(name),
1089 gens,
1090 parts.join(", "),
1091 ret.render()
1092 )
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1093 })
1094 }
1095
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1096 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 9h ago1097 // `unsafe fn` marks a contract for callers; it does not change what
1098 // the body means, so it lowers like any other proc.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1099 if sig.asyncness.is_some() {
1100 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
1101 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1102 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1103 // they disappear. Type parameters become Nim generic parameters.
1104 // Const parameters have no Nim equivalent and are still rejected.
1105 if sig.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1106 return Err(format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1107 "`fn {}` has a const generic parameter, which Nim has no \
1108 equivalent for",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1109 sig.ident
1110 ));
1111 }
1112 let mut params = Vec::new();
1113 for a in &sig.inputs {
1114 if let FnArg::Typed(t) = a {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1115 params.push(self.map_ty(&t.ty)?);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1116 }
1117 }
1118 let ret = match &sig.output {
1119 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1120 // A returned `&[T]` is a borrow of the caller's buffer, so it
1121 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
1122 // a `seq`, which `owned()` would do to both.
1123 ReturnType::Type(_, t) => {
1124 let n = self.map_ty(t)?;
1125 if returns_borrow(t) { n } else { n.owned() }
1126 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1127 };
1128 Ok((params, ret))
1129 }
1130
1131 // --------------------------------------------------------------- items
1132
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1133 /// Emit the type definitions only: they must precede every signature.
1134 fn item_types(&mut self, item: &Item) -> Result<(), String> {
1135 if !self.cfg_keeps(item_attrs(item))? {
1136 return Ok(());
1137 }
1138 match item {
1139 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
1140 Item::Mod(m) if m.content.is_some() => {
1141 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1142 for i in &items {
1143 self.item_types(i)?;
1144 }
1145 Ok(())
1146 }
1147 _ => Ok(()),
1148 }
1149 }
1150
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1151 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1152 if !self.cfg_keeps(item_attrs(item))? {
1153 return Ok(());
1154 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1155 // Types were emitted in their own pass.
1156 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
1157 return Ok(());
1158 }
1159 self.item_inner(item)
1160 }
1161
1162 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 8h ago1163 if !matches!(item, Item::Use(_) | Item::ExternCrate(_) | Item::Mod(_) | Item::Type(_)) {
1164 self.emitted += 1;
1165 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1166 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago1167 Item::Fn(f) => {
1168 let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
1169 self.func_named(&nim, &f.sig, &f.block, None)
1170 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1171 Item::Struct(s) => {
1172 let name = s.ident.to_string();
1173 let fields = self.structs[&name].clone();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1174 let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1175 self.line(&format!("type {}*{} = object", ident(&name), g));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1176 self.indent += 1;
1177 if fields.is_empty() {
1178 self.line("discard");
1179 }
1180 for (fname, fty) in &fields {
1181 self.line(&format!("{}*: {}", ident(fname), fty.render()));
1182 }
1183 self.indent -= 1;
1184 self.blank();
1185 Ok(())
1186 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1187 Item::Type(_) => Ok(()), // expanded at every use site
1188 Item::Enum(e) => {
1189 let def = self.enums[&e.ident.to_string()].clone();
1190 self.emit_enum(&def);
1191 Ok(())
1192 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1193 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1194 let t = self.map_ty(&c.ty)?.owned();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1195 // The annotation types the initialiser, exactly as it does for
1196 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
1197 let v = self.expr_at(&c.expr, Some(&t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1198 self.bind(&c.ident.to_string(), t.clone());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1199 // Only a top-level const is exported; `*` on a local is not
1200 // Nim syntax.
1201 let star = if self.indent == 0 { "*" } else { "" };
1202 let line = format!(
1203 "const {}{}: {} = {}",
1204 ident(&c.ident.to_string()),
1205 star,
1206 t.render(),
1207 v.code
1208 );
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1209 self.line(&line);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1210 if self.indent == 0 {
1211 self.blank();
1212 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1213 Ok(())
1214 }
1215 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1216 let outer_g =
1217 std::mem::replace(&mut self.impl_generics, Self::generics_of(&im.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1218 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1219 let outer = self.self_ty.replace(self_ty.clone());
1220 let r = self.impl_body(im, &self_ty);
1221 self.self_ty = outer;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1222 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1223 r
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1224 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1225 // `use` and `extern crate` are resolution directives with no Nim
1226 // analogue once everything is one module.
1227 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
1228 Item::Mod(m) if m.content.is_some() => {
1229 // An inline `mod` is flattened; Nim has no nested modules in a
1230 // single file.
1231 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1232 for i in &items {
1233 self.item(i)?;
1234 }
1235 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1236 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1237 Item::Mod(m) => {
1238 // Satisfied if that file was passed in too; everything is one
1239 // Nim module, so the declaration itself emits nothing.
1240 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
1241 return Ok(());
1242 }
1243 Err(format!(
1244 "`mod {};` refers to another file that was not passed to \
1245 rustnim; add it to the input list",
1246 m.ident
1247 ))
1248 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1249 other => Err(format!("unsupported item: {}", item_kind(other))),
1250 }
1251 }
1252
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1253 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
1254 fn none_of(&self, expect: Option<&Nim>) -> String {
1255 match expect {
1256 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
1257 format!("rsNone[{}]()", a[0].render())
1258 }
1259 _ => "rsNone()".to_string(),
1260 }
1261 }
1262
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1263 fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
1264 if let Some((path, _)) = &im.trait_ {
1265 let tr = path_name(path);
1266 if im.items.is_empty() {
1267 return Ok(());
1268 }
1269 if is_fmt_trait(&tr) {
1270 let syn::ImplItem::Fn(m) = &im.items[0] else {
1271 return Err(format!("unsupported item in `impl {tr}`"));
1272 };
1273 return self.fmt_impl(&tr, self_ty, &m.sig, &m.block);
1274 }
1275 if tr == "From" {
1276 let syn::ImplItem::Fn(m) = &im.items[0] else {
1277 return Err("`impl From` must contain `fn from`".into());
1278 };
1279 let name = {
1280 let (params, _) = self.signature(&m.sig)?;
1281 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
1282 self.from_impls[&(type_name(&src), type_name(self_ty))].clone()
1283 };
1284 return self.func_named(&name, &m.sig, &m.block, None);
1285 }
1286 let tyname = type_name(self_ty);
1287 for it in &im.items {
1288 let syn::ImplItem::Fn(m) = it else {
1289 return Err(format!("unsupported item in `impl {tr}`"));
1290 };
1291 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1292 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
1293 self.func_named(&nim, &m.sig, &m.block, recv)?;
1294 }
1295 return Ok(());
1296 }
1297 for it in &im.items {
1298 match it {
1299 syn::ImplItem::Fn(m) => {
1300 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1301 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
1302 self.func_named(&nim, &m.sig, &m.block, recv)?;
1303 }
1304 _ => return Err("only `fn` items are supported inside `impl`".into()),
1305 }
1306 }
1307 Ok(())
1308 }
1309
1310 /// The type an operator impl declares for its right-hand operand.
1311 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
1312 let n = type_name(t.as_ref()?);
1313 let sig = self.methods.get(&(n, op_method(op).to_string()))?;
1314 sig.params.get(1).cloned().map(|t| t.unvar())
1315 }
1316
1317 /// The proc implementing `op` for a user type, if there is one.
1318 fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> {
1319 let n = type_name(t.as_ref()?);
1320 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
1321 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
1322 Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1))
1323 } else {
1324 None
1325 }
1326 }
1327
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1328 fn emit_enum(&mut self, def: &EnumDef) {
1329 let name = ident(&def.name);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1330 let g = Self::gen_list(
1331 self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
1332 );
1333 if def.simple && !g.is_empty() {
1334 // A Nim `enum` cannot take parameters; an all-unit generic enum
1335 // has no payload to be generic in anyway, so this would be a
1336 // parameter that never appears.
1337 // Fall through to the object-variant form instead.
1338 }
1339 if def.simple && g.is_empty() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1340 // Every variant is a unit variant, so a plain Nim enum is an exact
1341 // fit: it compares, orders and `case`-checks like Rust's.
1342 self.line(&format!("type {name}* = enum"));
1343 self.indent += 1;
1344 for v in &def.variants {
1345 self.line(&format!("{}", ident(&v.name)));
1346 }
1347 self.indent -= 1;
1348 self.blank();
1349 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1350 self.indent += 1;
1351 self.line("case x");
1352 for v in &def.variants {
1353 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
1354 }
1355 self.indent -= 1;
1356 self.blank();
1357 return;
1358 }
1359
1360 // A data-carrying enum is a Nim object variant: one discriminant enum
1361 // plus a branch per variant. This is the same shape the prelude uses
1362 // for `Option` and `Result`.
1363 self.line("type");
1364 self.indent += 1;
1365 self.line(&format!("{}Kind* = enum", name));
1366 self.indent += 1;
1367 for v in &def.variants {
1368 self.line(&def.kind_ident(&v.name));
1369 }
1370 self.indent -= 1;
1371 self.blank();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1372 self.line(&format!("{}*{} = object", name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1373 self.indent += 1;
1374 self.line(&format!("case kind*: {}Kind", name));
1375 for v in &def.variants {
1376 if v.fields.is_empty() {
1377 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
1378 } else {
1379 self.line(&format!("of {}:", def.kind_ident(&v.name)));
1380 self.indent += 1;
1381 for (f, t) in &v.fields {
1382 self.line(&format!("{}*: {}", ident(f), t.render()));
1383 }
1384 self.indent -= 1;
1385 }
1386 }
1387 self.indent -= 2;
1388 self.blank();
1389
1390 for v in &def.variants {
1391 let args: Vec<String> = v
1392 .fields
1393 .iter()
1394 .enumerate()
1395 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
1396 .collect();
1397 let inits: Vec<String> = v
1398 .fields
1399 .iter()
1400 .enumerate()
1401 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
1402 .collect();
1403 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1404 all.extend(inits);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1405 let ret = format!("{}{}", name, g);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1406 self.line(&format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1407 "proc {}*{}({}): {} = {}({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1408 def.ctor_ident(&v.name),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1409 g,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1410 args.join(", "),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1411 ret,
1412 ret,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1413 all.join(", ")
1414 ));
1415 }
1416 self.blank();
1417
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1418 self.line(&format!("proc rsDebug*{}(x: {}{}): string =", g, name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1419 self.indent += 1;
1420 self.line("case x.kind");
1421 for v in &def.variants {
1422 if v.fields.is_empty() {
1423 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
1424 } else {
1425 let parts: Vec<String> = v
1426 .fields
1427 .iter()
1428 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
1429 .collect();
1430 self.line(&format!(
1431 "of {}: \"{}(\" & {} & \")\"",
1432 def.kind_ident(&v.name),
1433 v.name,
1434 parts.join(" & \", \" & ")
1435 ));
1436 }
1437 }
1438 self.indent -= 1;
1439 self.blank();
1440 }
1441
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1442 /// The concrete type an enum variant constructs, and the `[T]` list to
1443 /// spell at the constructor when the enum is generic.
1444 fn variant_type(
1445 &self,
1446 def: &EnumDef,
1447 expect: Option<&Nim>,
1448 ) -> Result<(Nim, String), String> {
1449 let params = self.type_generics.get(&def.name).cloned().unwrap_or_default();
1450 if params.is_empty() {
1451 return Ok((Nim::Named(def.name.clone(), vec![]), String::new()));
1452 }
1453 match expect {
1454 Some(Nim::Named(n, a)) if *n == def.name && a.len() == params.len() => Ok((
1455 Nim::Named(def.name.clone(), a.clone()),
1456 format!("[{}]", a.iter().map(|t| t.render()).collect::<Vec<_>>().join(", ")),
1457 )),
1458 _ => Err(format!(
1459 "`{}` is a variant of a generic enum, and its type parameters \
1460 cannot be inferred here; annotate the binding or the return type",
1461 def.name
1462 )),
1463 }
1464 }
1465
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1466 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1467 /// to the enum that declares it.
1468 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
1469 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1470 let last = segs.last()?.clone();
1471 if segs.len() >= 2 {
1472 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
1473 if def.get(&last).is_some() {
1474 return Some((def.clone(), last));
1475 }
1476 }
1477 }
1478 // Unqualified: only unambiguous if exactly one enum declares it.
1479 match self.variant_owner.get(&last) {
1480 Some(owners) if owners.len() == 1 => {
1481 let def = self.enums.get(&owners[0])?;
1482 Some((def.clone(), last))
1483 }
1484 _ => None,
1485 }
1486 }
1487
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1488 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
1489 ///
1490 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
1491 /// observable result of `{}` is exactly the bytes written. So the method
1492 /// becomes `proc rsDisplay(self: T): string` and every write through the
1493 /// formatter produces that string. A `fmt` body that does anything else
1494 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
1495 /// because those affect the output and this model does not carry them.
1496 /// The window an expression names, if it names one.
1497 fn window_of(&self, e: &Expr) -> Option<Alias> {
1498 match e {
1499 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
1500 Some(a @ Alias::Window { .. }) => Some(a),
1501 _ => None,
1502 },
1503 Expr::Reference(r) => self.window_of(&r.expr),
1504 Expr::Paren(p) => self.window_of(&p.expr),
1505 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
1506 _ => None,
1507 }
1508 }
1509
1510 /// Whether an expression is the `Formatter` parameter of the formatting
1511 /// impl currently being lowered.
1512 fn is_fmt_param(&self, e: &Expr) -> bool {
1513 let Some(f) = &self.fmt_param else { return false };
1514 match e {
1515 Expr::Path(p) => path_name(&p.path) == *f,
1516 Expr::Reference(r) => self.is_fmt_param(&r.expr),
1517 Expr::Paren(p) => self.is_fmt_param(&p.expr),
1518 _ => false,
1519 }
1520 }
1521
1522 fn fmt_impl(
1523 &mut self,
1524 tr: &str,
1525 self_ty: &Nim,
1526 sig: &syn::Signature,
1527 body: &syn::Block,
1528 ) -> Result<(), String> {
1529 let proc_name = fmt_proc(tr);
1530 // The formatter is the parameter after `self`.
1531 let f = sig
1532 .inputs
1533 .iter()
1534 .filter_map(|a| match a {
1535 FnArg::Typed(t) => match &*t.pat {
1536 Pat::Ident(i) => Some(i.ident.to_string()),
1537 _ => None,
1538 },
1539 _ => None,
1540 })
1541 .next()
1542 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1543
1544 self.push_scope();
1545 self.bind("self", self_ty.clone());
1546 let saved = self.fmt_param.replace(f);
1547 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 9h ago1548 // No assignment target: a formatter write *appends*, because a `fmt`
1549 // body may write repeatedly -- `UpperHex` writes once per byte in a
1550 // loop -- and assigning would keep only the last one.
1551 let outer_target = self.target.take();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1552
1553 self.line(&format!(
1554 "proc {}*(self: {}): string =",
1555 proc_name,
1556 self_ty.render()
1557 ));
1558 self.indent += 1;
1559 let before = self.out.len();
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago1560 let tail = self.block_body(body)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1561 self.emit_tail(tail);
1562 if self.out.len() == before {
1563 self.line("discard");
1564 }
1565 self.indent -= 1;
1566
1567 self.target = outer_target;
1568 self.ret = outer_ret;
1569 self.fmt_param = saved;
1570 self.pop_scope();
1571 self.blank();
1572 Ok(())
1573 }
1574
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1575 fn func(
1576 &mut self,
1577 sig: &syn::Signature,
1578 body: &syn::Block,
1579 recv: Option<Nim>,
1580 ) -> Result<(), String> {
1581 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1582 self.func_named(&name.clone(), sig, body, recv)
1583 }
1584
1585 fn func_named(
1586 &mut self,
1587 name: &str,
1588 sig: &syn::Signature,
1589 body: &syn::Block,
1590 recv: Option<Nim>,
1591 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1592 let (ptys, ret) = self.signature(sig)?;
1593
1594 self.push_scope();
1595 let mut rendered: Vec<String> = Vec::new();
1596
1597 if let Some(self_ty) = recv {
1598 // `&mut self` and `mut self` both mean the body may mutate the
1599 // receiver; only the former is observable by the caller, and a Nim
1600 // `var` parameter is the faithful spelling of that.
1601 let mutable = matches!(
1602 sig.inputs.first(),
1603 Some(FnArg::Receiver(r))
1604 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1605 );
1606 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1607 rendered.push(format!("self: {}", t.render()));
1608 self.bind("self", self_ty);
1609 }
1610
1611 let typed: Vec<&syn::PatType> = sig
1612 .inputs
1613 .iter()
1614 .filter_map(|a| match a {
1615 FnArg::Typed(t) => Some(t),
1616 _ => None,
1617 })
1618 .collect();
1619 for (p, t) in typed.iter().zip(ptys.iter()) {
1620 let pname = match &*p.pat {
1621 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1622 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
1623 // still needs a name for it.
1624 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1625 _ => return Err("only plain identifier parameters are supported".into()),
1626 };
1627 rendered.push(format!("{}: {}", ident(&pname), t.render()));
1628 // Inside the body a `var T` parameter is used exactly like a `T`.
1629 self.bind(&pname, t.clone().owned());
1630 }
1631
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1632 let mut gparams = self.impl_generics.clone();
1633 for g in Self::generics_of(&sig.generics) {
1634 if !gparams.contains(&g) {
1635 gparams.push(g);
1636 }
1637 }
1638 let gens = Self::gen_list(&gparams);
1639 let outer_fg = std::mem::replace(&mut self.fn_generics, gparams.clone());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1640 let head = if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1641 format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1642 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1643 format!(
1644 "proc {}*{}({}): {} =",
1645 ident(name),
1646 gens,
1647 rendered.join(", "),
1648 ret.render()
1649 )
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1650 };
1651 self.line(&head);
1652 self.indent += 1;
1653 let outer_ret = self.ret.replace(ret.clone());
1654
1655 // A Rust fn's trailing expression is its return value. Naming Nim's
1656 // implicit `result` as the target makes that true whether the tail is
1657 // a plain expression or an `if`/`match` with statement arms.
1658 let outer_target = if ret == Nim::Unit {
1659 self.target.take()
1660 } else {
1661 self.target.replace(("result".to_string(), Some(ret.clone())))
1662 };
1663 let before = self.out.len();
1664 let tail = self.block_body_at(body, Some(&ret))?;
1665 self.target = outer_target;
1666 match tail {
1667 Some(v) if ret != Nim::Unit => {
1668 let code = v.code.clone();
1669 self.line(&format!("result = {code}"));
1670 }
1671 Some(v) => {
1672 // A trailing expression in a `()`-returning fn is evaluated for
1673 // its effect; Nim requires an explicit discard.
1674 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1675 if needs_discard && !v.code.is_empty() {
1676 let code = v.code.clone();
1677 self.line(&format!("discard {code}"));
1678 }
1679 }
1680 None => {}
1681 }
1682 if self.out.len() == before {
1683 self.line("discard");
1684 }
1685
1686 self.indent -= 1;
1687 self.ret = outer_ret;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1688 self.fn_generics = outer_fg;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1689 self.pop_scope();
1690 self.blank();
1691 Ok(())
1692 }
1693
1694 // ---------------------------------------------------------- statements
1695
1696 /// Lower a block's statements. Returns the block's trailing expression,
1697 /// if it has one, *without* emitting it — the caller decides whether that
1698 /// value is a return value, a binding, or discarded.
1699 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
1700 self.block_body_at(b, None)
1701 }
1702
1703 fn block_body_at(
1704 &mut self,
1705 b: &syn::Block,
1706 expect: Option<&Nim>,
1707 ) -> Result<Option<Val>, String> {
1708 // An assignment target belongs to *this* block's trailing expression
1709 // only. A non-final `if` is a statement and must not assign anything.
1710 let target = self.target.take();
1711 let n = b.stmts.len();
1712 let mut tail = None;
1713 for (i, st) in b.stmts.iter().enumerate() {
1714 let last = i + 1 == n;
1715 match st {
1716 Stmt::Expr(e, None) if last && expressible(e) => {
1717 tail = Some(self.expr_at(e, expect)?)
1718 }
1719 Stmt::Expr(e, None) if last => {
1720 // A trailing `if`/`match` with statement arms, or a loop.
1721 // Lower it as statements; if this block's value is wanted,
1722 // each arm assigns it.
1723 match &target {
1724 Some((t, ty)) => {
1725 let (t, ty) = (t.clone(), ty.clone());
1726 self.assign_from(e, &t, ty.as_ref())?;
1727 }
1728 None => self.stmt(st)?,
1729 }
1730 }
1731 _ => self.stmt(st)?,
1732 }
1733 }
1734 self.target = target;
1735 Ok(tail)
1736 }
1737
1738 /// Lower a block in statement position (loop bodies, `if` arms).
1739 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
1740 self.push_scope();
1741 self.indent += 1;
1742 let before = self.out.len();
1743 let want = self.target.clone().and_then(|(_, t)| t);
1744 let tail = self.block_body_at(b, want.as_ref())?;
1745 self.emit_tail(tail);
1746 if self.out.len() == before {
1747 self.line("discard");
1748 }
1749 self.indent -= 1;
1750 self.pop_scope();
1751 Ok(())
1752 }
1753
1754 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
1755 match s {
1756 Stmt::Local(l) => self.local(l),
1757 Stmt::Expr(e, _) => {
1758 let v = self.expr_stmt(e)?;
1759 if let Some(v) = v {
1760 // A bare expression with a value must be discarded in Nim.
1761 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1762 let code = v.code.clone();
1763 if needs {
1764 self.line(&format!("discard {code}"));
1765 } else if !code.is_empty() {
1766 self.line(&code);
1767 }
1768 }
1769 Ok(())
1770 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1771 // A `const` declared inside a function body is local to it, and
1772 // must be emitted here rather than skipped as an already-emitted
1773 // top-level type.
1774 Stmt::Item(i) => self.item_inner(i),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1775 Stmt::Macro(m) => {
1776 let line = self.macro_call(&m.mac)?;
1777 self.line(&line);
1778 Ok(())
1779 }
1780 }
1781 }
1782
1783 fn local(&mut self, l: &Local) -> Result<(), String> {
1784 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
1785 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
1786 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1787 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 11h ago1788 _ => return Err("only `let <ident>` bindings are supported".into()),
1789 },
1790 Pat::Wild(_) => ("_".into(), false, None),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1791 Pat::Tuple(t) => return self.local_tuple(l, t),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1792 _ => return Err("destructuring `let` is not implemented yet".into()),
1793 };
1794
1795 let Some(init) = &l.init else {
1796 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
1797 // not. Rust's own rules make reading it before assignment illegal,
1798 // so the two agree on every program rustc accepts.
1799 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
1800 let t = t.owned();
1801 self.line(&format!("var {}: {}", ident(&name), t.render()));
1802 self.bind(&name, t);
1803 return Ok(());
1804 };
1805 if init.diverge.is_some() {
1806 return Err("`let ... else` is not implemented yet".into());
1807 }
1808
1809 if !expressible(&init.expr) && name != "_" {
1810 // The initialiser is an `if`/`match` whose arms are statements.
1811 // Declare first, then let each arm assign into the binding.
1812 let t = ann
1813 .clone()
1814 .ok_or_else(|| {
1815 format!(
1816 "`let {name} = match/if ...` needs a type annotation: \
1817 its arms are statements, so the binding must be \
1818 declared before they run"
1819 )
1820 })?
1821 .owned();
1822 self.line(&format!("var {}: {}", ident(&name), t.render()));
1823 self.bind(&name, t.clone());
1824 let target = ident(&name);
1825 return self.assign_from(&init.expr, &target, Some(&t));
1826 }
1827
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1828 // `let it = xs.chunks_exact(k)` binds an iterator, not a value.
1829 if is_iterator_expr(&init.expr) {
1830 let it = self.resolve_iter(&init.expr)?;
1831 self.bind_alias(&name, Alias::Iterator(Box::new(it)));
1832 return Ok(());
1833 }
1834
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1835 let v = self.expr_at(&init.expr, ann.as_ref())?;
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 9h ago1836
1837 // `let s = &buf[..n]` binds a view of a place that is already in
1838 // scope. Nim's borrow checker will not let a `let` borrow out of a
1839 // local, and there is nothing to materialise anyway -- a view is a
1840 // reference. Binding it as an alias substitutes the same expression at
1841 // each use, which re-evaluates nothing because the initialiser is a
1842 // place expression with no side effects.
1843 if v.window.is_none()
1844 && matches!(v.ty, Some(Nim::OpenArray(_)))
1845 && is_pure_place(&init.expr)
1846 {
1847 let t = v.ty.clone().unwrap();
1848 let elem = match &t {
1849 Nim::OpenArray(e) => Some((**e).clone()),
1850 _ => None,
1851 };
1852 self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) });
1853 let _ = elem;
1854 return Ok(());
1855 }
1856
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1857 if let Some(w) = v.window.clone() {
1858 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
1859 // view into the caller's buffer. Copying it into a `seq` would
1860 // still print the right bytes but would stop writes reaching the
1861 // caller, so it is bound as an alias.
1862 if v.guard.is_some() && v.guard_err.is_some() {
1863 return Err(format!(
1864 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
1865 which Nim cannot represent; apply `?` or `unwrap()` to it \
1866 in the same expression"
1867 ));
1868 }
1869 self.bind_alias(&name, w);
1870 return Ok(());
1871 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago1872 // A `let` binding a borrow keeps the view: `let res = encode(..)?`
1873 // names the caller's buffer, and copying it into a `seq` would still
1874 // print the right bytes while silently breaking the aliasing.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1875 let t = match (ann, &v.ty) {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago1876 (Some(a), _) => a.unvar(),
1877 (None, Some(t)) => t.clone().unvar(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1878 (None, None) => {
1879 return Err(format!(
1880 "cannot infer the type of `let {name}`; annotate it — \
1881 guessing here would change integer width, and with it the \
1882 meaning of any arithmetic on `{name}`"
1883 ))
1884 }
1885 };
1886
1887 if name == "_" {
1888 let code = v.code.clone();
1889 self.line(&format!("discard {code}"));
1890 return Ok(());
1891 }
1892 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
1893 // 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 9h ago1894 //
1895 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
1896 // Rust may write through it, and Nim only accepts a `var` where a
1897 // `var` parameter is wanted, so the binding has to be one.
1898 let mutable = mutable || is_mut_borrow(&init.expr);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1899 let kw = if mutable { "var" } else { "let" };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1900 // Inside a generic proc the binding's type may mention a parameter Nim
1901 // will infer; naming it in an annotation would not resolve.
1902 let line = if self.mentions_type_param(&t) {
1903 format!("{} {} = {}", kw, ident(&name), v.code)
1904 } else {
1905 format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)
1906 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1907 self.line(&line);
1908 self.bind(&name, t);
1909 Ok(())
1910 }
1911
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago1912 /// `let (a, b) = ..` — tuple destructuring.
1913 fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> {
1914 let Some(init) = &l.init else {
1915 return Err("a destructuring `let` needs an initialiser".into());
1916 };
1917 let names: Vec<(String, bool)> = t
1918 .elems
1919 .iter()
1920 .map(|p| match p {
1921 Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())),
1922 Pat::Wild(_) => Ok(("_".to_string(), false)),
1923 _ => Err("only plain identifiers are supported in a destructuring `let`"),
1924 })
1925 .collect::<Result<_, _>>()?;
1926
1927 // `split_at` hands back two *views* of the same slice. Nim has no
1928 // tuple of views, and there is nothing to materialise anyway, so each
1929 // name becomes a window into the original.
1930 if let Expr::MethodCall(m) = &*init.expr {
1931 let mname = m.method.to_string();
1932 if (mname == "split_at" || mname == "split_at_mut")
1933 && m.args.len() == 1
1934 && names.len() == 2
1935 {
1936 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
1937 let at = self.expr(&m.args[0])?;
1938 let cut = self.fresh("Cut");
1939 self.line(&format!("let {}: int = int({})", cut, at.code));
1940 self.bind_alias(
1941 &names[0].0,
1942 Alias::Window {
1943 code: code.clone(),
1944 off: base.clone(),
1945 len: cut.clone(),
1946 elem: elem.clone(),
1947 },
1948 );
1949 self.bind_alias(
1950 &names[1].0,
1951 Alias::Window {
1952 code,
1953 off: format!("({} + {})", base, cut),
1954 len: format!("({} - {})", len, cut),
1955 elem,
1956 },
1957 );
1958 return Ok(());
1959 }
1960 }
1961
1962 let v = self.expr(&init.expr)?;
1963 let tys = match &v.ty {
1964 Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(),
1965 _ => {
1966 return Err(format!(
1967 "cannot destructure this into {} bindings: its type is not a \
1968 tuple of that many elements",
1969 names.len()
1970 ))
1971 }
1972 };
1973 let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" };
1974 let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect();
1975 self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code));
1976 for ((n, _), t) in names.iter().zip(tys) {
1977 self.bind(n, t);
1978 }
1979 Ok(())
1980 }
1981
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1982 /// Expressions that are statements in Rust and statements in Nim too
1983 /// (control flow). Returns `None` when it emitted lines itself.
1984 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
1985 match e {
1986 Expr::If(_) => {
1987 self.if_stmt(e)?;
1988 Ok(None)
1989 }
1990 Expr::While(w) => {
1991 if w.label.is_some() {
1992 return Err("loop labels are not implemented yet".into());
1993 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1994 self.in_loop_cond = true;
1995 let c = self.expr(&w.cond);
1996 self.in_loop_cond = false;
1997 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1998 self.line(&format!("while {}:", c.code));
1999 let saved = self.target.take();
2000 self.nested_block(&w.body)?;
2001 self.target = saved;
2002 Ok(None)
2003 }
2004 Expr::Loop(l) => {
2005 if l.label.is_some() {
2006 return Err("loop labels are not implemented yet".into());
2007 }
2008 self.line("while true:");
2009 let saved = self.target.take();
2010 self.nested_block(&l.body)?;
2011 self.target = saved;
2012 Ok(None)
2013 }
2014 Expr::ForLoop(f) => {
2015 self.for_loop(f)?;
2016 Ok(None)
2017 }
2018 Expr::Block(b) => {
2019 if b.label.is_some() {
2020 return Err("block labels are not implemented yet".into());
2021 }
2022 self.line("block:");
2023 self.nested_block(&b.block)?;
2024 Ok(None)
2025 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2026 Expr::Unsafe(u) => {
2027 // Transparent in statement position too, for the same reason.
2028 self.nested_block_flat(&u.block)?;
2029 Ok(None)
2030 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2031 Expr::Match(_) => {
2032 self.match_stmt(e)?;
2033 Ok(None)
2034 }
2035 Expr::Return(r) => {
2036 match &r.expr {
2037 Some(e) => {
2038 let want = self.ret.clone();
2039 let v = self.expr_at(e, want.as_ref())?;
2040 self.line(&format!("return {}", v.code));
2041 }
2042 None => self.line("return"),
2043 }
2044 Ok(None)
2045 }
2046 Expr::Break(b) => {
2047 if b.expr.is_some() || b.label.is_some() {
2048 return Err("`break` with a value or a label is not implemented yet".into());
2049 }
2050 self.line("break");
2051 Ok(None)
2052 }
2053 Expr::Continue(c) => {
2054 if c.label.is_some() {
2055 return Err("labelled `continue` is not implemented yet".into());
2056 }
2057 self.line("continue");
2058 Ok(None)
2059 }
2060 Expr::Assign(a) => {
2061 let lhs = self.expr(&a.left)?;
2062 if !expressible(&a.right) {
2063 let target = lhs.code.clone();
2064 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
2065 }
2066 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
2067 self.line(&format!("{} = {}", lhs.code, rhs.code));
2068 Ok(None)
2069 }
2070 Expr::Binary(b) if is_compound(&b.op) => {
2071 let lhs = self.expr(&b.left)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago2072 // A compound assignment on a user type goes to that type's own
2073 // `impl OpAssign`, not to Nim's built-in operator.
2074 if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) {
2075 // The impl's own parameter type types the right operand,
2076 // so `b_vec *= 4` takes 4 at the width the impl declares.
2077 let want = self.op_param(&lhs.ty, compound_symbol(&b.op));
2078 let rhs = self.expr_at(&b.right, want.as_ref())?;
2079 self.line(&format!("{}({}, {})", f, lhs.code, rhs.code));
2080 return Ok(None);
2081 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2082 // `i += 1` must widen the literal to `i`'s type, not to the
2083 // i32 an unconstrained Rust literal would default to.
2084 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
2085 let op = self.bin_op(&b.op, &lhs, &rhs)?;
2086 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
2087 // both languages, so the expanded form is always correct.
2088 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
2089 Ok(None)
2090 }
2091 Expr::Macro(m) => {
2092 let line = self.macro_call(&m.mac)?;
2093 self.line(&line);
2094 Ok(None)
2095 }
2096 _ => Ok(Some(self.expr(e)?)),
2097 }
2098 }
2099
2100 /// Lower `e` in statement position, assigning each arm's value to
2101 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
2102 /// the trip when their arms are too big for a Nim `if`-expression.
2103 fn assign_from(
2104 &mut self,
2105 e: &Expr,
2106 target: &str,
2107 expect: Option<&Nim>,
2108 ) -> Result<(), String> {
2109 let saved = self.target.replace((target.to_string(), expect.cloned()));
2110 let r = match e {
2111 Expr::If(_) => self.if_stmt(e),
2112 Expr::Match(_) => self.match_stmt(e),
2113 other => {
2114 let v = self.expr_at(other, expect)?;
2115 self.line(&format!("{} = {}", target, v.code));
2116 Ok(())
2117 }
2118 };
2119 self.target = saved;
2120 r
2121 }
2122
2123 /// Emit a block's value into the active assignment target, if there is
2124 /// one, or discard it if there is not.
2125 fn emit_tail(&mut self, v: Option<Val>) {
2126 let Some(v) = v else { return };
2127 match self.target.clone() {
2128 Some((t, _)) => {
2129 let code = v.code.clone();
2130 self.line(&format!("{t} = {code}"));
2131 }
2132 None => {
2133 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2134 let code = v.code.clone();
2135 if needs {
2136 self.line(&format!("discard {code}"));
2137 } else if !code.is_empty() {
2138 self.line(&code);
2139 }
2140 }
2141 }
2142 }
2143
2144 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
2145 let Expr::If(i) = e else { unreachable!() };
2146 if let Expr::Let(_) = &*i.cond {
2147 return Err("`if let` is not implemented yet".into());
2148 }
2149 let c = self.expr(&i.cond)?;
2150 self.line(&format!("if {}:", c.code));
2151 self.nested_block(&i.then_branch)?;
2152 match &i.else_branch {
2153 None => {}
2154 Some((_, els)) => match &**els {
2155 Expr::If(_) => {
2156 // Nim needs `elif`; splice the nested `if` in as one.
2157 let mark = self.out.len();
2158 self.if_stmt(els)?;
2159 let tail = self.out.split_off(mark);
2160 let indent = " ".repeat(self.indent);
2161 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
2162 }
2163 Expr::Block(b) => {
2164 self.line("else:");
2165 self.nested_block(&b.block)?;
2166 }
2167 _ => return Err("unsupported `else` form".into()),
2168 },
2169 }
2170 Ok(())
2171 }
2172
2173 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
2174 if f.label.is_some() {
2175 return Err("loop labels are not implemented yet".into());
2176 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2177 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2178
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2179 // One index loop drives the whole chain. Rust's adaptors are lazy and
2180 // compose; resolving them to an index and binding each name to an
2181 // lvalue reproduces that without materialising anything.
2182 let i = self.fresh("Idx");
2183 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
2184 self.indent += 1;
2185 self.push_scope();
2186 let before = self.out.len();
2187
2188 self.bind_pattern(&f.pat, &it, &i)?;
2189
2190 let saved = self.target.take();
2191 if let Some(v) = self.block_body(&f.body)? {
2192 let code = v.code.clone();
2193 self.line(&format!("discard {code}"));
2194 }
2195 self.target = saved;
2196 if self.out.len() == before {
2197 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2198 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2199 self.pop_scope();
2200 self.indent -= 1;
2201 Ok(())
2202 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2203
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2204 /// Resolve a chain of iterator adaptors into a single `Iter`.
2205 ///
2206 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
2207 /// `filter`, `take_while` and friends are rejected rather than partially
2208 /// honoured: silently dropping an adaptor would change which elements the
2209 /// loop visits.
2210 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
2211 match e {
2212 Expr::Reference(r) => self.resolve_iter(&r.expr),
2213 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2214 Expr::Range(r) => {
2215 let lo = match &r.start {
2216 Some(e) => self.expr(e)?,
2217 None => return Err("a `for` over `..n` needs a start bound".into()),
2218 };
2219 let hi = match &r.end {
2220 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2221 None => {
2222 return Err("a `for` over an unbounded range would not terminate".into())
2223 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2224 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2225 let ty = lo.ty.clone().or(hi.ty.clone());
2226 Ok(Iter::Range {
2227 lo: lo.code,
2228 hi: hi.code,
2229 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
2230 ty,
2231 })
2232 }
2233 Expr::MethodCall(m) => {
2234 let name = m.method.to_string();
2235 match name.as_str() {
2236 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
2237 let mut it = self.resolve_iter(&m.receiver)?;
2238 if name == "iter_mut" {
2239 if let Iter::Elems { mutable, .. } = &mut it {
2240 *mutable = true;
2241 }
2242 }
2243 Ok(it)
2244 }
2245 "enumerate" if m.args.is_empty() => {
2246 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
2247 }
2248 "zip" if m.args.len() == 1 => {
2249 let a = self.resolve_iter(&m.receiver)?;
2250 let b = self.resolve_iter(&m.args[0])?;
2251 Ok(Iter::Zip(Box::new(a), Box::new(b)))
2252 }
2253 "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 9h ago2254 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2255 let k = self.expr(&m.args[0])?;
2256 Ok(Iter::Chunks {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2257 code,
2258 base,
2259 len,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2260 k: k.code,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2261 elem,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2262 mutable: name.ends_with("_mut"),
2263 })
2264 }
2265 "windows" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2266 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2267 let k = self.expr(&m.args[0])?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2268 Ok(Iter::Windows { code, base, len, k: k.code, elem })
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2269 }
2270 other => Err(format!(
2271 "iterator adaptor `.{other}()` is not implemented; it has \
2272 no index-loop equivalent here, and dropping it would \
2273 change which elements the loop visits"
2274 )),
2275 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2276 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago2277 Expr::Path(p) => {
2278 let n = path_name(&p.path);
2279 if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) {
2280 return Ok((*it).clone());
2281 }
2282 if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) {
2283 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
2284 }
2285 let v = self.expr(e)?;
2286 Ok(Iter::Elems {
2287 len: format!("{}.len", v.code),
2288 elem: elem_of(&v.ty),
2289 code: v.code,
2290 off: "0".into(),
2291 mutable: false,
2292 })
2293 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2294 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2295 // A `for` binding that is itself a window iterates that window,
2296 // not the whole container it points into.
2297 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 9h ago2298 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2299 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2300 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2301 Ok(Iter::Elems {
2302 len: format!("{}.len", v.code),
2303 elem: elem_of(&v.ty),
2304 code: v.code,
2305 off: "0".into(),
2306 mutable: false,
2307 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2308 }
2309 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2310 }
2311
2312 /// Bind a `for` pattern against a resolved iterator at index `i`.
2313 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
2314 match (p, it) {
2315 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
2316 self.bind_pattern(&t.elems[0], a, i)?;
2317 self.bind_pattern(&t.elems[1], b, i)
2318 }
2319 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
2320 if let Pat::Ident(id) = &t.elems[0] {
2321 let n = id.ident.to_string();
2322 // Rust's `enumerate` counts in `usize`.
2323 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
2324 self.bind(&n, Nim::Prim("uint".into()));
2325 }
2326 self.bind_pattern(&t.elems[1], inner, i)
2327 }
2328 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
2329 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
2330 ),
2331 (Pat::Wild(_), _) => Ok(()),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2332 // `for &byte in xs` — the `&` destructures the reference, which in
2333 // Nim is already the value.
2334 (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
2335 (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2336 (Pat::Ident(id), _) => {
2337 let name = id.ident.to_string();
2338 match it {
2339 Iter::Range { lo, ty, .. } => {
2340 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
2341 // The loop counts from zero; the range's own start is
2342 // added back so the binding has Rust's value and type.
2343 self.line(&format!(
2344 "let {}: {} = {}({}) + {}",
2345 ident(&name),
2346 t.render(),
2347 t.render(),
2348 i,
2349 lo
2350 ));
2351 self.bind(&name, t);
2352 Ok(())
2353 }
2354 Iter::Elems { code, off, elem, mutable, .. } => {
2355 let access = if off == "0" {
2356 format!("{}[{}]", code, i)
2357 } else {
2358 format!("{}[{} + {}]", code, off, i)
2359 };
2360 if *mutable {
2361 // An alias, not a copy: assigning through the
2362 // binding must reach the original element.
2363 self.bind_alias(
2364 &name,
2365 Alias::Value { code: access, ty: elem.clone() },
2366 );
2367 } else {
2368 let t = elem
2369 .clone()
2370 .ok_or("cannot infer the element type of this `for`")?;
2371 self.line(&format!(
2372 "let {}: {} = {}",
2373 ident(&name),
2374 t.render(),
2375 access
2376 ));
2377 self.bind(&name, t);
2378 }
2379 Ok(())
2380 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2381 Iter::Chunks { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2382 self.bind_alias(
2383 &name,
2384 Alias::Window {
2385 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2386 off: format!("({} + {} * int({}))", base, i, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2387 len: format!("int({})", k),
2388 elem: elem.clone(),
2389 },
2390 );
2391 Ok(())
2392 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2393 Iter::Windows { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2394 self.bind_alias(
2395 &name,
2396 Alias::Window {
2397 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2398 off: format!("({} + {})", base, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2399 len: format!("int({})", k),
2400 elem: elem.clone(),
2401 },
2402 );
2403 Ok(())
2404 }
2405 // Handled above: a zip or enumerate needs a tuple pattern,
2406 // and binding one name to the pair is not supported.
2407 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
2408 }
2409 }
2410 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2411 }
2412 }
2413
2414 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
2415 let Expr::Match(m) = e else { unreachable!() };
2416 let scrut = self.expr(&m.expr)?;
2417 let t = scrut
2418 .ty
2419 .clone()
2420 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2421 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2422 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2423
2424 // A `match` whose arms neither bind nor guard is a Nim `case`, which
2425 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
2426 // an if/elif chain, because Nim's `case` cannot destructure.
2427 let plain = m.arms.iter().all(|a| {
2428 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
2429 });
2430 if plain {
2431 self.match_case(m, &name, &t)
2432 } else {
2433 self.match_chain(m, &name, &t)
2434 }
2435 }
2436
2437 fn match_case(
2438 &mut self,
2439 m: &syn::ExprMatch,
2440 name: &str,
2441 t: &Nim,
2442 ) -> Result<(), String> {
2443 // A variant object is discriminated by its `kind` field.
2444 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
2445 self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2446
2447 let mut saw_wild = false;
2448 for arm in &m.arms {
2449 match &arm.pat {
2450 Pat::Wild(_) => {
2451 saw_wild = true;
2452 self.line("else:");
2453 }
2454 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2455 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2456 self.line(&format!("of {}:", labels.join(", ")));
2457 }
2458 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2459 self.arm_body(&arm.body)?;
2460 }
2461 if !saw_wild && !self.case_is_total(t, m) {
2462 // Rust checked exhaustiveness already, but Nim cannot always see
2463 // it -- an integer `case` needs every value covered -- so make the
2464 // unreachable arm explicit rather than leave a compile error.
2465 self.line("else:");
2466 self.line(" rsPanic(\"unreachable match arm\")");
2467 }
2468 Ok(())
2469 }
2470
2471 /// Whether a Nim `case` over this type is already total, in which case
2472 /// adding an `else` would be a compile error rather than a safety net.
2473 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
2474 let Nim::Named(n, _) = t else { return false };
2475 let Some(def) = self.enums.get(n) else { return false };
2476 def.variants.len() == m.arms.len()
2477 }
2478
2479 /// The if/elif form, for arms that bind or destructure.
2480 fn match_chain(
2481 &mut self,
2482 m: &syn::ExprMatch,
2483 name: &str,
2484 t: &Nim,
2485 ) -> Result<(), String> {
2486 let mut first = true;
2487 let mut closed = false;
2488 for arm in &m.arms {
2489 let (pat, guard) = match &arm.pat {
2490 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
2491 p => (p, None),
2492 };
2493 if guard.is_some() && binds(pat) {
2494 return Err("a `match` guard on a binding pattern is not \
2495 implemented yet"
2496 .into());
2497 }
2498 let test = self.pat_test(pat, name, t)?;
2499 let test = match (test, guard) {
2500 (Some(t), Some(g)) => {
2501 let g = self.expr(g)?;
2502 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2503 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2504 (None, Some(g)) => Some(self.expr(g)?.code),
2505 (t, None) => t,
2506 };
2507 match test {
2508 Some(test) => {
2509 self.line(&format!(
2510 "{} {}:",
2511 if first { "if" } else { "elif" },
2512 test
2513 ));
2514 first = false;
2515 }
2516 None => {
2517 // An irrefutable pattern: everything left falls here.
2518 if first {
2519 self.line("block:");
2520 } else {
2521 self.line("else:");
2522 }
2523 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2524 }
2525 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2526 self.indent += 1;
2527 self.push_scope();
2528 let before = self.out.len();
2529 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2530 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2531 self.arm_body_at(&arm.body, before)?;
2532 self.pop_scope();
2533 if closed {
2534 break;
2535 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2536 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2537 if !closed {
2538 // Rust proved this unreachable; Nim cannot see that, and leaving
2539 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2540 self.line("else:");
2541 self.line(" rsPanic(\"unreachable match arm\")");
2542 }
2543 Ok(())
2544 }
2545
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2546 /// The condition that selects this arm, or `None` if it always matches.
2547 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
2548 Ok(match p {
2549 Pat::Wild(_) => None,
2550 Pat::Ident(i) if i.subpat.is_none() => None,
2551 Pat::Or(o) => {
2552 let mut parts = Vec::new();
2553 for c in &o.cases {
2554 match self.pat_test(c, name, t)? {
2555 Some(x) => parts.push(x),
2556 None => return Ok(None),
2557 }
2558 }
2559 Some(format!("({})", parts.join(" or ")))
2560 }
2561 Pat::Lit(_) | Pat::Range(_) => {
2562 let labels = self.pat_labels(p, Some(t))?;
2563 Some(match p {
2564 Pat::Range(_) => format!("({} in {})", name, labels[0]),
2565 _ => format!("({} == {})", name, labels[0]),
2566 })
2567 }
2568 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
2569 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
2570 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
2571 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
2572 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
2573 _ => return Err("unsupported `match` pattern".into()),
2574 })
2575 }
2576
2577 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
2578 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
2579 let last = path_name(path);
2580 match last.as_str() {
2581 "Ok" => return Ok(format!("{name}.ok")),
2582 "Err" => return Ok(format!("(not {name}.ok)")),
2583 "Some" => return Ok(format!("{name}.has")),
2584 "None" => return Ok(format!("(not {name}.has)")),
2585 _ => {}
2586 }
2587 let Some((def, v)) = self.resolve_variant(path) else {
2588 return Err(format!(
2589 "`{last}` in a pattern is not a known enum variant; if it names \
2590 an enum declared in another module, that is not implemented yet"
2591 ));
2592 };
2593 if let Nim::Named(n, _) = t {
2594 if *n != def.name {
2595 return Err(format!(
2596 "pattern `{}::{}` does not match the scrutinee type `{}`",
2597 def.name, v, n
2598 ));
2599 }
2600 }
2601 Ok(if def.simple {
2602 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
2603 } else {
2604 format!("({}.kind == {})", name, def.kind_ident(&v))
2605 })
2606 }
2607
2608 /// Emit the `let`s that a pattern's bindings introduce.
2609 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
2610 match p {
2611 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
2612 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
2613 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
2614 Pat::Ident(i) if i.subpat.is_none() => {
2615 let b = i.ident.to_string();
2616 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
2617 self.bind(&b, t.clone());
2618 Ok(())
2619 }
2620 Pat::TupleStruct(ts) => {
2621 let fields = self.variant_fields(&ts.path, t)?;
2622 for (i, sub) in ts.elems.iter().enumerate() {
2623 let Some((fname, fty)) = fields.get(i) else {
2624 return Err(format!(
2625 "pattern binds {} field(s) but the variant has {}",
2626 ts.elems.len(),
2627 fields.len()
2628 ));
2629 };
2630 let access = format!("{}.{}", name, ident(fname));
2631 self.pat_bind(sub, &access, fty)?;
2632 }
2633 Ok(())
2634 }
2635 Pat::Struct(st) => {
2636 let fields = self.variant_fields(&st.path, t)?;
2637 for f in &st.fields {
2638 let syn::Member::Named(m) = &f.member else {
2639 return Err("unsupported struct pattern field".into());
2640 };
2641 let m = m.to_string();
2642 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
2643 return Err(format!("unknown field `{m}` in pattern"));
2644 };
2645 let access = format!("{}.{}", name, ident(fname));
2646 self.pat_bind(&f.pat, &access, fty)?;
2647 }
2648 Ok(())
2649 }
2650 _ => Err("unsupported `match` pattern".into()),
2651 }
2652 }
2653
2654 /// The payload fields a variant pattern destructures.
2655 fn variant_fields(
2656 &self,
2657 path: &syn::Path,
2658 t: &Nim,
2659 ) -> Result<Vec<(String, Nim)>, String> {
2660 let last = path_name(path);
2661 // `Ok`/`Err`/`Some` read the prelude's own field names.
2662 if let Nim::Named(n, a) = t {
2663 match (n.as_str(), last.as_str()) {
2664 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
2665 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
2666 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
2667 _ => {}
2668 }
2669 }
2670 let Some((def, v)) = self.resolve_variant(path) else {
2671 return Err(format!("`{last}` is not a known enum variant"));
2672 };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2673 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2674 // The variant's payload is declared in the enum's own parameters; the
2675 // scrutinee says what they are here.
2676 Ok(fields
2677 .into_iter()
2678 .map(|(n, ft)| (n, self.subst_type_args(&def.name, t, ft)))
2679 .collect())
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2680 }
2681
2682 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
2683 self.indent += 1;
2684 let before = self.out.len();
2685 self.indent -= 1;
2686 self.arm_body_at(body, before)
2687 }
2688
2689 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
2690 match body {
2691 Expr::Block(b) => self.nested_block(&b.block)?,
2692 other => {
2693 self.indent += 1;
2694 // An arm's value is the `match`'s value, so it is typed by
2695 // whatever the `match` is being assigned to -- without which
2696 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
2697 let want = self.target.clone().and_then(|(_, t)| t);
2698 let v = match (want, expressible(other)) {
2699 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
2700 _ => self.expr_stmt(other)?,
2701 };
2702 self.emit_tail(v);
2703 self.indent -= 1;
2704 }
2705 }
2706 if self.out.len() == before {
2707 self.indent += 1;
2708 self.line("discard");
2709 self.indent -= 1;
2710 }
2711 Ok(())
2712 }
2713
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2714 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
2715 match p {
2716 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
2717 Pat::Or(o) => {
2718 let mut out = Vec::new();
2719 for p in &o.cases {
2720 out.extend(self.pat_labels(p, expect)?);
2721 }
2722 Ok(out)
2723 }
2724 Pat::Range(r) => {
2725 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
2726 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
2727 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
2728 let op = match r.limits {
2729 syn::RangeLimits::HalfOpen(_) => "..<",
2730 syn::RangeLimits::Closed(_) => "..",
2731 };
2732 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
2733 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2734 Pat::Path(pp) => {
2735 if let Some((def, v)) = self.resolve_variant(&pp.path) {
2736 return Ok(vec![if def.simple {
2737 format!("{}.{}", ident(&def.name), ident(&v))
2738 } else {
2739 def.kind_ident(&v)
2740 }]);
2741 }
2742 Ok(vec![ident(&path_name(&pp.path))])
2743 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2744 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2745 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2746 .into()),
2747 }
2748 }
2749
2750 // --------------------------------------------------------- expressions
2751
2752 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
2753 self.expr_at(e, None)
2754 }
2755
2756 /// Lower `e`, with the type the surrounding code expects of it.
2757 ///
2758 /// Rust infers an unsuffixed integer literal's type from its context and
2759 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
2760 /// expected type down to the literal is what makes `let x: u8 = 255` and
2761 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
2762 /// widths silently diverge, which is exactly the class of bug this
2763 /// project refuses to ship.
2764 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
2765 match e {
2766 Expr::Lit(l) => self.lit_at(&l.lit, expect),
2767 Expr::Path(p) => {
2768 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2769 if name == "None" {
2770 return Ok(Val::new(self.none_of(expect), expect.cloned()));
2771 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2772 // `i32::MAX` and friends: an associated const on a primitive.
2773 if matches!(name.as_str(), "MAX" | "MIN") {
2774 if let Some(q) = p.path.segments.iter().rev().nth(1) {
2775 if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) {
2776 if t.is_integer() {
2777 let f = if name == "MAX" { "high" } else { "low" };
2778 return Ok(Val::new(
2779 format!("{}({})", f, t.render()),
2780 Some(t),
2781 ));
2782 }
2783 }
2784 }
2785 }
2786
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2787 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
2788 // declared here. In Nim that is a constructor call.
2789 if p.path.segments.len() > 1 {
2790 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
2791 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
2792 if n == "FmtError" {
2793 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
2794 }
2795 }
2796 }
2797 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
2798 return Ok(Val::new(
2799 format!("{}()", ident(&name)),
2800 Some(Nim::Named(name.clone(), vec![])),
2801 ));
2802 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2803 // A unit enum variant used as a value: `Error::InvalidLength`.
2804 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2805 let (ty, targs) = self.variant_type(&def, expect)?;
2806 return Ok(if def.simple && targs.is_empty() {
2807 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), Some(ty))
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2808 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2809 // A unit variant of a generic enum has no argument to
2810 // infer the parameters from, so they are written out.
2811 Val::new(
2812 format!("{}{}()", def.ctor_ident(&v), targs),
2813 Some(ty),
2814 )
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2815 });
2816 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2817 // A `for` binding that stands for an element of the container
2818 // it came from: using it must read (and assigning through it
2819 // must write) that element, not a copy.
2820 if let Some(a) = self.lookup_alias(&name) {
2821 return Ok(match a {
2822 Alias::Value { code, ty } => Val::new(code, ty),
2823 // A window *is* a slice; as a value it is the view it
2824 // denotes, which is what Rust's `&[T]` means too.
2825 Alias::Window { code, off, len, elem } => Val::new(
2826 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
2827 elem.map(|e| Nim::OpenArray(Box::new(e))),
2828 ),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago2829 // An iterator is not a value here: it is consumed by a
2830 // `for`, or asked for its `.remainder()`.
2831 Alias::Iterator(_) => {
2832 return Err(format!(
2833 "`{name}` is an iterator; it can be iterated or asked \
2834 for its `remainder()`, but not used as a value"
2835 ))
2836 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2837 });
2838 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2839 if let Some(t) = self.lookup(&name) {
2840 return Ok(Val::new(ident(&name), Some(t)));
2841 }
2842 // A top-level function used as a value, e.g. passed to a
2843 // parameter of `impl Fn(..)` type.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2844 if let Some(k) = self.resolve_fn(&p.path) {
2845 let sig = &self.fns[&k];
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2846 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 9h ago2847 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 11h ago2848 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2849 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2850 }
2851 Expr::Paren(p) => {
2852 let v = self.expr_at(&p.expr, expect)?;
2853 Ok(Val::new(format!("({})", v.code), v.ty))
2854 }
2855 Expr::Group(g) => self.expr_at(&g.expr, expect),
2856 // `&x` is a value in Nim; `&mut x` in an argument position binds to
2857 // a `var` parameter, which is also just `x` at the call site.
2858 Expr::Reference(r) => self.expr_at(&r.expr, expect),
2859 Expr::Unary(u) => self.unary(u, expect),
2860 Expr::Binary(b) => self.binary(b, expect),
2861 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2862 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
2863 let Expr::Range(r) = &*i.index else { unreachable!() };
2864 let base = self.expr(&i.expr)?;
2865 let lo = match &r.start {
2866 Some(e) => format!("int({})", self.expr(e)?.code),
2867 None => "0".into(),
2868 };
2869 // Nim's `toOpenArray` takes an inclusive upper bound.
2870 let hi = match (&r.end, r.limits) {
2871 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2872 format!("int({}) - 1", self.expr(e)?.code)
2873 }
2874 (Some(e), syn::RangeLimits::Closed(_)) => {
2875 format!("int({})", self.expr(e)?.code)
2876 }
2877 (None, _) => format!("{}.len - 1", base.code),
2878 };
2879 let elem = elem_of(&base.ty)
2880 .ok_or("cannot infer the element type of this slice")?;
2881 Ok(Val::new(
2882 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
2883 Some(Nim::OpenArray(Box::new(elem))),
2884 ))
2885 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2886 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2887 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
2888 let idx = self.expr(&i.index)?;
2889 return Ok(Val::new(
2890 format!("{}[{} + int({})]", code, off, idx.code),
2891 elem,
2892 ));
2893 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2894 let base = self.expr(&i.expr)?;
2895 let idx = self.expr(&i.index)?;
2896 // Rust indexes with usize; Nim wants an `int`, and a `uint`
2897 // index is a type error there rather than a silent conversion.
2898 let idx_code = match &idx.ty {
2899 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
2900 _ => idx.code.clone(),
2901 };
2902 let elem = match base.ty.clone() {
2903 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
2904 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
2905 _ => None,
2906 };
2907 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
2908 }
2909 Expr::Field(f) => {
2910 let base = self.expr(&f.base)?;
2911 let name = match &f.member {
2912 syn::Member::Named(n) => n.to_string(),
2913 syn::Member::Unnamed(i) => format!("f{}", i.index),
2914 };
2915 let t = match &base.ty {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2916 Some(bt @ Nim::Named(s, _)) => self
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2917 .structs
2918 .get(s)
2919 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2920 .map(|(_, t)| self.subst_type_args(s, bt, t.clone())),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2921 _ => None,
2922 };
2923 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
2924 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2925 // `unsafe` is a permission marker, not a semantic change: it does
2926 // not alter what the enclosed operations mean. So the block is
2927 // transparent here, and each operation inside still goes through
2928 // the ordinary lowering -- and is still rejected if it has no
2929 // faithful mapping.
2930 Expr::Unsafe(u) => match single_expr(&u.block) {
2931 Some(e) => self.expr_at(e, expect),
2932 None => Err("an `unsafe` block used as a value must be a single \
2933 expression"
2934 .into()),
2935 },
2936 Expr::Closure(c) => self.closure(c, expect),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2937 Expr::Try(t) => self.try_op(t),
2938 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2939 Expr::MethodCall(m) => self.method(m, expect),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago2940 Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
2941 // `vec![..]`'s elements take their type from the annotation on
2942 // the binding, exactly as Rust's would.
2943 let want = match expect {
2944 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
2945 _ => None,
2946 };
2947 let saved = std::mem::replace(&mut self.vec_expect, want.clone());
2948 let code = self.macro_call(&m.mac);
2949 self.vec_expect = saved;
2950 let code = code?;
2951 let ty = match want {
2952 Some(e) => Some(Nim::Seq(Box::new(e))),
2953 None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
2954 };
2955 Ok(Val::new(code, ty))
2956 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2957 Expr::Macro(m) => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago2958 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 11h ago2959 let code = self.macro_call(&m.mac)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago2960 // A formatter write is a statement that appends, not a value.
2961 let ty = if is_write { Some(Nim::Unit) } else { None };
2962 Ok(Val::new(code, ty))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2963 }
2964 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2965 if s.rest.is_some() {
2966 return Err("struct update syntax `..rest` is not implemented yet".into());
2967 }
2968 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
2969 // which is constructed positionally in Nim.
2970 if let Some((def, v)) = self.resolve_variant(&s.path) {
2971 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2972 let mut args = vec![String::new(); fields.len()];
2973 for f in &s.fields {
2974 let syn::Member::Named(m) = &f.member else {
2975 return Err("unsupported enum variant field".into());
2976 };
2977 let want = format!("{}_{}", v, m);
2978 let i = fields
2979 .iter()
2980 .position(|(n, _)| *n == want)
2981 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
2982 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
2983 }
2984 if let Some(i) = args.iter().position(|a| a.is_empty()) {
2985 return Err(format!(
2986 "`{}::{}` is missing field `{}`",
2987 def.name, v, fields[i].0
2988 ));
2989 }
2990 return Ok(Val::new(
2991 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
2992 Some(Nim::Named(def.name.clone(), vec![])),
2993 ));
2994 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2995 // `Self { .. }` inside an `impl` names the type being
2996 // implemented, and its fields are that type's fields.
2997 let name = match path_name(&s.path).as_str() {
2998 "Self" => self
2999 .self_ty
3000 .as_ref()
3001 .map(type_name)
3002 .ok_or("`Self` outside an `impl` block")?,
3003 other => other.to_string(),
3004 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3005 let mut parts = Vec::new();
3006 for f in &s.fields {
3007 let fname = match &f.member {
3008 syn::Member::Named(n) => n.to_string(),
3009 syn::Member::Unnamed(i) => format!("f{}", i.index),
3010 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3011 let want = self
3012 .structs
3013 .get(&name)
3014 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
3015 .map(|(_, t)| t.clone());
3016 let v = self.expr_at(&f.expr, want.as_ref())?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3017 parts.push(format!("{}: {}", ident(&fname), v.code));
3018 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3019 // Nim cannot infer an object's generic parameters from a
3020 // constructor's field values, so they are written out.
3021 let gp = self.type_generics.get(&name).cloned().unwrap_or_default();
3022 let ty = if gp.is_empty() {
3023 Nim::Named(name.clone(), vec![])
3024 } else {
3025 match expect {
3026 Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => {
3027 Nim::Named(name.clone(), a.clone())
3028 }
3029 _ => {
3030 return Err(format!(
3031 "`{name} {{ .. }}` is generic, and Nim cannot infer \
3032 its parameters from the field values; annotate the \
3033 binding or the return type"
3034 ))
3035 }
3036 }
3037 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3038 Ok(Val::new(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3039 format!("{}({})", ty.render(), parts.join(", ")),
3040 Some(ty),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3041 ))
3042 }
3043 Expr::Array(a) => {
3044 let mut parts = Vec::new();
3045 let mut elem = match expect {
3046 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
3047 Some((**t).clone())
3048 }
3049 _ => None,
3050 };
3051 for e in &a.elems {
3052 let want = elem.clone();
3053 let v = self.expr_at(e, want.as_ref())?;
3054 elem = elem.or(v.ty.clone());
3055 parts.push(v.code);
3056 }
3057 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
3058 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
3059 }
3060 Expr::Repeat(r) => {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago3061 // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size
3062 // array from a `seq`, so the expected type decides which, and
3063 // an array needs its elements written out.
3064 let want_elem = match expect {
3065 Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => {
3066 Some((**e).clone())
3067 }
3068 _ => None,
3069 };
3070 let v = self.expr_at(&r.expr, want_elem.as_ref())?;
3071 if let Some(Nim::Array(n, _)) = expect {
3072 let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect();
3073 let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t)));
3074 return Ok(Val::new(format!("[{}]", elems.join(", ")), t));
3075 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3076 let n = self.expr(&r.len)?;
3077 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
3078 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
3079 }
3080 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
3081 Expr::Tuple(t) => {
3082 let mut parts = Vec::new();
3083 let mut tys = Vec::new();
3084 for e in &t.elems {
3085 let v = self.expr(e)?;
3086 tys.push(v.ty.clone());
3087 parts.push(v.code);
3088 }
3089 let ty = tys
3090 .iter()
3091 .cloned()
3092 .collect::<Option<Vec<_>>>()
3093 .map(Nim::Tuple);
3094 Ok(Val::new(format!("({})", parts.join(", ")), ty))
3095 }
3096 // `if` and `match` are expressions in both languages, but only
3097 // when every arm is itself a single expression.
3098 Expr::If(i) => self.if_expr(i, expect),
3099 Expr::Block(b) if b.block.stmts.len() == 1 => {
3100 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
3101 self.expr_at(e, expect)
3102 } else {
3103 Err("block expression with statements in value position is not implemented yet".into())
3104 }
3105 }
3106 other => Err(format!(
3107 "unsupported expression in value position: {}",
3108 expr_kind(other)
3109 )),
3110 }
3111 }
3112
3113 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
3114 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
3115 return Err(
3116 "an `if` used as a value must have an `else` and single-expression arms".into(),
3117 );
3118 };
3119 let c = self.expr(&i.cond)?;
3120 let t = self.expr_at(then, expect)?;
3121 let want = expect.cloned().or_else(|| t.ty.clone());
3122 let e = match &**els {
3123 Expr::Block(b) => match single_expr(&b.block) {
3124 Some(x) => self.expr_at(x, want.as_ref())?,
3125 None => return Err("an `if` used as a value must have single-expression arms".into()),
3126 },
3127 other => self.expr_at(other, want.as_ref())?,
3128 };
3129 let ty = t.ty.clone().or(e.ty.clone());
3130 Ok(Val::new(
3131 format!("(if {}: {} else: {})", c.code, t.code, e.code),
3132 ty,
3133 ))
3134 }
3135
3136 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
3137 match l {
3138 Lit::Int(i) => {
3139 let suffix = i.suffix();
3140 if let Some(why) = ty::rejected(suffix) {
3141 return Err(format!("integer literal `{}`: {}", i, why));
3142 }
3143 let digits = i.base10_digits().to_string();
3144 // Rust's default for an unconstrained integer literal is i32.
3145 // Nim's is `int` (64-bit). Making the width explicit is what
3146 // keeps overflow behaviour the same on both sides.
3147 let t = if suffix.is_empty() {
3148 match expect {
3149 Some(t) if t.is_integer() => t.clone(),
3150 // Rust's fallback for an otherwise-unconstrained
3151 // integer literal.
3152 _ => Nim::Prim("int32".into()),
3153 }
3154 } else {
3155 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
3156 };
3157 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
3158 }
3159 Lit::Float(f) => {
3160 let t = match f.suffix() {
3161 "" => match expect {
3162 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
3163 _ => Nim::Prim("float64".into()),
3164 },
3165 "f64" => Nim::Prim("float64".into()),
3166 "f32" => Nim::Prim("float32".into()),
3167 s => return Err(format!("unknown float suffix `{s}`")),
3168 };
3169 let d = f.base10_digits();
3170 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
3171 Ok(Val::new(d, Some(t)))
3172 }
3173 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
3174 Lit::Str(s) => Ok(Val::new(
3175 fmt::nim_str(&s.value()),
3176 Some(Nim::Prim("string".into())),
3177 )),
3178 Lit::Char(c) => Ok(Val::new(
3179 format!("Rune({})", c.value() as u32),
3180 Some(Nim::Prim("Rune".into())),
3181 )),
3182 Lit::Byte(b) => Ok(Val::new(
3183 format!("{}'u8", b.value()),
3184 Some(Nim::Prim("uint8".into())),
3185 )),
3186 Lit::ByteStr(b) => {
3187 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
3188 Ok(Val::new(
3189 format!("@[{}]", bytes.join(", ")),
3190 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
3191 ))
3192 }
3193 other => Err(format!("unsupported literal: {other:?}")),
3194 }
3195 }
3196
3197 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
3198 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
3199 // the positive half of the range before the negation runs. Folding the
3200 // sign into the literal keeps `i8::MIN` and friends expressible.
3201 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
3202 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
3203 let v = self.lit_at(&l.lit, expect)?;
3204 return Ok(Val::new(format!("-{}", v.code), v.ty));
3205 }
3206 }
3207 let v = self.expr_at(&u.expr, expect)?;
3208 match u.op {
3209 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
3210 // Rust's `!` is logical on bool and bitwise-complement on integers.
3211 // Nim spells those `not` and `not` as well, so one mapping covers
3212 // both — but only because Nim overloads `not` the same way.
3213 UnOp::Not(_) => Ok(Val::new(format!("(not {})", v.code), v.ty)),
3214 UnOp::Deref(_) => Ok(v),
3215 _ => Err("unsupported unary operator".into()),
3216 }
3217 }
3218
3219 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
3220 // A comparison's operands are unrelated to the `bool` it produces, so
3221 // the outer expectation is not passed through to them.
3222 let down = match b.op {
3223 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3224 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
3225 _ => expect,
3226 };
3227 let mut l = self.expr_at(&b.left, down)?;
3228 // Rust unifies the two operand types; propagating whichever side is
3229 // known to the other reproduces that, and disagreement then surfaces
3230 // as a Nim type error rather than as a silent width change.
3231 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
3232 if l.ty.is_none() && r.ty.is_some() {
3233 l = self.expr_at(&b.left, r.ty.as_ref())?;
3234 }
3235 let r = std::mem::replace(&mut r, Val::untyped(""));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago3236 // A binary operator on a user type goes to that type's own impl.
3237 if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) {
3238 let want = self.op_param(&l.ty, binary_symbol(&b.op));
3239 let r = self.expr_at(&b.right, want.as_ref())?;
3240 let ret = self
3241 .methods
3242 .get(&(
3243 type_name(l.ty.as_ref().unwrap()),
3244 op_method(binary_symbol(&b.op)).to_string(),
3245 ))
3246 .map(|s| s.ret.clone());
3247 return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret));
3248 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3249 let op = self.bin_op(&b.op, &l, &r)?;
3250 let ty = match b.op {
3251 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3252 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
3253 // Rust's shift takes its result type from the *left* operand, and
3254 // the right may be a different width entirely.
3255 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
3256 _ => l.ty.clone().or(r.ty.clone()),
3257 };
3258 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
3259 }
3260
3261 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
3262 Ok(match op {
3263 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
3264 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
3265 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
3266 BinOp::Div(_) | BinOp::DivAssign(_) => {
3267 // Nim spells integer division `div`. Both languages truncate
3268 // toward zero, so once the right operator is chosen the
3269 // semantics match, including for negative operands.
3270 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3271 "cannot tell integer from float division here; annotate the operands",
3272 )?;
3273 if t.is_integer() { "div" } else { "/" }
3274 }
3275 BinOp::Rem(_) | BinOp::RemAssign(_) => {
3276 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3277 "cannot tell integer from float remainder here; annotate the operands",
3278 )?;
3279 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
3280 }
3281 BinOp::And(_) => "and",
3282 BinOp::Or(_) => "or",
3283 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
3284 // bools, exactly as Rust's `&`/`|`/`^` are.
3285 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
3286 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
3287 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
3288 // Settled empirically: Nim's `shr` on a signed integer is
3289 // arithmetic, matching Rust. See DESIGN.md.
3290 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
3291 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
3292 BinOp::Eq(_) => "==",
3293 BinOp::Ne(_) => "!=",
3294 BinOp::Lt(_) => "<",
3295 BinOp::Le(_) => "<=",
3296 BinOp::Gt(_) => ">",
3297 BinOp::Ge(_) => ">=",
3298 other => return Err(format!("unsupported binary operator {other:?}")),
3299 })
3300 }
3301
3302 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
3303 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3304 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3305 let from = v.ty.clone().ok_or_else(|| {
3306 format!(
3307 "cannot lower `as {}`: the source type is unknown, and `as` \
3308 truncates, so the source width decides the result",
3309 to.render()
3310 )
3311 })?;
3312
3313 let code = match (&from, &to) {
3314 (f, t) if f.is_integer() && t.is_integer() => {
3315 // Rust's `as` between integers is a pure bit-width truncation
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 9h ago3316 // or sign-extension, never a range check. `cast` says exactly
3317 // that. (Nim's `T(x)` turns out to truncate here as well --
3318 // see DESIGN.md item 5 -- but `cast` is the spelling that
3319 // means it rather than the one that happens to agree.)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3320 format!("cast[{}]({})", t.render(), v.code)
3321 }
3322 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
3323 format!("{}({})", p, v.code)
3324 }
3325 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
3326 format!("{}(ord({}))", t.render(), v.code)
3327 }
3328 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
3329 format!("cast[{}](int32({}))", t.render(), v.code)
3330 }
3331 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
3332 format!("Rune(int32({}))", v.code)
3333 }
3334 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
3335 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
3336 // Rust saturates float->int casts; Nim rounds and range-errors.
3337 // Not the same operation, so it is refused rather than mapped.
3338 return Err(format!(
3339 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
3340 no faithful mapping is implemented",
3341 t.render()
3342 ));
3343 }
3344 (f, t) => {
3345 return Err(format!(
3346 "unsupported cast from `{}` to `{}`",
3347 f.render(),
3348 t.render()
3349 ))
3350 }
3351 };
3352 Ok(Val::new(code, Some(to)))
3353 }
3354
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3355 /// Rust's `?`: return early on the error branch, otherwise yield the value.
3356 ///
3357 /// The early return is statements, not an expression, so they are emitted
3358 /// ahead of the line being built. Every caller lowers its sub-expressions
3359 /// 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 9h ago3360 /// The container, start offset, length and element type an expression
3361 /// denotes as a slice. A window alias contributes its own offset, so
3362 /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
3363 /// into the original buffer rather than through a rebuilt view.
3364 fn slice_parts(
3365 &mut self,
3366 e: &Expr,
3367 ) -> Result<(String, String, String, Option<Nim>), String> {
3368 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
3369 return Ok((code, off, len, elem));
3370 }
3371 let v = self.expr(e)?;
3372 let len = format!("{}.len", v.code);
3373 Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
3374 }
3375
3376 /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
3377 fn map_closure(
3378 &mut self,
3379 what: &str,
3380 recv: &Val,
3381 kind: &str,
3382 targs: &[Nim],
3383 c: &syn::ExprClosure,
3384 ) -> Result<Val, String> {
3385 if c.capture.is_some() {
3386 return Err("a `move` closure captures by value; Nim's closures \
3387 capture by reference, and the two are not the same"
3388 .into());
3389 }
3390 if c.inputs.len() != 1 {
3391 return Err(format!("`.{what}()` takes a one-argument closure"));
3392 }
3393 let pname = match &c.inputs[0] {
3394 Pat::Ident(i) => i.ident.to_string(),
3395 Pat::Wild(_) => "unused0".into(),
3396 _ => return Err("only plain identifier closure parameters are supported".into()),
3397 };
3398
3399 let is_opt = kind == "Option";
3400 let tmp = self.fresh("Map");
3401 let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
3402 self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
3403
3404 let body = match &*c.body {
3405 Expr::Block(b) => single_expr(&b.block)
3406 .ok_or("a closure body with statements is not implemented yet")?,
3407 other => other,
3408 };
3409 self.push_scope();
3410 // The parameter names the payload itself, so a view stays a view.
3411 self.bind_alias(
3412 &pname,
3413 Alias::Value {
3414 code: format!("{}.val", tmp),
3415 ty: Some(targs[0].clone()),
3416 },
3417 );
3418 let v = self.expr(body)?;
3419 self.pop_scope();
3420
3421 let inner = v
3422 .ty
3423 .clone()
3424 .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
3425 // `and_then`'s closure already returns the wrapped type; `map`'s does
3426 // not and has to be re-wrapped.
3427 let (test, some_branch, none_branch, out_ty) = if is_opt {
3428 let out = if what == "map" {
3429 Nim::Named("Option".into(), vec![inner.clone()])
3430 } else {
3431 inner.clone()
3432 };
3433 let body_code = if what == "map" {
3434 format!("rsSome[{}]({})", inner.render(), v.code)
3435 } else {
3436 v.code.clone()
3437 };
3438 (
3439 format!("{}.has", tmp),
3440 body_code,
3441 format!("rsNone[{}]()", elem_arg(&out).render()),
3442 out,
3443 )
3444 } else {
3445 let e = targs[1].clone();
3446 let out = if what == "map" {
3447 Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
3448 } else {
3449 inner.clone()
3450 };
3451 let ok_ty = elem_arg(&out);
3452 let body_code = if what == "map" {
3453 format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
3454 } else {
3455 v.code.clone()
3456 };
3457 (
3458 format!("{}.ok", tmp),
3459 body_code,
3460 format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
3461 out,
3462 )
3463 };
3464 Ok(Val::new(
3465 format!("(if {}: {} else: {})", test, some_branch, none_branch),
3466 Some(out_ty),
3467 ))
3468 }
3469
3470 /// `|x| x + 1` -> a Nim anonymous proc.
3471 ///
3472 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
3473 /// A `move` closure captures by value, which is a different thing, so it
3474 /// is rejected rather than lowered to the same construct.
3475 fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
3476 if c.capture.is_some() {
3477 return Err("a `move` closure captures by value; Nim's closures \
3478 capture by reference, and the two are not the same"
3479 .into());
3480 }
3481 let want: Option<&Vec<Nim>> = match expect {
3482 Some(Nim::Proc(a, _)) => Some(a),
3483 _ => None,
3484 };
3485
3486 self.push_scope();
3487 let mut parts = Vec::new();
3488 let mut ptys = Vec::new();
3489 for (i, p) in c.inputs.iter().enumerate() {
3490 let (name, ann) = match p {
3491 Pat::Ident(id) => (id.ident.to_string(), None),
3492 Pat::Type(t) => match &*t.pat {
3493 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
3494 _ => return Err("only plain identifier closure parameters are supported".into()),
3495 },
3496 Pat::Wild(_) => (format!("unused{i}"), None),
3497 _ => return Err("only plain identifier closure parameters are supported".into()),
3498 };
3499 let t = ann
3500 .or_else(|| want.and_then(|w| w.get(i).cloned()))
3501 .ok_or_else(|| {
3502 format!(
3503 "cannot infer the type of closure parameter `{name}`; \
3504 annotate it"
3505 )
3506 })?;
3507 parts.push(format!("{}: {}", ident(&name), t.render()));
3508 self.bind(&name, t.clone());
3509 ptys.push(t);
3510 }
3511
3512 let ret_ann = match &c.output {
3513 ReturnType::Default => None,
3514 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
3515 };
3516 let body = match &*c.body {
3517 Expr::Block(b) => single_expr(&b.block)
3518 .ok_or("a closure body with statements is not implemented yet")?,
3519 other => other,
3520 };
3521 let v = self.expr_at(body, ret_ann.as_ref())?;
3522 self.pop_scope();
3523
3524 let ret = ret_ann
3525 .or_else(|| v.ty.clone())
3526 .ok_or("cannot infer a closure's return type; annotate it")?;
3527 Ok(Val::new(
3528 format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
3529 Some(Nim::Proc(ptys, Box::new(ret))),
3530 ))
3531 }
3532
3533 /// Lower a block's statements at the current indentation, without opening
3534 /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
3535 /// of its own in the generated code.
3536 fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
3537 self.push_scope();
3538 let tail = self.block_body(b)?;
3539 self.emit_tail(tail);
3540 self.pop_scope();
3541 Ok(())
3542 }
3543
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3544 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
3545 if self.in_loop_cond {
3546 return Err("`?` in a loop condition is not implemented yet: the \
3547 early-return it expands to would be evaluated once, \
3548 before the loop, rather than on each iteration"
3549 .into());
3550 }
3551 let v = self.expr(&t.expr)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago3552 if self.fmt_param.is_some() {
3553 // Writing into a string cannot fail, so `?` on a formatter write
3554 // is a no-op. `?` on anything else can fail, and `format!` panics
3555 // when a formatting impl returns an error -- so that is what the
3556 // error branch does here, with std's own message.
3557 if v.ty.as_ref() == Some(&Nim::Unit) {
3558 return Ok(v);
3559 }
3560 if let Some(Nim::Named(n, a)) = v.ty.clone() {
3561 if n == "Result" && a.len() == 2 {
3562 let tmp = self.fresh("Fmt");
3563 self.line(&format!(
3564 "let {}: {} = {}",
3565 tmp,
3566 Nim::Named(n, a.clone()).render(),
3567 v.code
3568 ));
3569 self.line(&format!("if not {}.ok:", tmp));
3570 self.line(
3571 " rsPanic(\"a formatting trait implementation returned an error\")",
3572 );
3573 return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone())));
3574 }
3575 }
3576 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3577 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
3578 // An `Option`/`Result` of a view: the check is emitted here and the
3579 // view itself survives as an alias, since it has no value form.
3580 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
3581 let err = v.guard_err.clone().ok_or(
3582 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
3583 )?;
3584 let Nim::Named(n, ra) = &ret else {
3585 return Err(format!("`?` in a function returning `{}`", ret.render()));
3586 };
3587 if n != "Result" || ra.len() != 2 {
3588 return Err(format!("`?` in a function returning `{}`", ret.render()));
3589 }
3590 self.line(&format!("if not {}:", guard));
3591 self.line(&format!(
3592 " return rsErr[{}, {}]({})",
3593 ra[0].render(),
3594 ra[1].render(),
3595 err
3596 ));
3597 let mut out = Val::new(String::new(), None);
3598 out.window = Some(w);
3599 return Ok(out);
3600 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3601 let vt = v.ty.clone().ok_or(
3602 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
3603 )?;
3604 let ret = self
3605 .ret
3606 .clone()
3607 .ok_or("`?` outside a function with a return type")?;
3608 let tmp = self.fresh("Try");
3609 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
3610
3611 match (&vt, &ret) {
3612 (Nim::Named(a, ai), Nim::Named(b, bi))
3613 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
3614 {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3615 // Rust inserts a `From::from` on the error here. Where the
3616 // types differ we call the crate's own `impl From`; we never
3617 // assume the conversion is the identity.
3618 let err = if ai[1] == bi[1] {
3619 format!("{}.err", tmp)
3620 } else {
3621 let key = (type_name(&ai[1]), type_name(&bi[1]));
3622 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
3623 format!(
3624 "`?` needs `From<{}> for {}` to convert the error, and \
3625 no such `impl` is in scope; assuming the conversion is \
3626 the identity would be a guess",
3627 key.0, key.1
3628 )
3629 })?;
3630 format!("{}({}.err)", f, tmp)
3631 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3632 self.line(&format!("if not {}.ok:", tmp));
3633 self.line(&format!(
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3634 " return rsErr[{}, {}]({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3635 bi[0].render(),
3636 bi[1].render(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3637 err
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3638 ));
3639 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3640 }
3641 (Nim::Named(a, ai), Nim::Named(b, bi))
3642 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
3643 {
3644 self.line(&format!("if not {}.has:", tmp));
3645 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
3646 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3647 }
3648 _ => Err(format!(
3649 "`?` on `{}` in a function returning `{}` is not a supported \
3650 combination",
3651 vt.render(),
3652 ret.render()
3653 )),
3654 }
3655 }
3656
3657 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 11h ago3658 let Expr::Path(p) = &*c.func else {
3659 return Err("only calls to named functions are supported".into());
3660 };
3661 let name = path_name(&p.path);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3662 let target = self.resolve_fn(&p.path);
3663 let ptys: Vec<Nim> = target
3664 .as_ref()
3665 .and_then(|k| self.fns.get(k))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3666 .map(|s| s.params.clone())
3667 .unwrap_or_default();
3668 let mut args = Vec::new();
3669 for (i, a) in c.args.iter().enumerate() {
3670 let want = ptys.get(i).cloned();
3671 args.push(self.expr_at(a, want.as_ref())?);
3672 }
3673 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
3674
3675 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3676 // `Ok`/`Err` must name the *whole* Result type, not just the half
3677 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
3678 match name.as_str() {
3679 "Some" => {
3680 let inner = match expect {
3681 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
3682 _ => {
3683 return Err("`Some(..)` needs a known `Option<T>` type here; \
3684 annotate the binding or the return type"
3685 .into())
3686 }
3687 };
3688 return Ok(Val::new(
3689 format!("rsSome[{}]({})", inner, codes.join(", ")),
3690 expect.cloned(),
3691 ));
3692 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago3693 "Ok" if self.fmt_param.is_some()
3694 && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) =>
3695 {
3696 // `Ok(())` ends a `fmt` body: nothing more is written.
3697 return Ok(Val::new(String::new(), Some(Nim::Unit)));
3698 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3699 "Ok" | "Err" => {
3700 let (t, e) = match expect {
3701 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
3702 (a[0].render(), a[1].render())
3703 }
3704 _ => {
3705 return Err(format!(
3706 "`{name}(..)` needs a known `Result<T, E>` type here; \
3707 annotate the binding or the return type"
3708 ))
3709 }
3710 };
3711 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
3712 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
3713 return Ok(Val::new(
3714 format!("{}[{}, {}]({})", ctor, t, e, arg),
3715 expect.cloned(),
3716 ));
3717 }
3718 _ => {}
3719 }
3720
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago3721 // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's
3722 // object constructor names its fields even when Rust's does not.
3723 if let Some(fields) = self.structs.get(&name).cloned() {
3724 if fields.len() == c.args.len() {
3725 let mut parts = Vec::new();
3726 for (i, a) in c.args.iter().enumerate() {
3727 let v = self.expr_at(a, Some(&fields[i].1))?;
3728 parts.push(format!("{}: {}", ident(&fields[i].0), v.code));
3729 }
3730 return Ok(Val::new(
3731 format!("{}({})", ident(&name), parts.join(", ")),
3732 Some(Nim::Named(name.clone(), vec![])),
3733 ));
3734 }
3735 }
3736
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3737 // `Spacing::from(d)`: a `From` impl called through its target type.
3738 // Rust picks the impl by the argument's type, and so do we -- Nim
3739 // cannot overload on return type, so each impl has its own proc name.
3740 if name == "from" && codes.len() == 1 {
3741 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3742 let q = if q == "Self" {
3743 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3744 } else {
3745 q
3746 };
3747 if let Some(src) = args[0].ty.as_ref().map(type_name) {
3748 if let Some(f) = self.from_impls.get(&(src, q.clone())).cloned() {
3749 return Ok(Val::new(
3750 format!("{}({})", f, codes[0]),
3751 Some(Nim::Named(q, vec![])),
3752 ));
3753 }
3754 }
3755 }
3756 }
3757
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago3758 // `u32::from(b)`: `From` between primitives is lossless by definition
3759 // -- it is the widening direction only -- so a plain Nim conversion is
3760 // exact. (The truncating direction is `as`, which is `cast`.)
3761 if name == "from" && codes.len() == 1 {
3762 if let Some(q) = p.path.segments.iter().rev().nth(1) {
3763 if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) {
3764 return Ok(Val::new(
3765 format!("{}({})", t, codes[0]),
3766 Some(Nim::Prim(t)),
3767 ));
3768 }
3769 }
3770 }
3771
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3772 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
3773 // string view; no copy, no validation, same memory.
3774 if name == "from_utf8_unchecked" && codes.len() == 1 {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago3775 // `String::from_utf8_unchecked(v)` takes ownership and yields an
3776 // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields
3777 // a view. Same name, different operations -- the qualifier says
3778 // which, and an unqualified call is ambiguous.
3779 let q = p
3780 .path
3781 .segments
3782 .iter()
3783 .rev()
3784 .nth(1)
3785 .map(|s| s.ident.to_string());
3786 return match q.as_deref() {
3787 Some("String") => Ok(Val::new(
3788 format!("rsStringOf({})", codes[0]),
3789 Some(Nim::Prim("string".into())),
3790 )),
3791 Some("str") => Ok(Val::new(
3792 format!("rsStrView({})", codes[0]),
3793 Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
3794 )),
3795 _ => Err(
3796 "`from_utf8_unchecked` must be written as `str::..` (a \
3797 borrowed view) or `String::..` (an owned string); the two \
3798 are different operations"
3799 .into(),
3800 ),
3801 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3802 }
3803
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3804 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
3805 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3806 let (ty, _) = self.variant_type(&def, expect)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3807 return Ok(Val::new(
3808 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3809 Some(ty),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3810 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3811 }
3812
3813 // A bare path that names a primitive type is Rust's tuple-struct-like
3814 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3815 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
3816 // is invoked.
3817 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
3818 return Ok(Val::new(
3819 format!("{}({})", ident(&name), codes.join(", ")),
3820 Some((*ret).clone()),
3821 ));
3822 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago3823 // `Adler32::new()` / `Adler32::default()`: a method called through
3824 // its type rather than through a receiver.
3825 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3826 // `Self::new()` inside an `impl` names the type being implemented.
3827 let q = if q == "Self" {
3828 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3829 } else {
3830 q
3831 };
3832 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3833 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
3834 let ret = Self::instantiate(sig, &arg_tys);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago3835 let nim = self
3836 .statics
3837 .get(&(q.clone(), name.clone()))
3838 .cloned()
3839 .unwrap_or_else(|| ident(&name));
3840 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
3841 }
3842 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3843 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
3844 let ret = target
3845 .as_ref()
3846 .and_then(|k| self.fns.get(k))
3847 .map(|sig| Self::instantiate(sig, &arg_tys));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3848 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 11h ago3849 return Err(format!(
3850 "call to unknown function `{name}`; only functions defined in \
3851 this file and the supported standard-library subset can be lowered"
3852 ));
3853 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3854 let nim = match &target {
3855 Some((m, n)) => self.fn_name(m, n),
3856 None => ident(&name),
3857 };
3858 Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3859 }
3860
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3861 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 11h ago3862 let name = m.method.to_string();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago3863 // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield.
3864 if name == "remainder" && m.args.is_empty() {
3865 if let Expr::Path(p) = &*m.receiver {
3866 if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) {
3867 if let Iter::Chunks { code, base, len, k, elem, .. } = &*it {
3868 let kept = format!("(({} div int({})) * int({}))", len, k, k);
3869 let mut v = Val::new(
3870 String::new(),
3871 elem.clone().map(|e| Nim::OpenArray(Box::new(e))),
3872 );
3873 v.window = Some(Alias::Window {
3874 code: code.clone(),
3875 off: format!("({} + {})", base, kept),
3876 len: format!("({} - {})", len, kept),
3877 elem: elem.clone(),
3878 });
3879 return Ok(v);
3880 }
3881 return Err(
3882 "`.remainder()` is only defined for a `chunks_exact` iterator".into(),
3883 );
3884 }
3885 }
3886 return Err("`.remainder()` needs an iterator bound by `let`".into());
3887 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3888 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
3889 match name.as_str() {
3890 "len" => {
3891 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
3892 }
3893 "is_empty" => {
3894 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
3895 }
3896 other => {
3897 return Err(format!(
3898 "`.{other}()` on a slice window from `chunks_exact`/\
3899 `windows` is not implemented; only indexing and \
3900 `len()` are"
3901 ))
3902 }
3903 }
3904 }
3905 let recv = self.expr(&m.receiver)?;
3906 let rt0 = recv.ty.clone();
3907
3908// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
3909 // way to put a view in an object, so instead of materialising an
3910 // Option the view and its validity condition travel together until
3911 // an `ok_or`/`?`/`unwrap` resolves them.
3912 if matches!(name.as_str(), "get" | "get_mut")
3913 && matches!(m.args.first(), Some(Expr::Range(_)))
3914 {
3915 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 9h ago3916 let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3917 let lo = match &r.start {
3918 Some(e) => format!("int({})", self.expr(e)?.code),
3919 None => "0".into(),
3920 };
3921 let len = match (&r.end, r.limits) {
3922 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
3923 format!("(int({}) - {})", self.expr(e)?.code, lo)
3924 }
3925 (Some(e), syn::RangeLimits::Closed(_)) => {
3926 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
3927 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3928 (None, _) => format!("({} - {})", blen, lo),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3929 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3930 // Hoisted, so the bounds are computed once -- as Rust computes
3931 // them once -- and cannot be re-evaluated later in a scope where
3932 // the names they mention have been shadowed by a loop pattern.
3933 let off_t = self.fresh("Off");
3934 let len_t = self.fresh("Len");
3935 self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
3936 self.line(&format!("let {}: int = {}", len_t, len));
3937 let elem = belem
3938 .or_else(|| elem_of(&rt0))
3939 .ok_or("cannot infer the element type of this slice")?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3940 let mut v = Val::new(
3941 String::new(),
3942 Some(Nim::Named(
3943 "Option".into(),
3944 vec![Nim::OpenArray(Box::new(elem.clone()))],
3945 )),
3946 );
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3947 v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3948 v.window = Some(Alias::Window {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3949 code,
3950 off: off_t,
3951 len: len_t,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3952 elem: Some(elem),
3953 });
3954 return Ok(v);
3955 }
3956
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago3957 // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
3958 // parameter type comes from the receiver, so they are handled before
3959 // the arguments are lowered. The closure is expanded inline, with its
3960 // parameter aliased to the payload: that keeps the whole thing an
3961 // expression and avoids handing a view to a generic proc.
3962 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
3963 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
3964 (recv.ty.clone(), &m.args[0])
3965 {
3966 if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
3967 {
3968 return self.map_closure(&name, &recv, &kind, &targs, c);
3969 }
3970 }
3971 }
3972
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3973 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
3974 // own type; `v.push(e)` takes the element type.
3975 let arg_want = match (name.as_str(), &recv.ty) {
3976 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
3977 (_, t) => t.clone(),
3978 };
3979 let mut args = Vec::new();
3980 for a in &m.args {
3981 args.push(self.expr_at(a, arg_want.as_ref())?);
3982 }
3983 let a0 = args.first().map(|a| a.code.clone());
3984 let rt = recv.ty.clone();
3985
3986 let (code, ty) = match name.as_str() {
3987 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
3988 // explicit so that a `usize` binding type-checks on the Nim side.
3989 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
3990 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
3991 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
3992 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
3993 | "into_iter" => (recv.code.clone(), rt.clone()),
3994 "unwrap" | "expect" => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago3995 // Expanded inline rather than called as a generic proc: when
3996 // the payload is a view, Nim can only borrow from a path
3997 // expression, which a proc body containing the panic is not.
3998 let (kind, inner) = match &rt {
3999 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
4000 ("Option", a[0].clone())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4001 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4002 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4003 ("Result", a[0].clone())
4004 }
4005 _ => {
4006 return Err(format!(
4007 "`.{name}()` needs a known `Option`/`Result` receiver type"
4008 ))
4009 }
4010 };
4011 if self.in_loop_cond {
4012 return Err(format!(
4013 "`.{name}()` in a loop condition is not implemented yet: the \
4014 check it expands to would run once, before the loop"
4015 ));
4016 }
4017 let tmp = self.fresh("Unwrap");
4018 let rty = rt.clone().unwrap();
4019 self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code));
4020 let (test, msg) = if kind == "Option" {
4021 (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value")
4022 } else {
4023 (format!("{}.ok", tmp), "called `Result::unwrap()` on an `Err` value")
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4024 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4025 let msg = if name == "expect" {
4026 args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg))
4027 } else {
4028 fmt::nim_str(msg)
4029 };
4030 self.line(&format!("if not {}:", test));
4031 self.line(&format!(" rsPanic({})", msg));
4032 // If the payload is a view, hand back an alias rather than a
4033 // value: Nim will not let a `let` borrow out of a local, and a
4034 // view is a reference anyway, so there is nothing to bind.
4035 // `{tmp}.val` is a plain field access, so substituting it at
4036 // each use re-evaluates nothing.
4037 if matches!(inner, Nim::OpenArray(_)) {
4038 let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone()));
4039 v.window = Some(Alias::Value {
4040 code: format!("{}.val", tmp),
4041 ty: Some(inner),
4042 });
4043 return Ok(v);
4044 }
4045 (format!("{}.val", tmp), Some(inner))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4046 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4047 "ok_or" if recv.guard.is_some() => {
4048 let e = args.first().ok_or("`ok_or` takes one argument")?;
4049 let ety = e.ty.clone();
4050 let mut v = recv.clone();
4051 v.guard_err = Some(e.code.clone());
4052 v.ty = match (&recv.ty, ety) {
4053 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
4054 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
4055 }
4056 _ => None,
4057 };
4058 return Ok(v);
4059 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4060 "ok_or" => {
4061 let inner = match &rt {
4062 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
4063 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
4064 };
4065 let e = args.first().ok_or("`ok_or` takes one argument")?;
4066 let ety = e
4067 .ty
4068 .clone()
4069 .ok_or("`ok_or` needs a known error type for its argument")?;
4070 (
4071 format!(
4072 "rsOkOr[{}, {}]({}, {})",
4073 inner.render(),
4074 ety.render(),
4075 recv.code,
4076 e.code
4077 ),
4078 Some(Nim::Named("Result".into(), vec![inner, ety])),
4079 )
4080 }
4081 "unwrap_or" => {
4082 let inner = match &rt {
4083 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
4084 Some(a[0].clone())
4085 }
4086 _ => None,
4087 };
4088 (
4089 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
4090 inner,
4091 )
4092 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4093 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
4094 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
4095 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
4096 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
4097
4098 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
4099 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
4100 // Nim raises OverflowDefect, so the operation is routed through
4101 // the unsigned view of the same width, which is what Rust's
4102 // wrapping_* is defined to compute.
4103 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
4104 let op = match name.as_str() {
4105 "wrapping_add" => "+",
4106 "wrapping_sub" => "-",
4107 _ => "*",
4108 };
4109 let t = rt.clone().ok_or_else(|| {
4110 format!("`{name}` needs a known receiver type to pick the wrapping width")
4111 })?;
4112 if !t.is_integer() {
4113 return Err(format!("`{name}` on a non-integer type"));
4114 }
4115 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
4116 if t.is_unsigned() {
4117 (format!("({} {} {})", recv.code, op, arg), Some(t))
4118 } else {
4119 let u = unsigned_peer(&t)?;
4120 (
4121 format!(
4122 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
4123 t.render(), u, recv.code, op, u, arg
4124 ),
4125 Some(t),
4126 )
4127 }
4128 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4129 // Inside a formatting impl, a write through the `Formatter` *is*
4130 // 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 9h ago4131 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => {
4132 let a = args.first().ok_or("`write_str` takes one argument")?;
4133 // A `&str` argument is a character view, not a Nim string.
4134 let text = match &a.ty {
4135 Some(Nim::Prim(p)) if p == "string" => a.code.clone(),
4136 _ => format!("rsDisplay({})", a.code),
4137 };
4138 (format!("result.add({})", text), Some(Nim::Unit))
4139 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4140 "saturating_add" | "saturating_sub" | "saturating_mul" | "checked_add"
4141 | "checked_sub" | "checked_mul" => {
4142 let t = rt
4143 .clone()
4144 .filter(|t| t.is_integer())
4145 .ok_or_else(|| format!("`{name}` needs a known integer receiver"))?;
4146 let arg = args
4147 .first()
4148 .ok_or_else(|| format!("`{name}` takes one argument"))?;
4149 let f = match name.as_str() {
4150 "saturating_add" => "rsSatAdd",
4151 "saturating_sub" => "rsSatSub",
4152 "saturating_mul" => "rsSatMul",
4153 "checked_add" => "rsChkAdd",
4154 "checked_sub" => "rsChkSub",
4155 _ => "rsChkMul",
4156 };
4157 let out = if name.starts_with("checked") {
4158 Nim::Named("Option".into(), vec![t])
4159 } else {
4160 t
4161 };
4162 (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
4163 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4164 "abs" => (format!("abs({})", recv.code), rt.clone()),
4165 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4166 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4167 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
4168 "as_bytes" | "into_bytes" => (
4169 format!("rsBytes({})", recv.code),
4170 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
4171 ),
4172
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4173 "into" => {
4174 // `.into()` resolves through the `impl From` declarations, and
4175 // needs the target type to pick one.
4176 let from = rt
4177 .clone()
4178 .ok_or("`.into()` needs a known receiver type")?;
4179 let to = expect
4180 .ok_or("`.into()` needs a known target type; annotate the binding")?;
4181 let key = (type_name(&from), type_name(to));
4182 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4183 format!(
4184 "no `impl From<{}> for {}` in this file, so `.into()` has \
4185 no conversion to call",
4186 key.0, key.1
4187 )
4188 })?;
4189 (format!("{}({})", f, recv.code), Some(to.clone()))
4190 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4191 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4192 // A method defined in this file via `impl`, found by the
4193 // receiver's type rather than by name alone.
4194 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4195 let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
4196 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago4197 let sig = key
4198 .as_ref()
4199 .and_then(|k| self.methods.get(k))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4200 .map(|s| Self::instantiate(s, &arg_tys));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4201 if let Some(ret) = sig {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago4202 // Use the name the proc was actually emitted under: an
4203 // inherent method is qualified by its module, a trait
4204 // method by its trait.
4205 let nim = key
4206 .and_then(|k| self.statics.get(&k).cloned())
4207 .unwrap_or_else(|| ident(&name));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4208 let mut all = vec![recv.code.clone()];
4209 all.extend(args.iter().map(|a| a.code.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago4210 (format!("{}({})", nim, all.join(", ")), Some(ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4211 } else {
4212 return Err(format!(
4213 "unsupported method `.{name}()`; it is neither defined in \
4214 this file nor part of the standard-library subset that \
4215 has a verified Nim equivalent"
4216 ));
4217 }
4218 }
4219 };
4220 Ok(Val::new(code, ty))
4221 }
4222
4223 // -------------------------------------------------------------- macros
4224
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago4225 /// The element type of a `vec![..]`, from its first element.
4226 fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
4227 let body = mac.tokens.to_string();
4228 if body.trim().is_empty() {
4229 return Ok(None);
4230 }
4231 let first: Option<Expr> = if body.contains(';') {
4232 // The whole body must be consumed or the parse fails, so the
4233 // length is parsed too even though only the element is wanted.
4234 mac.parse_body_with(|input: syn::parse::ParseStream| {
4235 let v: Expr = input.parse()?;
4236 input.parse::<syn::Token![;]>()?;
4237 let _len: Expr = input.parse()?;
4238 Ok(v)
4239 })
4240 .ok()
4241 } else {
4242 mac.parse_body_with(
4243 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4244 )
4245 .ok()
4246 .and_then(|p| p.into_iter().next())
4247 };
4248 match first {
4249 Some(e) => Ok(self.expr(&e)?.ty),
4250 None => Ok(None),
4251 }
4252 }
4253
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4254 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
4255 let name = path_name(&mac.path);
4256 match name.as_str() {
4257 "println" | "print" | "eprintln" | "eprint" => {
4258 let s = self.format_args(mac)?;
4259 let nl = name.ends_with("ln");
4260 Ok(match (name.starts_with('e'), nl) {
4261 (false, true) => format!("echo {s}"),
4262 (false, false) => format!("stdout.write({s})"),
4263 (true, true) => format!("stderr.writeLine({s})"),
4264 (true, false) => format!("stderr.write({s})"),
4265 })
4266 }
4267 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4268 "write" | "writeln" => {
4269 // `write!(f, "..", ..)` inside a formatting impl: the first
4270 // argument is the sink, the rest is an ordinary format call.
4271 let args: Vec<Expr> = mac
4272 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
4273 .map_err(|e| format!("write!: {e}"))?
4274 .into_iter()
4275 .collect();
4276 let sink = args.first().ok_or("`write!` needs a sink")?;
4277 if !self.is_fmt_param(sink) {
4278 return Err("`write!` to anything but the `Formatter` of the \
4279 enclosing formatting impl is not implemented"
4280 .into());
4281 }
4282 let s = self.format_pieces(&args[1..])?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4283 let s = if name == "writeln" {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4284 format!("({} & \"\\n\")", s)
4285 } else {
4286 s
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4287 };
4288 Ok(format!("result.add({})", s))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4289 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4290 "panic" => {
4291 let s = self.format_args(mac)?;
4292 Ok(format!("rsPanic({s})"))
4293 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4294 // `debug_assert*` fires in debug builds, which is the profile
4295 // this project models, so it lowers the same as `assert*`.
4296 "assert" | "debug_assert" => {
4297 let args: Vec<Expr> = mac
4298 .parse_body_with(
4299 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4300 )
4301 .map_err(|e| format!("{name}!: {e}"))?
4302 .into_iter()
4303 .collect();
4304 let cond = args.first().ok_or("`assert!` needs a condition")?;
4305 let v = self.expr(cond)?;
4306 let msg = if args.len() > 1 {
4307 self.format_pieces(&args[1..])?
4308 } else {
4309 fmt::nim_str("assertion failed")
4310 };
4311 Ok(format!("(if not ({}): rsPanic({}))", v.code, msg))
4312 }
4313 "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => {
4314 let args: Vec<Expr> = mac
4315 .parse_body_with(
4316 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4317 )
4318 .map_err(|e| format!("{name}!: {e}"))?
4319 .into_iter()
4320 .collect();
4321 if args.len() < 2 {
4322 return Err(format!("`{name}!` takes two operands"));
4323 }
4324 let a = self.expr(&args[0])?;
4325 let b = self.expr_at(&args[1], a.ty.as_ref())?;
4326 let ne = name.ends_with("_ne");
4327 let op = if ne { "!=" } else { "==" };
4328 // Rust's message shows both sides; reproducing it keeps a
4329 // failing assertion as informative as the original.
4330 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 11h ago4331 Ok(format!(
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4332 "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))",
4333 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 11h ago4334 ))
4335 }
4336 "vec" => {
4337 let body = mac.tokens.to_string();
4338 if body.trim().is_empty() {
4339 return Ok("@[]".into());
4340 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4341 // `vec![elem; n]` is the repeat form, not a list. The macro
4342 // body has no brackets, so it is parsed directly.
4343 if body.contains(';') {
4344 let (v, n) = mac
4345 .parse_body_with(|input: syn::parse::ParseStream| {
4346 let v: Expr = input.parse()?;
4347 input.parse::<syn::Token![;]>()?;
4348 let n: Expr = input.parse()?;
4349 Ok((v, n))
4350 })
4351 .map_err(|e| format!("vec![elem; n]: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago4352 let want = self.vec_expect.clone();
4353 let v = self.expr_at(&v, want.as_ref())?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4354 let n = self.expr(&n)?;
4355 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
4356 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4357 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
4358 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
4359 .map_err(|e| format!("vec!: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago4360 let want = self.vec_expect.clone();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4361 let mut parts = Vec::new();
4362 for e in &elems {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago4363 parts.push(self.expr_at(e, want.as_ref())?.code);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4364 }
4365 Ok(format!("@[{}]", parts.join(", ")))
4366 }
4367 other => Err(format!(
4368 "unsupported macro `{other}!`; a macro whose expansion is not \
4369 known cannot be lowered faithfully"
4370 )),
4371 }
4372 }
4373
4374 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
4375 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 10h ago4376 let args: Vec<Expr> = mac
4377 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
4378 .map_err(|e| format!("format arguments: {e}"))?
4379 .into_iter()
4380 .collect();
4381 self.format_pieces(&args)
4382 }
4383
4384 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
4385 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
4386 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 11h ago4387 if args.is_empty() {
4388 return Ok("\"\"".into());
4389 }
4390 return Err("the first argument must be a literal format string".into());
4391 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4392 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4393
4394 let pieces = fmt::parse(&s.value())?;
4395 let mut parts: Vec<String> = Vec::new();
4396 let mut next = 0usize;
4397 let mut used = vec![false; rest.len()];
4398 for p in &pieces {
4399 match p {
4400 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
4401 fmt::Piece::Arg { r#ref, spec } => {
4402 let v = match r#ref {
4403 fmt::Ref::Next => {
4404 let e = rest.get(next).ok_or("too few arguments for format string")?;
4405 used[next] = true;
4406 next += 1;
4407 self.expr(e)?
4408 }
4409 fmt::Ref::Index(i) => {
4410 let e = rest.get(*i).ok_or("format index out of range")?;
4411 used[*i] = true;
4412 self.expr(e)?
4413 }
4414 fmt::Ref::Named(n) => {
4415 let t = self.lookup(n).ok_or_else(|| {
4416 format!("`{{{n}}}` captures `{n}`, which is not in scope")
4417 })?;
4418 Val::new(ident(n), Some(t))
4419 }
4420 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4421 let integer = v.ty.as_ref().is_some_and(|t| t.is_integer());
4422 if spec.radix.is_some() && !integer && v.ty.is_none() {
4423 return Err(
4424 "a radix format (`{:x}`, `{:b}`, ...) needs a known \
4425 argument type: on an integer it formats the bit \
4426 pattern, on anything else it calls that type's own \
4427 impl"
4428 .into(),
4429 );
4430 }
4431 parts.push(fmt::render_arg(&v.code, spec, integer));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4432 }
4433 }
4434 }
4435 // Rust rejects an argument that no `{}` consumes; so do we, rather
4436 // than dropping it from the output.
4437 if let Some(i) = used.iter().position(|u| !u) {
4438 return Err(format!(
4439 "argument {} is never used by the format string",
4440 i + 1
4441 ));
4442 }
4443 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
4444 }
4445}
4446
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4447/// Whether a pattern introduces a binding.
4448fn binds(p: &Pat) -> bool {
4449 match p {
4450 Pat::Ident(_) => true,
4451 Pat::Guard(g) => binds(&g.pat),
4452 Pat::Paren(x) => binds(&x.pat),
4453 Pat::Reference(r) => binds(&r.pat),
4454 Pat::Or(o) => o.cases.iter().any(binds),
4455 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
4456 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
4457 _ => false,
4458 }
4459}
4460
4461/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
4462fn destructures(p: &Pat) -> bool {
4463 matches!(
4464 p,
4465 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
4466 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
4467 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
4468 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
4469}
4470
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4471/// Whether an expression has a direct Nim expression form.
4472///
4473/// Nim's `if` is an expression only when every arm is a single expression, and
4474/// its `case` is never one here. Anything else has to be lowered as statements
4475/// that assign into a target.
4476fn expressible(e: &Expr) -> bool {
4477 match e {
4478 Expr::If(i) => {
4479 let Some(then) = single_expr(&i.then_branch) else { return false };
4480 if !expressible(then) {
4481 return false;
4482 }
4483 match &i.else_branch {
4484 None => false,
4485 Some((_, els)) => match &**els {
4486 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
4487 other => expressible(other),
4488 },
4489 }
4490 }
4491 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
4492 _ => true,
4493 }
4494}
4495
4496/// The single expression a block consists of, if that is all it is. An `if`
4497/// can only be lowered as a Nim `if`-expression when both arms are this shape.
4498fn single_expr(b: &syn::Block) -> Option<&Expr> {
4499 match (b.stmts.len(), b.stmts.first()) {
4500 (1, Some(Stmt::Expr(e, None))) => Some(e),
4501 _ => None,
4502 }
4503}
4504
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4505/// Substitute `params[i] -> args[i]` through a type. Enough of the type
4506/// grammar is covered to expand the aliases we accept; anything else is left
4507/// alone and will be reported by `ty::map` if it is unsupported.
4508fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
4509 use syn::Type;
4510 match t {
4511 Type::Path(p) => {
4512 if p.qself.is_none() && p.path.segments.len() == 1 {
4513 let seg = &p.path.segments[0];
4514 if seg.arguments.is_empty() {
4515 let name = seg.ident.to_string();
4516 if let Some(i) = params.iter().position(|x| *x == name) {
4517 return args[i].clone();
4518 }
4519 }
4520 }
4521 let mut p = p.clone();
4522 for seg in &mut p.path.segments {
4523 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
4524 for g in &mut a.args {
4525 if let syn::GenericArgument::Type(t) = g {
4526 *t = substitute(t, params, args);
4527 }
4528 }
4529 }
4530 }
4531 Type::Path(p)
4532 }
4533 Type::Reference(r) => {
4534 let mut r = r.clone();
4535 r.elem = Box::new(substitute(&r.elem, params, args));
4536 Type::Reference(r)
4537 }
4538 Type::Slice(sl) => {
4539 let mut sl = sl.clone();
4540 sl.elem = Box::new(substitute(&sl.elem, params, args));
4541 Type::Slice(sl)
4542 }
4543 Type::Array(a) => {
4544 let mut a = a.clone();
4545 a.elem = Box::new(substitute(&a.elem, params, args));
4546 Type::Array(a)
4547 }
4548 Type::Tuple(tp) => {
4549 let mut tp = tp.clone();
4550 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
4551 Type::Tuple(tp)
4552 }
4553 Type::Paren(p) => substitute(&p.elem, params, args),
4554 Type::Group(g) => substitute(&g.elem, params, args),
4555 other => other.clone(),
4556 }
4557}
4558
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4559// --------------------------------------------------------------- utilities
4560
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4561/// Whether a return type is a borrow of one of the arguments, which Nim
4562/// models with a view rather than with an owned copy.
4563fn returns_borrow(t: &syn::Type) -> bool {
4564 match t {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago4565 syn::Type::Reference(r) => match &*r.elem {
4566 syn::Type::Slice(_) => true,
4567 // `&str` is a borrow of someone else's bytes too, and returning it
4568 // means returning a view, not an owned string.
4569 syn::Type::Path(p) => p.path.is_ident("str"),
4570 _ => false,
4571 },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4572 syn::Type::Paren(p) => returns_borrow(&p.elem),
4573 syn::Type::Group(g) => returns_borrow(&g.elem),
4574 _ => false,
4575 }
4576}
4577
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 9h ago4578/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
4579/// to the crate root, which is where a flattened module's items live unless
4580/// they came from one of the extra input files.
4581fn module_of(prefix: &[String]) -> String {
4582 match prefix.last() {
4583 Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
4584 _ => String::new(),
4585 }
4586}
4587
4588/// The first type argument of an `Option[T]` / `Result[T, E]`.
4589fn elem_arg(t: &Nim) -> Nim {
4590 match t {
4591 Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
4592 other => other.clone(),
4593 }
4594}
4595
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4596/// The element type of a sequence-like Nim type.
4597fn elem_of(t: &Option<Nim>) -> Option<Nim> {
4598 match t {
4599 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
4600 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
4601 _ => None,
4602 }
4603}
4604
4605/// The short name a Nim type is known by, for keying method tables.
4606fn type_name(t: &Nim) -> String {
4607 match t {
4608 Nim::Named(n, _) => n.clone(),
4609 Nim::Prim(p) => p.clone(),
4610 other => other.render(),
4611 }
4612}
4613
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago4614/// `(trait, operator)` for every operator trait we dispatch.
4615const OPERATOR_TRAITS: &[(&str, &str)] = &[
4616 ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"),
4617 ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"),
4618 ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="),
4619 ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="),
4620 ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="),
4621 ("Neg", "neg"), ("Not", "not"),
4622];
4623
4624/// `(operator, trait method name)`.
4625const OP_METHOD: &[(&str, &str)] = &[
4626 ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"),
4627 ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"),
4628 ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"),
4629 ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"),
4630 ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"),
4631 (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"),
4632];
4633
4634fn op_method(op: &str) -> &'static str {
4635 OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("")
4636}
4637
4638/// The operator symbol a compound assignment applies.
4639fn compound_symbol(op: &BinOp) -> &'static str {
4640 match op {
4641 BinOp::AddAssign(_) => "+=",
4642 BinOp::SubAssign(_) => "-=",
4643 BinOp::MulAssign(_) => "*=",
4644 BinOp::DivAssign(_) => "/=",
4645 BinOp::RemAssign(_) => "%=",
4646 BinOp::BitAndAssign(_) => "&=",
4647 BinOp::BitOrAssign(_) => "|=",
4648 BinOp::BitXorAssign(_) => "^=",
4649 BinOp::ShlAssign(_) => "<<=",
4650 BinOp::ShrAssign(_) => ">>=",
4651 _ => "",
4652 }
4653}
4654
4655fn binary_symbol(op: &BinOp) -> &'static str {
4656 match op {
4657 BinOp::Add(_) => "+",
4658 BinOp::Sub(_) => "-",
4659 BinOp::Mul(_) => "*",
4660 BinOp::Div(_) => "/",
4661 BinOp::Rem(_) => "%",
4662 BinOp::BitAnd(_) => "&",
4663 BinOp::BitOr(_) => "|",
4664 BinOp::BitXor(_) => "^",
4665 BinOp::Shl(_) => "<<",
4666 BinOp::Shr(_) => ">>",
4667 _ => "",
4668 }
4669}
4670
4671/// The operator a trait overloads, if it is one of the operator traits.
4672fn operator_trait(t: &str) -> Option<&'static str> {
4673 Some(match t {
4674 "Add" => "+",
4675 "Sub" => "-",
4676 "Mul" => "*",
4677 "Div" => "/",
4678 "Rem" => "%",
4679 "BitAnd" => "&",
4680 "BitOr" => "|",
4681 "BitXor" => "^",
4682 "Shl" => "<<",
4683 "Shr" => ">>",
4684 "AddAssign" => "+=",
4685 "SubAssign" => "-=",
4686 "MulAssign" => "*=",
4687 "DivAssign" => "/=",
4688 "RemAssign" => "%=",
4689 "BitAndAssign" => "&=",
4690 "BitOrAssign" => "|=",
4691 "BitXorAssign" => "^=",
4692 "ShlAssign" => "<<=",
4693 "ShrAssign" => ">>=",
4694 "Neg" => "neg",
4695 "Not" => "not",
4696 _ => return None,
4697 })
4698}
4699
4700/// The Nim proc name for a trait method, qualified by trait and type so that
4701/// two traits declaring the same method name cannot collide.
4702fn trait_method_name(ty: &str, tr: &str, m: &str) -> String {
4703 format!("rs{}_{}_{}", tr, ty, m)
4704}
4705
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4706fn is_fmt_trait(t: &str) -> bool {
4707 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
4708}
4709
4710/// The prelude proc a formatting trait's output is produced by.
4711fn fmt_proc(t: &str) -> &'static str {
4712 match t {
4713 "Display" => "rsDisplay",
4714 "Debug" => "rsDebug",
4715 "LowerHex" => "rsLowerHex",
4716 "UpperHex" => "rsUpperHex",
4717 "Binary" => "rsBinary",
4718 _ => "rsOctal",
4719 }
4720}
4721
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 8h ago4722/// Whether an expression is an iterator-producing chain rather than a value.
4723fn is_iterator_expr(e: &Expr) -> bool {
4724 match e {
4725 Expr::MethodCall(m) => matches!(
4726 m.method.to_string().as_str(),
4727 "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact"
4728 | "chunks_exact_mut" | "windows"
4729 ),
4730 Expr::Paren(p) => is_iterator_expr(&p.expr),
4731 _ => false,
4732 }
4733}
4734
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 9h ago4735/// Whether an expression denotes a place -- a variable, a field, or an index
4736/// or slice of one -- and so may be re-evaluated with no side effect.
4737fn is_pure_place(e: &Expr) -> bool {
4738 match e {
4739 Expr::Path(_) => true,
4740 Expr::Field(f) => is_pure_place(&f.base),
4741 Expr::Index(i) => {
4742 is_pure_place(&i.expr)
4743 && match &*i.index {
4744 Expr::Range(r) => {
4745 r.start.as_deref().map_or(true, is_pure_place)
4746 && r.end.as_deref().map_or(true, is_pure_place)
4747 }
4748 other => is_pure_place(other),
4749 }
4750 }
4751 Expr::Lit(_) => true,
4752 Expr::Reference(r) => is_pure_place(&r.expr),
4753 Expr::Paren(p) => is_pure_place(&p.expr),
4754 Expr::Group(g) => is_pure_place(&g.expr),
4755 // Arithmetic on places is still side-effect free, so a bound like
4756 // `..want - 1` does not stop the binding being an alias.
4757 Expr::Binary(b) if !is_compound(&b.op) => {
4758 is_pure_place(&b.left) && is_pure_place(&b.right)
4759 }
4760 Expr::Unary(u) => is_pure_place(&u.expr),
4761 Expr::Cast(c) => is_pure_place(&c.expr),
4762 _ => false,
4763 }
4764}
4765
4766/// Whether an expression is a `&mut` borrow, directly or through parens.
4767fn is_mut_borrow(e: &Expr) -> bool {
4768 match e {
4769 Expr::Reference(r) => r.mutability.is_some(),
4770 Expr::Paren(p) => is_mut_borrow(&p.expr),
4771 Expr::Group(g) => is_mut_borrow(&g.expr),
4772 _ => false,
4773 }
4774}
4775
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4776fn takes_self(sig: &syn::Signature) -> bool {
4777 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
4778}
4779
4780fn path_name(p: &syn::Path) -> String {
4781 p.segments
4782 .last()
4783 .map(|s| s.ident.to_string())
4784 .unwrap_or_default()
4785}
4786
4787fn is_compound(op: &BinOp) -> bool {
4788 matches!(
4789 op,
4790 BinOp::AddAssign(_)
4791 | BinOp::SubAssign(_)
4792 | BinOp::MulAssign(_)
4793 | BinOp::DivAssign(_)
4794 | BinOp::RemAssign(_)
4795 | BinOp::BitAndAssign(_)
4796 | BinOp::BitOrAssign(_)
4797 | BinOp::BitXorAssign(_)
4798 | BinOp::ShlAssign(_)
4799 | BinOp::ShrAssign(_)
4800 )
4801}
4802
4803/// The Nim literal suffix for an integer type (`5'i32`).
4804fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
4805 let Nim::Prim(p) = t else {
4806 return Err("not a primitive integer".into());
4807 };
4808 Ok(match p.as_str() {
4809 "int8" => "i8",
4810 "int16" => "i16",
4811 "int32" => "i32",
4812 "int64" => "i64",
4813 "int" => "i",
4814 "uint8" => "u8",
4815 "uint16" => "u16",
4816 "uint32" => "u32",
4817 "uint64" => "u64",
4818 "uint" => "u",
4819 other => return Err(format!("no Nim literal suffix for `{other}`")),
4820 })
4821}
4822
4823/// The unsigned integer type of the same width, used to spell `wrapping_*`.
4824fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
4825 let Nim::Prim(p) = t else {
4826 return Err("not a primitive integer".into());
4827 };
4828 Ok(match p.as_str() {
4829 "int8" => "uint8",
4830 "int16" => "uint16",
4831 "int32" => "uint32",
4832 "int64" => "uint64",
4833 "int" => "uint",
4834 other => return Err(format!("`{other}` has no unsigned peer")),
4835 })
4836}
4837
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4838fn quote_meta(m: &syn::Meta) -> String {
4839 match m {
4840 syn::Meta::Path(p) => path_name(p),
4841 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
4842 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
4843 }
4844}
4845
4846fn item_attrs(i: &Item) -> &[syn::Attribute] {
4847 match i {
4848 Item::Fn(f) => &f.attrs,
4849 Item::Struct(s) => &s.attrs,
4850 Item::Enum(e) => &e.attrs,
4851 Item::Impl(x) => &x.attrs,
4852 Item::Const(c) => &c.attrs,
4853 Item::Type(t) => &t.attrs,
4854 Item::Mod(m) => &m.attrs,
4855 Item::Use(u) => &u.attrs,
4856 Item::ExternCrate(e) => &e.attrs,
4857 Item::Static(s) => &s.attrs,
4858 _ => &[],
4859 }
4860}
4861
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4862fn item_kind(i: &Item) -> &'static str {
4863 match i {
4864 Item::Trait(_) => "`trait`",
4865 Item::Static(_) => "`static`",
4866 Item::Macro(_) => "macro definition",
4867 Item::Union(_) => "`union`",
4868 Item::ForeignMod(_) => "`extern` block",
4869 _ => "item",
4870 }
4871}
4872
4873fn expr_kind(e: &Expr) -> &'static str {
4874 match e {
4875 Expr::Async(_) => "`async` block",
4876 Expr::Await(_) => "`.await`",
4877 Expr::Try(_) => "`?`",
4878 Expr::Range(_) => "range",
4879 Expr::Match(_) => "`match` (only statement position is implemented)",
4880 Expr::Let(_) => "`let` expression",
4881 Expr::Unsafe(_) => "`unsafe` block",
4882 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
4883 _ => "expression",
4884 }
4885}