nandi/rustnimpublic Fork 0
4e4d09dcdd22d17ba510de5639fc3a952ac73f6e
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 · 5725 lines · 243.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 10h 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 10h 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 10h 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 9h 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,
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago162 /// `Error = 1` — Nim enums take explicit ordinals too, so the value is
163 /// preserved rather than the variant being renumbered.
164 discriminant: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago165 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
166 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
167 /// Nim requires the branches of a variant object to have distinct fields.
168 fields: Vec<(String, Nim)>,
169}
170
171#[derive(Clone)]
172struct EnumDef {
173 name: String,
174 /// True when every variant is a unit variant, which Nim represents as a
175 /// plain `enum` rather than an object variant.
176 simple: bool,
177 variants: Vec<Variant>,
178}
179
180impl EnumDef {
181 fn kind_ident(&self, v: &str) -> String {
182 format!("k{}{}", self.name, v)
183 }
184 fn ctor_ident(&self, v: &str) -> String {
185 format!("{}{}", self.name, v)
186 }
187 fn get(&self, v: &str) -> Option<&Variant> {
188 self.variants.iter().find(|x| x.name == v)
189 }
190}
191
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago192pub struct Lowerer {
193 out: String,
194 indent: usize,
195 scopes: Vec<HashMap<String, Nim>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago196 /// Names introduced by a `for` pattern that stand for an lvalue or a
197 /// window into a container, rather than for a variable of their own.
198 alias_scopes: Vec<HashMap<String, Alias>>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago199 /// `(module, name) -> signature`. Rust keeps `lower::decode` and
200 /// `mixed::decode` apart by module; flattening into one Nim module would
201 /// merge them, so the module is part of the key and of the emitted name.
202 fns: HashMap<(String, String), Sig>,
203 /// Module being lowered: the file stem, or empty for the crate root.
204 cur_mod: String,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago205 /// The type of the `impl` block being lowered, which `Self` names.
206 self_ty: Option<Nim>,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago207 /// Type parameters of the enclosing `impl`, which its methods share.
208 impl_generics: Vec<String>,
209 /// Type parameters of the proc being lowered, impl's included.
210 fn_generics: Vec<String>,
211 /// Type parameters declared by each generic struct or enum.
212 type_generics: HashMap<String, Vec<String>>,
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago213 /// `(type, name) -> type` for `type Item = ..;` inside an `impl`. Rust
214 /// writes those as `Self::Item`, which has to resolve before any
215 /// signature mentioning it is mapped.
216 assoc: HashMap<(String, String), Nim>,
217 /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`.
218 assoc_consts: HashMap<(String, String), (String, Nim)>,
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago219 /// Symbols declared by an `extern "C"` block.
220 foreign: std::collections::HashSet<String>,
221 /// Const-qualified C pointer aliases already declared.
222 const_ptrs: std::collections::HashSet<String>,
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 7h ago223 /// `macro_rules!` definitions we can expand, and the reasons for those we
224 /// cannot, so a call site can say *why* rather than "unknown macro".
225 mrules: HashMap<String, crate::mrules::MacroDef>,
226 mrules_bad: HashMap<String, String>,
Expand multi-rule and recursive macro_rules d459281 nandithebull 6h ago227 macro_depth: usize,
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago228 /// Types declared by a `bitflags!` invocation.
229 bitflags: std::collections::HashSet<String>,
230 /// `(type, flag) -> nim const name`.
231 flag_consts: HashMap<(String, String), String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago232 /// `use` brings a name into scope from another module. Flattening loses
233 /// the module structure, so the mapping is recorded and consulted when a
234 /// bare call is resolved.
235 use_map: HashMap<String, String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago236 /// struct name -> (field, type)
237 structs: HashMap<String, Vec<(String, Nim)>>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago238 enums: HashMap<String, EnumDef>,
239 /// variant name -> enums declaring it. A variant named by more than one
240 /// enum must be written qualified, or it is rejected as ambiguous.
241 variant_owner: HashMap<String, Vec<String>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago242 /// `(receiver type, method) -> signature`. Keyed by type because two
243 /// types may define the same method name, and Nim tells them apart by
244 /// overload resolution on the first parameter.
245 methods: HashMap<(String, String), Sig>,
246 /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
247 /// on a user type can be checked rather than assumed.
248 fmt_impls: HashMap<(String, String), ()>,
249 /// `(from, to)` conversions declared by `impl From<A> for B`.
250 from_impls: HashMap<(String, String), String>,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago251 /// Operator traits implemented for a type, so `a += b` on a user type can
252 /// be dispatched to the impl rather than to Nim's built-in operator.
253 op_impls: HashMap<(String, String), ()>,
254 /// `(type, method) -> nim name`, for calls written as `Type::method(..)`.
255 statics: HashMap<(String, String), String>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago256 /// Forward declarations, emitted between the type definitions and the
257 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
258 /// proc is declared up front rather than the input being reordered --
259 /// which would not work for mutual recursion anyway.
260 forwards: Vec<String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago261 /// Element type a `vec![..]` should build, from the binding's annotation.
262 vec_expect: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago263 /// While lowering a formatting impl: the `Formatter` parameter's name.
264 /// Writes through it produce the proc's string result.
265 fmt_param: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago266 /// `type X<T> = ...`, expanded before any type is mapped.
267 aliases: HashMap<String, (Vec<String>, syn::Type)>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago268 /// Module names supplied as separate input files. A `mod x;` naming one
269 /// of these is satisfied by that file having been passed in.
270 pub modules: Vec<String>,
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 9h ago271 /// How many items were actually translated. If this is zero the input
272 /// produced nothing but the prelude, and reporting success for that is
273 /// the precise failure this project exists to avoid -- see `findings/`.
274 emitted: usize,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago275 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
276 /// evaluated against these exactly as rustc would, so an item that is
277 /// dropped here is genuinely not part of the program being compiled.
278 pub features: Vec<String>,
279 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago280 /// Return type of the proc being lowered, so `return e` and a trailing
281 /// expression can type their literals the way Rust's inference would.
282 ret: Option<Nim>,
283 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
284 /// statement must assign their value to.
285 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago286 /// Set while lowering a `while` condition, which Nim re-evaluates each
287 /// iteration and so cannot have statements hoisted out of it.
288 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago289 tmp: usize,
290}
291
292impl Lowerer {
293 pub fn new() -> Self {
294 Lowerer {
295 out: String::new(),
296 indent: 0,
297 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago298 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago299 fns: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago300 cur_mod: String::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago301 self_ty: None,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago302 impl_generics: Vec::new(),
303 fn_generics: Vec::new(),
304 type_generics: HashMap::new(),
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago305 assoc: HashMap::new(),
306 assoc_consts: HashMap::new(),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago307 foreign: std::collections::HashSet::new(),
308 const_ptrs: std::collections::HashSet::new(),
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 7h ago309 mrules: HashMap::new(),
310 mrules_bad: HashMap::new(),
Expand multi-rule and recursive macro_rules d459281 nandithebull 6h ago311 macro_depth: 0,
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago312 bitflags: std::collections::HashSet::new(),
313 flag_consts: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago314 use_map: HashMap::new(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago315 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago316 enums: HashMap::new(),
317 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago318 methods: HashMap::new(),
319 fmt_impls: HashMap::new(),
320 from_impls: HashMap::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago321 op_impls: HashMap::new(),
322 statics: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago323 fmt_param: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago324 vec_expect: None,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago325 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago326 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago327 modules: Vec::new(),
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 9h ago328 emitted: 0,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago329 features: Vec::new(),
330 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago331 ret: None,
332 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago333 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago334 tmp: 0,
335 }
336 }
337
338 // ------------------------------------------------------------ emission
339
340 fn line(&mut self, s: &str) {
341 for _ in 0..self.indent {
342 self.out.push_str(" ");
343 }
344 self.out.push_str(s);
345 self.out.push('\n');
346 }
347
348 fn blank(&mut self) {
349 self.out.push('\n');
350 }
351
352 fn fresh(&mut self, hint: &str) -> String {
353 self.tmp += 1;
354 format!("rsTmp{}{}", hint, self.tmp)
355 }
356
357 // --------------------------------------------------------------- scope
358
359 fn push_scope(&mut self) {
360 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago361 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago362 }
363 fn pop_scope(&mut self) {
364 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago365 self.alias_scopes.pop();
366 }
367 fn bind_alias(&mut self, name: &str, a: Alias) {
368 self.alias_scopes
369 .last_mut()
370 .unwrap()
371 .insert(name.to_string(), a);
372 }
373 fn lookup_alias(&self, name: &str) -> Option<Alias> {
374 self.alias_scopes
375 .iter()
376 .rev()
377 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago378 }
379 fn bind(&mut self, name: &str, t: Nim) {
380 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
381 }
382 fn lookup(&self, name: &str) -> Option<Nim> {
383 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
384 }
385
386 // ---------------------------------------------------------------- file
387
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago388 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 ago389 self.out.push_str(include_str!("prelude.nim"));
390 self.blank();
391
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago392 // Pass 0: type aliases. A signature in one file may use an alias
393 // declared in another, and inputs are given in whatever order suits
394 // 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 10h ago395 for (m, f) in files {
396 self.cur_mod = m.clone();
397 for item in &f.items {
398 self.collect_aliases(item)?;
399 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago400 }
401
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago402 // Pass 1: signatures and struct shapes, so that a call can be typed
403 // regardless of declaration order (Rust has no forward declarations).
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago404 for (m, f) in files {
405 self.cur_mod = m.clone();
406 for item in &f.items {
407 self.collect(item)?;
408 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago409 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago410 // Pass 2: type definitions, which every signature may mention.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago411 for (m, f) in files {
412 self.cur_mod = m.clone();
413 for item in &f.items {
414 self.item_types(item)?;
415 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago416 }
417
418 // Pass 3: forward declarations. Rust imposes no declaration order and
419 // Nim does, so everything is declared before any body is emitted;
420 // reordering the input would not handle mutual recursion anyway.
421 if !self.forwards.is_empty() {
422 for f in self.forwards.clone() {
423 self.line(&f);
424 }
425 self.blank();
426 }
427
428 // Pass 4: bodies.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago429 for (m, f) in files {
430 self.cur_mod = m.clone();
431 for item in &f.items {
432 self.item(item)?;
433 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago434 }
435
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 9h ago436 // An input that translates to nothing is a failure, however plausible
437 // the output file looks. The prelude alone is not a translation.
438 if self.emitted == 0 {
439 return Err(format!(
440 "nothing was translated: the input has no items this lowering \
441 emits{}. Writing a file containing only the prelude would \
442 report success for work that was not done",
443 if self.dropped_by_cfg > 0 {
444 format!(
445 " ({} item(s) were dropped by `#[cfg]`; enable them with \
446 `--cfg feature=<name>`)",
447 self.dropped_by_cfg
448 )
449 } else {
450 String::new()
451 }
452 ));
453 }
454
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago455 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 ago456 self.blank();
457 self.line("when isMainModule:");
458 self.indent += 1;
459 self.line("try:");
460 self.line(" main()");
461 // Rust's panic exits 101 with a message on stderr. Nim's Defects
462 // exit 1. Mapping them here is what keeps the differential runner's
463 // exit-status comparison meaningful for panicking programs.
464 self.line("except RustPanic as e:");
465 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
466 self.line(" quit(101)");
467 self.line("except Defect as e:");
468 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
469 self.line(" quit(101)");
470 self.indent -= 1;
471 }
472 Ok(std::mem::take(&mut self.out))
473 }
474
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago475 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
476 if !self.cfg_keeps(item_attrs(item))? {
477 return Ok(());
478 }
479 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago480 Item::Use(u) => self.collect_use(&u.tree, &[]),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago481 Item::Type(t) => {
482 let params: Vec<String> = t
483 .generics
484 .params
485 .iter()
486 .filter_map(|g| match g {
487 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
488 _ => None,
489 })
490 .collect();
491 self.aliases
492 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
493 }
494 Item::Mod(m) if m.content.is_some() => {
495 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
496 for i in &items {
497 self.collect_aliases(i)?;
498 }
499 }
500 _ => {}
501 }
502 Ok(())
503 }
504
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago505 /// Record what a `use` brings into scope, as `name -> module`.
506 fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
507 use syn::UseTree;
508 match t {
509 UseTree::Path(p) => {
510 let mut pre = prefix.to_vec();
511 pre.push(p.ident.to_string());
512 self.collect_use(&p.tree, &pre);
513 }
514 UseTree::Group(g) => {
515 for t in &g.items {
516 self.collect_use(t, prefix);
517 }
518 }
519 UseTree::Name(n) => {
520 let m = module_of(prefix);
521 self.use_map.insert(n.ident.to_string(), m);
522 }
523 UseTree::Rename(r) => {
524 let m = module_of(prefix);
525 self.use_map.insert(r.rename.to_string(), m);
526 }
527 // A glob brings in an unknown set of names; resolution falls back
528 // to the current module and the root, as it would without it.
529 UseTree::Glob(_) => {}
530 }
531 }
532
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago533 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago534 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
535 // silently would change what the program does; picking a feature set
536 // on the user's behalf would be a guess. So it is reported, except on
537 // items that carry no runtime meaning here anyway.
538 if !self.cfg_keeps(item_attrs(item))? {
539 self.dropped_by_cfg += 1;
540 return Ok(());
541 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago542 match item {
543 Item::Fn(f) => {
544 let (params, ret) = self.signature(&f.sig)?;
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago545 let mut gen_names = Self::generics_of(&f.sig.generics);
546 gen_names.extend(self.impl_trait_params(&f.sig));
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago547 let name = f.sig.ident.to_string();
548 let nim = self.fn_name(&self.cur_mod, &name);
549 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
550 self.fns
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago551 .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 ago552 }
553 Item::Struct(s) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago554 if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
555 return Err(format!(
556 "`struct {}` has a const generic parameter, which Nim has \
557 no equivalent for",
558 s.ident
559 ));
560 }
561 let g = Self::generics_of(&s.generics);
562 // The parameters must be in scope while the field types are
563 // mapped, so that `T` resolves to itself rather than to an
564 // unknown named type.
565 self.type_generics.insert(s.ident.to_string(), g);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago566 let mut fields = Vec::new();
567 for (i, f) in s.fields.iter().enumerate() {
568 let name = match &f.ident {
569 Some(id) => id.to_string(),
570 None => format!("f{i}"), // tuple struct
571 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago572 // A field of `&[T]` / `&str` type is a borrow, and Nim's
573 // view types allow it as an object field, so it stays a
574 // view rather than being copied into a `seq`.
575 let t = self.map_ty(&f.ty)?;
576 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
577 fields.push((name, t));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago578 }
579 self.structs.insert(s.ident.to_string(), fields);
580 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago581 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => {
582 self.collect_bitflags(&m.mac)?;
583 }
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 7h ago584 // A `macro_rules!` definition is recorded, not emitted: it is
585 // expanded at each call site, where the types its body needs are
586 // known. See `src/mrules.rs` for why that beats translating it
587 // into a Nim template.
588 Item::Macro(m) if path_name(&m.mac.path) == "macro_rules" => {
589 if let Some(name) = &m.ident {
590 match crate::mrules::parse(m.mac.tokens.clone()) {
591 Ok(def) => {
592 self.mrules.insert(name.to_string(), def);
593 }
594 Err(why) => {
595 // Recorded as unusable rather than silently absent:
596 // a call site gets this reason instead of "unknown
597 // macro".
598 self.mrules_bad.insert(name.to_string(), why);
599 }
600 }
601 }
602 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago603 Item::ForeignMod(f) => {
604 for it in &f.items {
605 if let syn::ForeignItem::Fn(fi) = it {
606 let (params, ret) = self.signature(&fi.sig)?;
607 self.fns.insert(
608 (self.cur_mod.clone(), fi.sig.ident.to_string()),
609 Sig { params, ret, generics: Vec::new() },
610 );
611 }
612 }
613 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago614 Item::Mod(m) if m.content.is_some() => {
615 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
616 for i in &items {
617 self.collect(i)?;
618 }
619 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago620 Item::Type(t) => {
621 let params: Vec<String> = t
622 .generics
623 .params
624 .iter()
625 .filter_map(|g| match g {
626 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
627 _ => None,
628 })
629 .collect();
630 self.aliases
631 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
632 }
633 Item::Enum(e) => {
634 let name = e.ident.to_string();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago635 if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
636 return Err(format!(
637 "`enum {name}` has a const generic parameter, which Nim \
638 has no equivalent for"
639 ));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago640 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago641 self.type_generics
642 .insert(name.clone(), Self::generics_of(&e.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago643 let mut variants = Vec::new();
644 for v in &e.variants {
645 let vname = v.ident.to_string();
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago646 let discriminant = match &v.discriminant {
647 Some((_, e)) => Some(self.expr(e)?.code),
648 None => None,
649 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago650 let mut fields = Vec::new();
651 for (i, f) in v.fields.iter().enumerate() {
652 // Nim requires the branches of a variant object to have
653 // distinct field names, so each is prefixed.
654 let fname = match &f.ident {
655 Some(id) => format!("{vname}_{id}"),
656 None => format!("{vname}_f{i}"),
657 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago658 let t = self.map_ty(&f.ty)?;
659 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
660 fields.push((fname, t));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago661 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago662 variants.push(Variant { name: vname, discriminant, fields });
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago663 }
664 let simple = variants.iter().all(|v| v.fields.is_empty());
665 for v in &variants {
666 self.variant_owner
667 .entry(v.name.clone())
668 .or_default()
669 .push(name.clone());
670 }
671 self.enums.insert(
672 name.clone(),
673 EnumDef { name, simple, variants },
674 );
675 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago676 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago677 let outer_g =
678 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 ago679 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago680 let outer_self = self.self_ty.replace(self_ty.clone());
681 let r = self.collect_impl(im, &self_ty);
682 self.self_ty = outer_self;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago683 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago684 return r;
685 }
686 _ => {}
687 }
688 Ok(())
689 }
690
691 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
692 {
693 let self_ty = self_ty.clone();
694 let tyname = type_name(&self_ty);
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago695 // Associated types first: a signature in the same block may name
696 // one, and it has to resolve by the time that signature is mapped.
697 for it in &im.items {
698 if let syn::ImplItem::Type(t) = it {
699 let v = self.map_ty(&t.ty)?;
700 self.assoc.insert((tyname.clone(), t.ident.to_string()), v);
701 }
702 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago703 if let Some((path, _)) = &im.trait_ {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago704 let tr = path_name(path);
705 if im.items.is_empty() {
706 // A marker trait with no items. We do not model trait
707 // resolution at all, so it generates nothing; any use
708 // that actually needed the trait (a `dyn`, a bound) is
709 // rejected where it appears.
710 return Ok(());
711 }
712 if is_fmt_trait(&tr) {
713 self.forwards.push(format!(
714 "proc {}*(self: {}): string",
715 fmt_proc(&tr),
716 self_ty.render()
717 ));
718 self.fmt_impls.insert((tyname, tr), ());
719 return Ok(());
720 }
721 if tr == "From" {
722 let syn::ImplItem::Fn(m) = &im.items[0] else {
723 return Err("`impl From` must contain `fn from`".into());
724 };
725 let (params, _) = self.signature(&m.sig)?;
726 let src = params
727 .first()
728 .ok_or("`fn from` takes one argument")?
729 .clone();
730 let name = format!("rsFrom{}{}", tyname, type_name(&src));
731 self.forwards.push(self.head_of(&name, &m.sig, None)?);
732 self.from_impls
733 .insert((type_name(&src), tyname), name);
734 return Ok(());
735 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago736 // Any other trait: its methods are emitted as procs on
737 // the type, named after the trait so two traits declaring
738 // the same method name do not collide. The *trait* is not
739 // modelled -- no dynamic dispatch, no bounds -- and a use
740 // that needs it is rejected where it appears.
741 if let Some(op) = operator_trait(&tr) {
742 self.op_impls.insert((tyname.clone(), op.to_string()), ());
743 }
744 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago745 // Already recorded above; a const is emitted with the
746 // bodies.
747 if matches!(it, syn::ImplItem::Type(_) | syn::ImplItem::Const(_)) {
748 continue;
749 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago750 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago751 return Err(format!(
752 "unsupported item in `impl {tr}`: only `fn`, \
753 `type` and `const` are implemented"
754 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago755 };
756 let mname = m.sig.ident.to_string();
757 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago758 let mut gen_names = self.impl_generics.clone();
759 gen_names.extend(Self::generics_of(&m.sig.generics));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago760 let recv = if takes_self(&m.sig) {
761 params.insert(0, self_ty.clone());
762 Some(self_ty.clone())
763 } else {
764 None
765 };
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago766 let nim = trait_method_name(&tyname, &tr, &mname, recv.is_some());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago767 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
768 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago769 .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 9h ago770 self.statics.insert((tyname.clone(), mname), nim);
771 }
772 return Ok(());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago773 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago774 for it in &im.items {
775 if let syn::ImplItem::Fn(m) = it {
776 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago777 let mut gen_names = self.impl_generics.clone();
778 gen_names.extend(Self::generics_of(&m.sig.generics));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago779 if takes_self(&m.sig) {
780 params.insert(0, self_ty.clone());
781 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago782 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 9h ago783 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
784 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 ago785 self.forwards.push(head);
786 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago787 .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 9h ago788 self.statics
789 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago790 }
791 }
792 }
793 Ok(())
794 }
795
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago796 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
797 ///
798 /// This is evaluation, not approximation: rustc does the same thing, and
799 /// an item whose predicate is false is not part of the compiled program.
800 /// A predicate that cannot be evaluated is reported rather than assumed.
801 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
802 for a in attrs {
803 if a.path().is_ident("cfg") {
804 let pred: syn::Meta = a
805 .parse_args()
806 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
807 if !self.cfg_eval(&pred)? {
808 return Ok(false);
809 }
810 }
811 }
812 Ok(true)
813 }
814
815 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
816 match m {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago817 // Bare flags whose value is determined by the profile this project
818 // models: a normal (non-`--test`) debug build, not a docs build.
819 // Anything platform-specific stays rejected, since we would be
820 // picking a target on the user's behalf.
821 syn::Meta::Path(p) if p.is_ident("test") => Ok(false),
822 syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true),
823 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 ago824 syn::Meta::Path(p) if p.is_ident("doctest") || p.is_ident("miri") => Ok(false),
825 // Host facts. The generated Nim is compiled for this machine, so
826 // these are known rather than chosen. See DESIGN.md item 10: it
827 // does make the output host-shaped.
828 syn::Meta::Path(p) if p.is_ident("unix") => Ok(cfg!(unix)),
829 syn::Meta::Path(p) if p.is_ident("windows") => Ok(cfg!(windows)),
830 syn::Meta::NameValue(nv)
831 if nv.path.is_ident("target_os")
832 || nv.path.is_ident("target_arch")
833 || nv.path.is_ident("target_family")
834 || nv.path.is_ident("target_vendor") =>
835 {
836 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
837 return Err("this `cfg` key expects a string".into());
838 };
839 let key = nv.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
840 Ok(s.value()
841 == match key.as_str() {
842 "target_os" => std::env::consts::OS,
843 "target_arch" => std::env::consts::ARCH,
844 "target_family" => std::env::consts::FAMILY,
845 _ => "unknown",
846 })
847 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago848 // The generated Nim is compiled for the same machine, so the
849 // target's word size and endianness are known rather than
850 // guessed. This does mean the output is host-shaped: a crate that
851 // branches on pointer width has had that branch decided here.
852 syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => {
853 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
854 return Err("`target_pointer_width = ..` expects a string".into());
855 };
856 Ok(s.value() == (usize::BITS).to_string())
857 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago858 // Every integer width and pointer-sized atomic exists on the
859 // targets Nim builds for here; like the other host facts this is
860 // read off the machine rather than chosen.
861 syn::Meta::NameValue(nv) if nv.path.is_ident("target_has_atomic") => {
862 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
863 return Err("`target_has_atomic = ..` expects a string".into());
864 };
865 Ok(matches!(
866 s.value().as_str(),
867 "8" | "16" | "32" | "64" | "ptr"
868 ))
869 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago870 syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => {
871 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
872 return Err("`target_endian = ..` expects a string".into());
873 };
874 Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" })
875 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago876 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
877 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
878 return Err("`feature = ..` expects a string".into());
879 };
880 Ok(self.features.iter().any(|f| *f == s.value()))
881 }
882 syn::Meta::List(l) if l.path.is_ident("not") => {
883 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
884 Ok(!self.cfg_eval(&inner)?)
885 }
886 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
887 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
888 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
889 .map_err(|e| e.to_string())?;
890 let all = l.path.is_ident("all");
891 let mut acc = all;
892 for i in &items {
893 let v = self.cfg_eval(i)?;
894 acc = if all { acc && v } else { acc || v };
895 }
896 Ok(acc)
897 }
898 other => Err(format!(
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 8h ago899 "`#[cfg({})]` is not a predicate rustnim can evaluate. \
900 Features (`--cfg feature=..`), host facts (`unix`, `windows`, \
901 `target_os`, `target_arch`, `target_family`, \
902 `target_pointer_width`, `target_endian`), `doc`/`doctest`/\
903 `miri`, and `not`/`all`/`any` over those are. A custom or \
904 build-script `cfg` has no value we could know",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago905 quote_meta(other)
906 )),
907 }
908 }
909
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago910 /// 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 ago911 /// lowering goes through here rather than calling `ty::map` directly, so
912 /// an alias cannot be missed in one position and honoured in another.
913 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago914 // `Self::Item` names an associated type of the enclosing `impl`.
915 if let syn::Type::Path(p) = t {
916 let segs: Vec<String> =
917 p.path.segments.iter().map(|s| s.ident.to_string()).collect();
918 if segs.len() == 2 {
919 let owner = if segs[0] == "Self" {
920 self.self_ty.as_ref().map(type_name)
921 } else {
922 Some(segs[0].clone())
923 };
924 if let Some(o) = owner {
925 if let Some(a) = self.assoc.get(&(o, segs[1].clone())) {
926 return Ok(a.clone());
927 }
928 }
929 }
930 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago931 let n = ty::map(&self.expand(t, 0)?)?;
932 Ok(self.subst_self(n))
933 }
934
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago935 /// Substitute a generic type's parameters with the arguments the use site
936 /// supplies: a field of `Holder<T>` read through a `Holder<i32>` is `i32`.
937 fn subst_type_args(&self, name: &str, used_as: &Nim, field: Nim) -> Nim {
938 let Some(params) = self.type_generics.get(name) else { return field };
939 if params.is_empty() {
940 return field;
941 }
942 let Nim::Named(n, args) = used_as else { return field };
943 if n != name || args.len() != params.len() {
944 return field;
945 }
946 let map: HashMap<String, Nim> =
947 params.iter().cloned().zip(args.iter().cloned()).collect();
948 Self::subst(&field, &map)
949 }
950
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago951 /// `Self` inside an `impl` block names the type being implemented.
952 fn subst_self(&self, t: Nim) -> Nim {
953 let Some(me) = &self.self_ty else { return t };
954 match t {
955 Nim::Named(n, _) if n == "Self" => me.clone(),
956 Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))),
957 Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))),
958 Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))),
959 Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))),
960 Nim::Named(n, a) => {
961 Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect())
962 }
963 other => other,
964 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago965 }
966
967 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
968 if depth > 16 {
969 return Err("type alias expansion did not terminate; is it cyclic?".into());
970 }
971 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
972 // Only an unqualified name can be one of this file's aliases.
973 // `fmt::Result` and `core::result::Result` are different types that
974 // merely end in the same segment.
975 if p.path.segments.len() != 1 {
976 return Ok(t.clone());
977 }
978 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
979 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
980 return Ok(t.clone());
981 };
982 let args: Vec<syn::Type> = match &seg.arguments {
983 syn::PathArguments::AngleBracketed(a) => a
984 .args
985 .iter()
986 .filter_map(|g| match g {
987 GenericArgument::Type(t) => Some(t.clone()),
988 _ => None,
989 })
990 .collect(),
991 _ => vec![],
992 };
993 if args.len() != params.len() {
994 // Flattening several files into one module can bring a crate's own
995 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
996 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
997 // module; here they are told apart by arity, and a use that fits
998 // neither is left for `ty::map` to report.
999 return Ok(t.clone());
1000 }
1001 self.expand(&substitute(target, params, &args), depth + 1)
1002 }
1003
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1004 /// The type parameters a generic item declares.
1005 ///
1006 /// Trait bounds and `where` clauses are dropped. Nim instantiates a
1007 /// generic structurally: an operation the bound would have permitted
1008 /// either exists for the instantiated type or is a compile error at the
1009 /// instantiation site. So dropping a bound cannot make an accepted
1010 /// program mean something different — it only makes rustnim accept some
1011 /// programs rustc would have rejected, which does not matter when the
1012 /// input is known-good Rust.
1013 fn generics_of(g: &syn::Generics) -> Vec<String> {
1014 g.params
1015 .iter()
1016 .filter_map(|p| match p {
1017 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
1018 _ => None,
1019 })
1020 .collect()
1021 }
1022
1023 /// Bind a signature's type parameters by matching its declared parameter
1024 /// types against the actual argument types, then substitute into `ret`.
1025 ///
1026 /// This is the small amount of inference a call site needs: Nim will
1027 /// resolve the instantiation itself, but the *binding* still has to be
1028 /// annotated with a concrete type, and `T` is not one.
1029 fn instantiate(sig: &Sig, args: &[Option<Nim>]) -> Nim {
1030 if sig.generics.is_empty() {
1031 return sig.ret.clone();
1032 }
1033 let mut bound: HashMap<String, Nim> = HashMap::new();
1034 for (decl, actual) in sig.params.iter().zip(args) {
1035 if let Some(a) = actual {
1036 Self::unify(decl, a, &sig.generics, &mut bound);
1037 }
1038 }
1039 Self::subst(&sig.ret, &bound)
1040 }
1041
1042 fn unify(decl: &Nim, actual: &Nim, params: &[String], out: &mut HashMap<String, Nim>) {
1043 match (decl, actual) {
1044 (Nim::Named(n, da), _) if params.iter().any(|p| p == n) && da.is_empty() => {
1045 out.entry(n.clone()).or_insert_with(|| actual.clone());
1046 }
1047 (Nim::Named(_, da), Nim::Named(_, aa)) if da.len() == aa.len() => {
1048 for (d, a) in da.iter().zip(aa) {
1049 Self::unify(d, a, params, out);
1050 }
1051 }
1052 (Nim::Seq(d), Nim::Seq(a))
1053 | (Nim::OpenArray(d), Nim::OpenArray(a))
1054 | (Nim::Seq(d), Nim::OpenArray(a))
1055 | (Nim::OpenArray(d), Nim::Seq(a))
1056 | (Nim::Var(d), Nim::Var(a))
1057 | (Nim::Array(_, d), Nim::Array(_, a)) => Self::unify(d, a, params, out),
1058 (Nim::Var(d), a) => Self::unify(d, a, params, out),
1059 (d, Nim::Var(a)) => Self::unify(d, a, params, out),
1060 (Nim::Tuple(d), Nim::Tuple(a)) if d.len() == a.len() => {
1061 for (d, a) in d.iter().zip(a) {
1062 Self::unify(d, a, params, out);
1063 }
1064 }
1065 _ => {}
1066 }
1067 }
1068
1069 fn subst(t: &Nim, m: &HashMap<String, Nim>) -> Nim {
1070 match t {
1071 Nim::Named(n, a) if a.is_empty() => m.get(n).cloned().unwrap_or_else(|| t.clone()),
1072 Nim::Named(n, a) => {
1073 Nim::Named(n.clone(), a.iter().map(|x| Self::subst(x, m)).collect())
1074 }
1075 Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))),
1076 Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))),
1077 Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))),
1078 Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))),
1079 Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()),
1080 other => other.clone(),
1081 }
1082 }
1083
1084 /// Whether a type mentions a type parameter that is in scope here. Such a
1085 /// type cannot be used as a Nim annotation at an instantiation site: Nim
1086 /// infers it, and writing `T` would name something that is not bound.
1087 fn mentions_type_param(&self, t: &Nim) -> bool {
1088 match t {
1089 Nim::Named(n, a) => {
1090 self.fn_generics.iter().any(|g| g == n)
1091 || a.iter().any(|x| self.mentions_type_param(x))
1092 }
1093 Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) => {
1094 self.mentions_type_param(e)
1095 }
1096 Nim::Tuple(ts) => ts.iter().any(|x| self.mentions_type_param(x)),
1097 Nim::Proc(a, r) => {
1098 a.iter().any(|x| self.mentions_type_param(x)) || self.mentions_type_param(r)
1099 }
1100 _ => false,
1101 }
1102 }
1103
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago1104 /// The Nim name of a `log::Level` written as a path.
1105 fn log_level_of(&mut self, e: &Expr) -> Result<String, String> {
1106 let Expr::Path(p) = e else {
1107 return Err("a log level must be written as `Level::Info`".into());
1108 };
1109 let last = path_name(&p.path);
1110 Ok(match last.as_str() {
1111 "Error" => "rsLvlError",
1112 "Warn" => "rsLvlWarn",
1113 "Info" => "rsLvlInfo",
1114 "Debug" => "rsLvlDebug",
1115 "Trace" => "rsLvlTrace",
1116 other => return Err(format!("`Level::{other}` is not a log level")),
1117 }
1118 .to_string())
1119 }
1120
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1121 /// `[T, U]`, or empty.
1122 fn gen_list(params: &[String]) -> String {
1123 if params.is_empty() {
1124 String::new()
1125 } else {
1126 format!("[{}]", params.join(", "))
1127 }
1128 }
1129
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago1130 /// The Nim name for a function, qualified by its module.
1131 fn fn_name(&self, module: &str, name: &str) -> String {
1132 if module.is_empty() {
1133 ident(name)
1134 } else {
1135 format!("{}_{}", module, ident(name))
1136 }
1137 }
1138
1139 /// Resolve a call path to the module and name it refers to: an explicit
1140 /// `mixed::decode`, then the current module, then the crate root.
1141 fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
1142 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1143 let last = segs.last()?.clone();
1144 if segs.len() >= 2 {
1145 let q = &segs[segs.len() - 2];
1146 if self.fns.contains_key(&(q.clone(), last.clone())) {
1147 return Some((q.clone(), last));
1148 }
1149 }
1150 let imported = self.use_map.get(&last).cloned();
1151 for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
1152 .into_iter()
1153 .flatten()
1154 {
1155 if self.fns.contains_key(&(m.clone(), last.clone())) {
1156 return Some((m, last));
1157 }
1158 }
1159 None
1160 }
1161
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1162 /// The Nim `proc` head for a Rust signature, used both for the forward
1163 /// declaration and for the definition, so the two cannot drift apart.
1164 fn head_of(
1165 &self,
1166 name: &str,
1167 sig: &syn::Signature,
1168 recv: Option<&Nim>,
1169 ) -> Result<String, String> {
1170 let (ptys, ret) = self.signature(sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1171 // A method inside `impl<T> Foo<T>` is generic in the impl's
1172 // parameters as well as its own.
1173 let mut params = self.impl_generics.clone();
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago1174 for g in Self::generics_of(&sig.generics)
1175 .into_iter()
1176 .chain(self.impl_trait_params(sig))
1177 {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1178 if !params.contains(&g) {
1179 params.push(g);
1180 }
1181 }
1182 let gens = Self::gen_list(&params);
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1183 let mut parts = Vec::new();
1184 if let Some(self_ty) = recv {
1185 let mutable = matches!(
1186 sig.inputs.first(),
1187 Some(FnArg::Receiver(r))
1188 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1189 );
1190 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1191 parts.push(format!("self: {}", t.render()));
1192 }
1193 let typed: Vec<&syn::PatType> = sig
1194 .inputs
1195 .iter()
1196 .filter_map(|a| match a {
1197 FnArg::Typed(t) => Some(t),
1198 _ => None,
1199 })
1200 .collect();
1201 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
1202 let pname = match &*p.pat {
1203 Pat::Ident(id) => id.ident.to_string(),
1204 Pat::Wild(_) => format!("unused{}", parts.len()),
1205 _ => return Err("only plain identifier parameters are supported".into()),
1206 };
1207 let _ = i;
1208 parts.push(format!("{}: {}", ident(&pname), t.render()));
1209 }
1210 Ok(if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1211 format!("proc {}*{}({})", ident(name), gens, parts.join(", "))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1212 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1213 format!(
1214 "proc {}*{}({}): {}",
1215 ident(name),
1216 gens,
1217 parts.join(", "),
1218 ret.render()
1219 )
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1220 })
1221 }
1222
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago1223 /// `impl Trait` in argument position *is* a generic parameter — that is
1224 /// Rust's own desugaring, `fn f(x: impl T)` for `fn f<A: T>(x: A)`. The
1225 /// bound is dropped like any other, so what is left is a fresh parameter.
1226 /// In return position it is an opaque type instead, and has no equivalent.
1227 fn impl_trait_params(&self, sig: &syn::Signature) -> Vec<String> {
1228 let mut out = Vec::new();
1229 for a in &sig.inputs {
1230 if let FnArg::Typed(t) = a {
1231 if matches!(&*t.ty, syn::Type::ImplTrait(_)) && self.map_ty(&t.ty).is_err() {
1232 out.push(format!("ImplT{}", out.len() + 1));
1233 }
1234 }
1235 }
1236 out
1237 }
1238
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1239 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 10h ago1240 // `unsafe fn` marks a contract for callers; it does not change what
1241 // 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 ago1242 if sig.asyncness.is_some() {
1243 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
1244 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1245 // 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 ago1246 // they disappear. Type parameters become Nim generic parameters.
1247 // Const parameters have no Nim equivalent and are still rejected.
1248 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 ago1249 return Err(format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1250 "`fn {}` has a const generic parameter, which Nim has no \
1251 equivalent for",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1252 sig.ident
1253 ));
1254 }
1255 let mut params = Vec::new();
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago1256 let mut fresh = 0usize;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1257 for a in &sig.inputs {
1258 if let FnArg::Typed(t) = a {
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago1259 match self.map_ty(&t.ty) {
1260 Ok(n) => params.push(n),
1261 Err(e) => {
1262 if matches!(&*t.ty, syn::Type::ImplTrait(_)) {
1263 fresh += 1;
1264 params.push(Nim::Named(format!("ImplT{fresh}"), vec![]));
1265 } else {
1266 return Err(e);
1267 }
1268 }
1269 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1270 }
1271 }
1272 let ret = match &sig.output {
1273 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1274 // A returned `&[T]` is a borrow of the caller's buffer, so it
1275 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
1276 // a `seq`, which `owned()` would do to both.
1277 ReturnType::Type(_, t) => {
1278 let n = self.map_ty(t)?;
1279 if returns_borrow(t) { n } else { n.owned() }
1280 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1281 };
1282 Ok((params, ret))
1283 }
1284
1285 // --------------------------------------------------------------- items
1286
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1287 /// Emit the type definitions only: they must precede every signature.
1288 fn item_types(&mut self, item: &Item) -> Result<(), String> {
1289 if !self.cfg_keeps(item_attrs(item))? {
1290 return Ok(());
1291 }
1292 match item {
1293 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago1294 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago1295 Item::ForeignMod(_) => self.item_inner(item),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1296 Item::Mod(m) if m.content.is_some() => {
1297 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1298 for i in &items {
1299 self.item_types(i)?;
1300 }
1301 Ok(())
1302 }
1303 _ => Ok(()),
1304 }
1305 }
1306
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1307 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1308 if !self.cfg_keeps(item_attrs(item))? {
1309 return Ok(());
1310 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1311 // Types were emitted in their own pass.
1312 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
1313 return Ok(());
1314 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago1315 if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags")
1316 || matches!(item, Item::ForeignMod(_))
1317 {
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago1318 return Ok(()); // emitted with the types
1319 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1320 self.item_inner(item)
1321 }
1322
1323 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 9h ago1324 if !matches!(item, Item::Use(_) | Item::ExternCrate(_) | Item::Mod(_) | Item::Type(_)) {
1325 self.emitted += 1;
1326 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1327 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago1328 Item::Fn(f) => {
1329 let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
1330 self.func_named(&nim, &f.sig, &f.block, None)
1331 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1332 Item::Struct(s) => {
1333 let name = s.ident.to_string();
1334 let fields = self.structs[&name].clone();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1335 let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1336 self.line(&format!("type {}*{} = object", ident(&name), g));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1337 self.indent += 1;
1338 if fields.is_empty() {
1339 self.line("discard");
1340 }
1341 for (fname, fty) in &fields {
1342 self.line(&format!("{}*: {}", ident(fname), fty.render()));
1343 }
1344 self.indent -= 1;
1345 self.blank();
1346 Ok(())
1347 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago1348 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.emit_bitflags(&m.mac),
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 7h ago1349 Item::Macro(m) if path_name(&m.mac.path) == "macro_rules" => Ok(()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1350 Item::Type(_) => Ok(()), // expanded at every use site
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago1351 Item::Trait(t) => {
1352 // We do not model trait resolution, so a declaration generates
1353 // nothing and a use that needed it is rejected where it
1354 // appears. A *default body*, though, is code: dropping it
1355 // would silently remove a method the impls inherit.
1356 for it in &t.items {
1357 if let syn::TraitItem::Fn(f) = it {
1358 if f.default.is_some() {
1359 return Err(format!(
1360 "`trait {}` gives `{}` a default body; trait \
1361 resolution is not modelled, so that body has no \
1362 impl to be emitted into and dropping it would \
1363 remove code",
1364 t.ident, f.sig.ident
1365 ));
1366 }
1367 }
1368 }
1369 Ok(())
1370 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1371 Item::Enum(e) => {
1372 let def = self.enums[&e.ident.to_string()].clone();
1373 self.emit_enum(&def);
1374 Ok(())
1375 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1376 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1377 let t = self.map_ty(&c.ty)?.owned();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1378 // The annotation types the initialiser, exactly as it does for
1379 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
1380 let v = self.expr_at(&c.expr, Some(&t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1381 self.bind(&c.ident.to_string(), t.clone());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1382 // Only a top-level const is exported; `*` on a local is not
1383 // Nim syntax.
1384 let star = if self.indent == 0 { "*" } else { "" };
1385 let line = format!(
1386 "const {}{}: {} = {}",
1387 ident(&c.ident.to_string()),
1388 star,
1389 t.render(),
1390 v.code
1391 );
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1392 self.line(&line);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1393 if self.indent == 0 {
1394 self.blank();
1395 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1396 Ok(())
1397 }
1398 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1399 let outer_g =
1400 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 ago1401 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1402 let outer = self.self_ty.replace(self_ty.clone());
1403 let r = self.impl_body(im, &self_ty);
1404 self.self_ty = outer;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1405 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1406 r
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1407 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1408 // `use` and `extern crate` are resolution directives with no Nim
1409 // analogue once everything is one module.
1410 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago1411 Item::ForeignMod(f) => self.foreign_mod(f),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1412 Item::Mod(m) if m.content.is_some() => {
1413 // An inline `mod` is flattened; Nim has no nested modules in a
1414 // single file.
1415 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1416 for i in &items {
1417 self.item(i)?;
1418 }
1419 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1420 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1421 Item::Mod(m) => {
1422 // Satisfied if that file was passed in too; everything is one
1423 // Nim module, so the declaration itself emits nothing.
1424 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
1425 return Ok(());
1426 }
1427 Err(format!(
1428 "`mod {};` refers to another file that was not passed to \
1429 rustnim; add it to the input list",
1430 m.ident
1431 ))
1432 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago1433 other => Err(format!("unsupported item: {}", item_kind(other))),
1434 }
1435 }
1436
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1437 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
1438 fn none_of(&self, expect: Option<&Nim>) -> String {
1439 match expect {
1440 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
1441 format!("rsNone[{}]()", a[0].render())
1442 }
1443 _ => "rsNone()".to_string(),
1444 }
1445 }
1446
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1447 fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
1448 if let Some((path, _)) = &im.trait_ {
1449 let tr = path_name(path);
1450 if im.items.is_empty() {
1451 return Ok(());
1452 }
1453 if is_fmt_trait(&tr) {
1454 let syn::ImplItem::Fn(m) = &im.items[0] else {
1455 return Err(format!("unsupported item in `impl {tr}`"));
1456 };
1457 return self.fmt_impl(&tr, self_ty, &m.sig, &m.block);
1458 }
1459 if tr == "From" {
1460 let syn::ImplItem::Fn(m) = &im.items[0] else {
1461 return Err("`impl From` must contain `fn from`".into());
1462 };
1463 let name = {
1464 let (params, _) = self.signature(&m.sig)?;
1465 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
1466 self.from_impls[&(type_name(&src), type_name(self_ty))].clone()
1467 };
1468 return self.func_named(&name, &m.sig, &m.block, None);
1469 }
1470 let tyname = type_name(self_ty);
1471 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago1472 if let syn::ImplItem::Const(c) = it {
1473 self.assoc_const(&tyname, c)?;
1474 continue;
1475 }
1476 if matches!(it, syn::ImplItem::Type(_)) {
1477 continue; // a type binding emits nothing
1478 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1479 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago1480 return Err(format!(
1481 "unsupported item in `impl {tr}`: only `fn`, `type` and \
1482 `const` are implemented"
1483 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1484 };
1485 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago1486 let nim =
1487 trait_method_name(&tyname, &tr, &m.sig.ident.to_string(), recv.is_some());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1488 self.func_named(&nim, &m.sig, &m.block, recv)?;
1489 }
1490 return Ok(());
1491 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago1492 let tyname = type_name(self_ty);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1493 for it in &im.items {
1494 match it {
1495 syn::ImplItem::Fn(m) => {
1496 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1497 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
1498 self.func_named(&nim, &m.sig, &m.block, recv)?;
1499 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago1500 syn::ImplItem::Type(_) => {}
1501 syn::ImplItem::Const(c) => self.assoc_const(&tyname, c)?,
1502 _ => {
1503 return Err("only `fn`, `type` and `const` items are supported \
1504 inside `impl`"
1505 .into())
1506 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1507 }
1508 }
1509 Ok(())
1510 }
1511
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago1512 /// An `extern "C" { .. }` block: declarations of symbols someone else
1513 /// defines. Nim's `importc` is the same statement, and both are bound by
1514 /// the C ABI, so the two declarations describe one symbol rather than one
1515 /// being a translation of the other.
1516 fn foreign_mod(&mut self, f: &syn::ItemForeignMod) -> Result<(), String> {
1517 let abi = f
1518 .abi
1519 .name
1520 .as_ref()
1521 .map(|s| s.value())
1522 .unwrap_or_else(|| "C".into());
1523 if abi != "C" {
1524 return Err(format!(
1525 "`extern \"{abi}\"` is not the C ABI; only that one has a Nim \
1526 equivalent"
1527 ));
1528 }
1529 // A const-qualified C pointer needs a type whose C spelling carries
1530 // the `const`; Nim has no such type built in, so one is declared per
1531 // element type actually used.
1532 let mut needed: Vec<Nim> = Vec::new();
1533 for it in &f.items {
1534 if let syn::ForeignItem::Fn(fi) = it {
1535 let (params, ret) = self.signature(&fi.sig)?;
1536 for t in params.iter().chain(std::iter::once(&ret)) {
1537 if let Nim::ConstPtr(inner) = t {
1538 if !matches!(&**inner, Nim::Prim(p) if p == "void")
1539 && !needed.contains(t)
1540 {
1541 needed.push(t.clone());
1542 }
1543 }
1544 }
1545 }
1546 }
1547 for t in &needed {
1548 let Nim::ConstPtr(inner) = t else { continue };
1549 let alias = ty::const_ptr_alias(inner);
1550 if !self.const_ptrs.insert(alias.clone()) {
1551 continue;
1552 }
1553 let c = ty::c_spelling(inner).ok_or_else(|| {
1554 format!(
1555 "`*const {}` has no C spelling we know, so a const-qualified \
1556 declaration cannot be emitted for it",
1557 inner.render()
1558 )
1559 })?;
1560 self.line(&format!(
1561 "type {}* {{.importc: \"const {} *\", nodecl.}} = distinct pointer",
1562 alias, c
1563 ));
1564 }
1565
1566 for it in &f.items {
1567 match it {
1568 syn::ForeignItem::Fn(fi) => {
1569 if fi.sig.variadic.is_some() {
1570 return Err(format!(
1571 "`{}` is variadic; Nim needs `varargs` with a fixed \
1572 calling shape, which this does not give us",
1573 fi.sig.ident
1574 ));
1575 }
1576 let name = fi.sig.ident.to_string();
1577 let head = self.head_of(&name, &fi.sig, None)?;
1578 // `importc` names the C symbol, so the Nim name may differ
1579 // from it without changing what is linked.
1580 self.line(&format!(
1581 "{} {{.importc: \"{}\", cdecl.}}",
1582 head, name
1583 ));
1584 let (params, ret) = self.signature(&fi.sig)?;
1585 self.fns.insert(
1586 (self.cur_mod.clone(), name.clone()),
1587 Sig { params, ret, generics: Vec::new() },
1588 );
1589 self.foreign.insert(name);
1590 }
1591 syn::ForeignItem::Static(st) => {
1592 let t = self.map_ty(&st.ty)?;
1593 let name = st.ident.to_string();
1594 self.line(&format!(
1595 "var {}* {{.importc: \"{}\".}}: {}",
1596 ident(&name),
1597 name,
1598 t.render()
1599 ));
1600 self.bind(&name, t);
1601 }
1602 syn::ForeignItem::Type(_) => {
1603 // An opaque C type: Nim spells it as a distinct object.
1604 continue;
1605 }
1606 _ => return Err("unsupported item in an `extern` block".into()),
1607 }
1608 }
1609 self.blank();
1610 Ok(())
1611 }
1612
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago1613 /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant
1614 /// namespace, so it becomes a module-level const named for both.
1615 fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {
1616 let t = self.map_ty(&c.ty)?.owned();
1617 let v = self.expr_at(&c.expr, Some(&t))?;
1618 let name = format!("{}_{}", tyname, c.ident);
1619 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
1620 self.blank();
1621 self.assoc_consts
1622 .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));
1623 Ok(())
1624 }
1625
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1626 /// The type an operator impl declares for its right-hand operand.
1627 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
1628 let n = type_name(t.as_ref()?);
1629 let sig = self.methods.get(&(n, op_method(op).to_string()))?;
1630 sig.params.get(1).cloned().map(|t| t.unvar())
1631 }
1632
1633 /// The proc implementing `op` for a user type, if there is one.
1634 fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> {
1635 let n = type_name(t.as_ref()?);
1636 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
1637 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago1638 Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1, true))
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago1639 } else {
1640 None
1641 }
1642 }
1643
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago1644 /// Register a `bitflags!` type's operations so call sites resolve.
1645 fn collect_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> {
1646 let input: crate::macros::BitflagsInput = mac
1647 .parse_body()
1648 .map_err(|e| format!("`bitflags!`: {e}"))?;
1649 for def in &input.0 {
1650 let name = def.name.to_string();
1651 let repr = self.map_ty(&def.repr)?;
1652 if !repr.is_integer() {
1653 return Err(format!("`bitflags! {name}` needs an integer representation"));
1654 }
1655 let me = Nim::Named(name.clone(), vec![]);
1656 let b = Nim::Prim("bool".into());
1657 self.structs
1658 .insert(name.clone(), vec![("bitsField".into(), repr.clone())]);
1659 self.type_generics.insert(name.clone(), Vec::new());
1660
1661 let mut m = |n: &str, params: Vec<Nim>, ret: Nim, nim: String| {
1662 self.methods.insert(
1663 (name.clone(), n.to_string()),
1664 Sig { params, ret, generics: Vec::new() },
1665 );
1666 self.statics.insert((name.clone(), n.to_string()), nim);
1667 };
1668 let s1 = vec![me.clone()];
1669 let s2 = vec![me.clone(), me.clone()];
1670 let vs2 = vec![Nim::Var(Box::new(me.clone())), me.clone()];
1671 m("bits", s1.clone(), repr.clone(), format!("{name}_bits"));
1672 m("is_empty", s1.clone(), b.clone(), format!("{name}_is_empty"));
1673 m("is_all", s1.clone(), b.clone(), format!("{name}_is_all"));
1674 m("contains", s2.clone(), b.clone(), format!("{name}_contains"));
1675 m("intersects", s2.clone(), b.clone(), format!("{name}_intersects"));
1676 for (rust, nim) in [
1677 ("union", "union"),
1678 ("intersection", "intersection"),
1679 ("difference", "difference"),
1680 ("symmetric_difference", "symmetric_difference"),
1681 ] {
1682 m(rust, s2.clone(), me.clone(), format!("{name}_{nim}"));
1683 }
1684 for n in ["insert", "remove", "toggle"] {
1685 m(n, vs2.clone(), Nim::Unit, format!("{name}_{n}"));
1686 }
1687 m(
1688 "set",
1689 vec![Nim::Var(Box::new(me.clone())), me.clone(), b.clone()],
1690 Nim::Unit,
1691 format!("{name}_set"),
1692 );
1693 m("empty", vec![], me.clone(), format!("{name}_empty"));
1694 m("all", vec![], me.clone(), format!("{name}_all"));
1695 m(
1696 "from_bits",
1697 vec![repr.clone()],
1698 Nim::Named("Option".into(), vec![me.clone()]),
1699 format!("{name}_from_bits"),
1700 );
1701 m(
1702 "from_bits_truncate",
1703 vec![repr.clone()],
1704 me.clone(),
1705 format!("{name}_from_bits_truncate"),
1706 );
1707 m("complement", s1.clone(), me.clone(), format!("{name}_complement"));
1708
1709 // The operator forms, routed through the same dispatch that a
1710 // hand-written `impl BitOr` would use.
1711 for (op, trait_name, method) in [
1712 ("|", "BitOr", "bitor"),
1713 ("&", "BitAnd", "bitand"),
1714 ("^", "BitXor", "bitxor"),
1715 ("-", "Sub", "sub"),
1716 ("not", "Not", "not"),
1717 ] {
1718 self.op_impls.insert((name.clone(), op.to_string()), ());
1719 let params = if op == "not" { s1.clone() } else { s2.clone() };
1720 self.methods.insert(
1721 (name.clone(), method.to_string()),
1722 Sig { params, ret: me.clone(), generics: Vec::new() },
1723 );
1724 let _ = trait_name;
1725 }
1726 self.bitflags.insert(name);
1727 }
1728 Ok(())
1729 }
1730
1731 /// Emit the Nim for a `bitflags!` type. See `src/macros.rs` for why this
1732 /// is lowered directly rather than by expanding the macro.
1733 fn emit_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> {
1734 let input: crate::macros::BitflagsInput = mac
1735 .parse_body()
1736 .map_err(|e| format!("`bitflags!`: {e}"))?;
1737 for def in &input.0 {
1738 let name = def.name.to_string();
1739 let repr = self.map_ty(&def.repr)?;
1740 let r = repr.render();
1741
1742 self.line(&format!("type {name}* = object"));
1743 self.line(&format!(" bitsField*: {r}"));
1744 self.blank();
1745 self.line(&format!("proc {name}_bits*(x: {name}): {r} = x.bitsField"));
1746
1747 // The constants. A flag's value may name earlier flags, as
1748 // `const ALL = Self::READ.bits() | ..` does, so they are emitted
1749 // in order and each is in scope for the next.
1750 self.push_scope();
1751 self.bind_static_type(&name, &repr);
1752 for (fname, value) in &def.flags {
1753 let v = self.expr_at(value, Some(&repr))?;
1754 self.line(&format!(
1755 "const {}{}* = {}(bitsField: {})",
1756 name, fname, name, v.code
1757 ));
1758 self.flag_consts
1759 .insert((name.clone(), fname.to_string()), format!("{name}{fname}"));
1760 }
1761 self.pop_scope();
1762
1763 let all: Vec<String> = def
1764 .flags
1765 .iter()
1766 .map(|(f, _)| format!("{name}{f}.bitsField"))
1767 .collect();
1768 let all_bits = if all.is_empty() {
1769 format!("{}(0)", r)
1770 } else {
1771 all.join(" or ")
1772 };
1773 self.blank();
1774 self.line(&format!("const {name}AllBits: {r} = {all_bits}"));
1775 self.blank();
1776
1777 for l in [
1778 format!("proc {name}_empty*(): {name} = {name}(bitsField: {r}(0))"),
1779 format!("proc {name}_all*(): {name} = {name}(bitsField: {name}AllBits)"),
1780 format!("proc {name}_is_empty*(x: {name}): bool = x.bitsField == {r}(0)"),
1781 format!("proc {name}_is_all*(x: {name}): bool = (x.bitsField and {name}AllBits) == {name}AllBits"),
1782 format!("proc {name}_contains*(a, b: {name}): bool = (a.bitsField and b.bitsField) == b.bitsField"),
1783 format!("proc {name}_intersects*(a, b: {name}): bool = (a.bitsField and b.bitsField) != {r}(0)"),
1784 format!("proc {name}_union*(a, b: {name}): {name} = {name}(bitsField: a.bitsField or b.bitsField)"),
1785 format!("proc {name}_intersection*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and b.bitsField)"),
1786 format!("proc {name}_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and (not b.bitsField))"),
1787 format!("proc {name}_symmetric_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField xor b.bitsField)"),
1788 // `!x` complements and then masks to `all()`, which is what
1789 // bitflags does and not what a plain `not` would give.
1790 format!("proc {name}_complement*(x: {name}): {name} = {name}(bitsField: (not x.bitsField) and {name}AllBits)"),
1791 format!("proc {name}_from_bits_truncate*(b: {r}): {name} = {name}(bitsField: b and {name}AllBits)"),
1792 format!("proc {name}_from_bits*(b: {r}): Option[{name}] ="),
1793 format!(" if (b and (not {name}AllBits)) != {r}(0): rsNone[{name}]() else: rsSome({name}(bitsField: b))"),
1794 format!("proc {name}_insert*(x: var {name}, o: {name}) = x.bitsField = x.bitsField or o.bitsField"),
1795 format!("proc {name}_remove*(x: var {name}, o: {name}) = x.bitsField = x.bitsField and (not o.bitsField)"),
1796 format!("proc {name}_toggle*(x: var {name}, o: {name}) = x.bitsField = x.bitsField xor o.bitsField"),
1797 format!("proc {name}_set*(x: var {name}, o: {name}, on: bool) ="),
1798 format!(" if on: {name}_insert(x, o) else: {name}_remove(x, o)"),
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago1799 // Named as a trait method with a receiver is: by trait, not by
1800 // type, so Nim overloads on the operand.
1801 format!("proc rsBitOr_bitor*(a, b: {name}): {name} = {name}_union(a, b)"),
1802 format!("proc rsBitAnd_bitand*(a, b: {name}): {name} = {name}_intersection(a, b)"),
1803 format!("proc rsBitXor_bitxor*(a, b: {name}): {name} = {name}_symmetric_difference(a, b)"),
1804 format!("proc rsSub_sub*(a, b: {name}): {name} = {name}_difference(a, b)"),
1805 format!("proc rsNot_not*(a: {name}): {name} = {name}_complement(a)"),
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago1806 ] {
1807 self.line(&l);
1808 }
1809
1810 // Debug prints the set flag names, or `0x0` when empty -- again
1811 // matching the crate rather than a guess.
1812 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1813 self.line(&format!(" result = \"{name}(\""));
1814 self.line(" var first = true");
1815 for (fname, _) in &def.flags {
1816 self.line(&format!(
1817 " if (x.bitsField and {name}{f}.bitsField) == {name}{f}.bitsField and {name}{f}.bitsField != {r}(0):",
1818 f = fname
1819 ));
1820 self.line(" if not first: result.add(\" | \")");
1821 self.line(&format!(" result.add(\"{fname}\")"));
1822 self.line(" first = false");
1823 }
1824 self.line(" if first: result.add(\"0x0\")");
1825 self.line(" result.add(\")\")");
1826 self.blank();
1827 }
1828 Ok(())
1829 }
1830
1831 fn bind_static_type(&mut self, _name: &str, _repr: &Nim) {}
1832
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1833 fn emit_enum(&mut self, def: &EnumDef) {
1834 let name = ident(&def.name);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1835 let g = Self::gen_list(
1836 self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
1837 );
1838 if def.simple && !g.is_empty() {
1839 // A Nim `enum` cannot take parameters; an all-unit generic enum
1840 // has no payload to be generic in anyway, so this would be a
1841 // parameter that never appears.
1842 // Fall through to the object-variant form instead.
1843 }
1844 if def.simple && g.is_empty() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1845 // Every variant is a unit variant, so a plain Nim enum is an exact
1846 // fit: it compares, orders and `case`-checks like Rust's.
1847 self.line(&format!("type {name}* = enum"));
1848 self.indent += 1;
1849 for v in &def.variants {
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago1850 match &v.discriminant {
1851 Some(d) => self.line(&format!("{} = {}", ident(&v.name), d)),
1852 None => self.line(&ident(&v.name)),
1853 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1854 }
1855 self.indent -= 1;
1856 self.blank();
1857 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1858 self.indent += 1;
1859 self.line("case x");
1860 for v in &def.variants {
1861 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
1862 }
1863 self.indent -= 1;
1864 self.blank();
1865 return;
1866 }
1867
1868 // A data-carrying enum is a Nim object variant: one discriminant enum
1869 // plus a branch per variant. This is the same shape the prelude uses
1870 // for `Option` and `Result`.
1871 self.line("type");
1872 self.indent += 1;
1873 self.line(&format!("{}Kind* = enum", name));
1874 self.indent += 1;
1875 for v in &def.variants {
1876 self.line(&def.kind_ident(&v.name));
1877 }
1878 self.indent -= 1;
1879 self.blank();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1880 self.line(&format!("{}*{} = object", name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1881 self.indent += 1;
1882 self.line(&format!("case kind*: {}Kind", name));
1883 for v in &def.variants {
1884 if v.fields.is_empty() {
1885 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
1886 } else {
1887 self.line(&format!("of {}:", def.kind_ident(&v.name)));
1888 self.indent += 1;
1889 for (f, t) in &v.fields {
1890 self.line(&format!("{}*: {}", ident(f), t.render()));
1891 }
1892 self.indent -= 1;
1893 }
1894 }
1895 self.indent -= 2;
1896 self.blank();
1897
1898 for v in &def.variants {
1899 let args: Vec<String> = v
1900 .fields
1901 .iter()
1902 .enumerate()
1903 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
1904 .collect();
1905 let inits: Vec<String> = v
1906 .fields
1907 .iter()
1908 .enumerate()
1909 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
1910 .collect();
1911 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1912 all.extend(inits);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1913 let ret = format!("{}{}", name, g);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1914 self.line(&format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1915 "proc {}*{}({}): {} = {}({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1916 def.ctor_ident(&v.name),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1917 g,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1918 args.join(", "),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1919 ret,
1920 ret,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1921 all.join(", ")
1922 ));
1923 }
1924 self.blank();
1925
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1926 self.line(&format!("proc rsDebug*{}(x: {}{}): string =", g, name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1927 self.indent += 1;
1928 self.line("case x.kind");
1929 for v in &def.variants {
1930 if v.fields.is_empty() {
1931 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
1932 } else {
1933 let parts: Vec<String> = v
1934 .fields
1935 .iter()
1936 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
1937 .collect();
1938 self.line(&format!(
1939 "of {}: \"{}(\" & {} & \")\"",
1940 def.kind_ident(&v.name),
1941 v.name,
1942 parts.join(" & \", \" & ")
1943 ));
1944 }
1945 }
1946 self.indent -= 1;
1947 self.blank();
1948 }
1949
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago1950 /// The concrete type an enum variant constructs, and the `[T]` list to
1951 /// spell at the constructor when the enum is generic.
1952 fn variant_type(
1953 &self,
1954 def: &EnumDef,
1955 expect: Option<&Nim>,
1956 ) -> Result<(Nim, String), String> {
1957 let params = self.type_generics.get(&def.name).cloned().unwrap_or_default();
1958 if params.is_empty() {
1959 return Ok((Nim::Named(def.name.clone(), vec![]), String::new()));
1960 }
1961 match expect {
1962 Some(Nim::Named(n, a)) if *n == def.name && a.len() == params.len() => Ok((
1963 Nim::Named(def.name.clone(), a.clone()),
1964 format!("[{}]", a.iter().map(|t| t.render()).collect::<Vec<_>>().join(", ")),
1965 )),
1966 _ => Err(format!(
1967 "`{}` is a variant of a generic enum, and its type parameters \
1968 cannot be inferred here; annotate the binding or the return type",
1969 def.name
1970 )),
1971 }
1972 }
1973
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago1974 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1975 /// to the enum that declares it.
1976 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
1977 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1978 let last = segs.last()?.clone();
1979 if segs.len() >= 2 {
1980 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
1981 if def.get(&last).is_some() {
1982 return Some((def.clone(), last));
1983 }
1984 }
1985 }
1986 // Unqualified: only unambiguous if exactly one enum declares it.
1987 match self.variant_owner.get(&last) {
1988 Some(owners) if owners.len() == 1 => {
1989 let def = self.enums.get(&owners[0])?;
1990 Some((def.clone(), last))
1991 }
1992 _ => None,
1993 }
1994 }
1995
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago1996 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
1997 ///
1998 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
1999 /// observable result of `{}` is exactly the bytes written. So the method
2000 /// becomes `proc rsDisplay(self: T): string` and every write through the
2001 /// formatter produces that string. A `fmt` body that does anything else
2002 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
2003 /// because those affect the output and this model does not carry them.
2004 /// The window an expression names, if it names one.
2005 fn window_of(&self, e: &Expr) -> Option<Alias> {
2006 match e {
2007 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
2008 Some(a @ Alias::Window { .. }) => Some(a),
2009 _ => None,
2010 },
2011 Expr::Reference(r) => self.window_of(&r.expr),
2012 Expr::Paren(p) => self.window_of(&p.expr),
2013 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
2014 _ => None,
2015 }
2016 }
2017
2018 /// Whether an expression is the `Formatter` parameter of the formatting
2019 /// impl currently being lowered.
2020 fn is_fmt_param(&self, e: &Expr) -> bool {
2021 let Some(f) = &self.fmt_param else { return false };
2022 match e {
2023 Expr::Path(p) => path_name(&p.path) == *f,
2024 Expr::Reference(r) => self.is_fmt_param(&r.expr),
2025 Expr::Paren(p) => self.is_fmt_param(&p.expr),
2026 _ => false,
2027 }
2028 }
2029
2030 fn fmt_impl(
2031 &mut self,
2032 tr: &str,
2033 self_ty: &Nim,
2034 sig: &syn::Signature,
2035 body: &syn::Block,
2036 ) -> Result<(), String> {
2037 let proc_name = fmt_proc(tr);
2038 // The formatter is the parameter after `self`.
2039 let f = sig
2040 .inputs
2041 .iter()
2042 .filter_map(|a| match a {
2043 FnArg::Typed(t) => match &*t.pat {
2044 Pat::Ident(i) => Some(i.ident.to_string()),
2045 _ => None,
2046 },
2047 _ => None,
2048 })
2049 .next()
2050 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
2051
2052 self.push_scope();
2053 self.bind("self", self_ty.clone());
2054 let saved = self.fmt_param.replace(f);
2055 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 ago2056 // No assignment target: a formatter write *appends*, because a `fmt`
2057 // body may write repeatedly -- `UpperHex` writes once per byte in a
2058 // loop -- and assigning would keep only the last one.
2059 let outer_target = self.target.take();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2060
2061 self.line(&format!(
2062 "proc {}*(self: {}): string =",
2063 proc_name,
2064 self_ty.render()
2065 ));
2066 self.indent += 1;
2067 let before = self.out.len();
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago2068 let tail = self.block_body(body)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2069 self.emit_tail(tail);
2070 if self.out.len() == before {
2071 self.line("discard");
2072 }
2073 self.indent -= 1;
2074
2075 self.target = outer_target;
2076 self.ret = outer_ret;
2077 self.fmt_param = saved;
2078 self.pop_scope();
2079 self.blank();
2080 Ok(())
2081 }
2082
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2083 fn func(
2084 &mut self,
2085 sig: &syn::Signature,
2086 body: &syn::Block,
2087 recv: Option<Nim>,
2088 ) -> Result<(), String> {
2089 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2090 self.func_named(&name.clone(), sig, body, recv)
2091 }
2092
2093 fn func_named(
2094 &mut self,
2095 name: &str,
2096 sig: &syn::Signature,
2097 body: &syn::Block,
2098 recv: Option<Nim>,
2099 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2100 let (ptys, ret) = self.signature(sig)?;
2101
2102 self.push_scope();
2103 let mut rendered: Vec<String> = Vec::new();
2104
2105 if let Some(self_ty) = recv {
2106 // `&mut self` and `mut self` both mean the body may mutate the
2107 // receiver; only the former is observable by the caller, and a Nim
2108 // `var` parameter is the faithful spelling of that.
2109 let mutable = matches!(
2110 sig.inputs.first(),
2111 Some(FnArg::Receiver(r))
2112 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
2113 );
2114 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
2115 rendered.push(format!("self: {}", t.render()));
2116 self.bind("self", self_ty);
2117 }
2118
2119 let typed: Vec<&syn::PatType> = sig
2120 .inputs
2121 .iter()
2122 .filter_map(|a| match a {
2123 FnArg::Typed(t) => Some(t),
2124 _ => None,
2125 })
2126 .collect();
2127 for (p, t) in typed.iter().zip(ptys.iter()) {
2128 let pname = match &*p.pat {
2129 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2130 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
2131 // still needs a name for it.
2132 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2133 _ => return Err("only plain identifier parameters are supported".into()),
2134 };
2135 rendered.push(format!("{}: {}", ident(&pname), t.render()));
2136 // Inside the body a `var T` parameter is used exactly like a `T`.
2137 self.bind(&pname, t.clone().owned());
2138 }
2139
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2140 let mut gparams = self.impl_generics.clone();
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago2141 for g in Self::generics_of(&sig.generics)
2142 .into_iter()
2143 .chain(self.impl_trait_params(sig))
2144 {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2145 if !gparams.contains(&g) {
2146 gparams.push(g);
2147 }
2148 }
2149 let gens = Self::gen_list(&gparams);
2150 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 ago2151 let head = if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2152 format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2153 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2154 format!(
2155 "proc {}*{}({}): {} =",
2156 ident(name),
2157 gens,
2158 rendered.join(", "),
2159 ret.render()
2160 )
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2161 };
2162 self.line(&head);
2163 self.indent += 1;
2164 let outer_ret = self.ret.replace(ret.clone());
2165
2166 // A Rust fn's trailing expression is its return value. Naming Nim's
2167 // implicit `result` as the target makes that true whether the tail is
2168 // a plain expression or an `if`/`match` with statement arms.
2169 let outer_target = if ret == Nim::Unit {
2170 self.target.take()
2171 } else {
2172 self.target.replace(("result".to_string(), Some(ret.clone())))
2173 };
2174 let before = self.out.len();
2175 let tail = self.block_body_at(body, Some(&ret))?;
2176 self.target = outer_target;
2177 match tail {
2178 Some(v) if ret != Nim::Unit => {
2179 let code = v.code.clone();
2180 self.line(&format!("result = {code}"));
2181 }
2182 Some(v) => {
2183 // A trailing expression in a `()`-returning fn is evaluated for
2184 // its effect; Nim requires an explicit discard.
2185 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2186 if needs_discard && !v.code.is_empty() {
2187 let code = v.code.clone();
2188 self.line(&format!("discard {code}"));
2189 }
2190 }
2191 None => {}
2192 }
2193 if self.out.len() == before {
2194 self.line("discard");
2195 }
2196
2197 self.indent -= 1;
2198 self.ret = outer_ret;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2199 self.fn_generics = outer_fg;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2200 self.pop_scope();
2201 self.blank();
2202 Ok(())
2203 }
2204
2205 // ---------------------------------------------------------- statements
2206
2207 /// Lower a block's statements. Returns the block's trailing expression,
2208 /// if it has one, *without* emitting it — the caller decides whether that
2209 /// value is a return value, a binding, or discarded.
2210 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
2211 self.block_body_at(b, None)
2212 }
2213
2214 fn block_body_at(
2215 &mut self,
2216 b: &syn::Block,
2217 expect: Option<&Nim>,
2218 ) -> Result<Option<Val>, String> {
2219 // An assignment target belongs to *this* block's trailing expression
2220 // only. A non-final `if` is a statement and must not assign anything.
2221 let target = self.target.take();
2222 let n = b.stmts.len();
2223 let mut tail = None;
2224 for (i, st) in b.stmts.iter().enumerate() {
2225 let last = i + 1 == n;
2226 match st {
2227 Stmt::Expr(e, None) if last && expressible(e) => {
2228 tail = Some(self.expr_at(e, expect)?)
2229 }
2230 Stmt::Expr(e, None) if last => {
2231 // A trailing `if`/`match` with statement arms, or a loop.
2232 // Lower it as statements; if this block's value is wanted,
2233 // each arm assigns it.
2234 match &target {
2235 Some((t, ty)) => {
2236 let (t, ty) = (t.clone(), ty.clone());
2237 self.assign_from(e, &t, ty.as_ref())?;
2238 }
2239 None => self.stmt(st)?,
2240 }
2241 }
2242 _ => self.stmt(st)?,
2243 }
2244 }
2245 self.target = target;
2246 Ok(tail)
2247 }
2248
2249 /// Lower a block in statement position (loop bodies, `if` arms).
2250 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
2251 self.push_scope();
2252 self.indent += 1;
2253 let before = self.out.len();
2254 let want = self.target.clone().and_then(|(_, t)| t);
2255 let tail = self.block_body_at(b, want.as_ref())?;
2256 self.emit_tail(tail);
2257 if self.out.len() == before {
2258 self.line("discard");
2259 }
2260 self.indent -= 1;
2261 self.pop_scope();
2262 Ok(())
2263 }
2264
2265 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
2266 match s {
2267 Stmt::Local(l) => self.local(l),
2268 Stmt::Expr(e, _) => {
2269 let v = self.expr_stmt(e)?;
2270 if let Some(v) = v {
2271 // A bare expression with a value must be discarded in Nim.
2272 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2273 let code = v.code.clone();
2274 if needs {
2275 self.line(&format!("discard {code}"));
2276 } else if !code.is_empty() {
2277 self.line(&code);
2278 }
2279 }
2280 Ok(())
2281 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago2282 // A `const` declared inside a function body is local to it, and
2283 // must be emitted here rather than skipped as an already-emitted
2284 // top-level type.
2285 Stmt::Item(i) => self.item_inner(i),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2286 Stmt::Macro(m) => {
2287 let line = self.macro_call(&m.mac)?;
2288 self.line(&line);
2289 Ok(())
2290 }
2291 }
2292 }
2293
2294 fn local(&mut self, l: &Local) -> Result<(), String> {
2295 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
2296 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
2297 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2298 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 ago2299 _ => return Err("only `let <ident>` bindings are supported".into()),
2300 },
2301 Pat::Wild(_) => ("_".into(), false, None),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago2302 Pat::Tuple(t) => return self.local_tuple(l, t),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2303 _ => return Err("destructuring `let` is not implemented yet".into()),
2304 };
2305
2306 let Some(init) = &l.init else {
2307 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
2308 // not. Rust's own rules make reading it before assignment illegal,
2309 // so the two agree on every program rustc accepts.
2310 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
2311 let t = t.owned();
2312 self.line(&format!("var {}: {}", ident(&name), t.render()));
2313 self.bind(&name, t);
2314 return Ok(());
2315 };
2316 if init.diverge.is_some() {
2317 return Err("`let ... else` is not implemented yet".into());
2318 }
2319
2320 if !expressible(&init.expr) && name != "_" {
2321 // The initialiser is an `if`/`match` whose arms are statements.
2322 // Declare first, then let each arm assign into the binding.
2323 let t = ann
2324 .clone()
2325 .ok_or_else(|| {
2326 format!(
2327 "`let {name} = match/if ...` needs a type annotation: \
2328 its arms are statements, so the binding must be \
2329 declared before they run"
2330 )
2331 })?
2332 .owned();
2333 self.line(&format!("var {}: {}", ident(&name), t.render()));
2334 self.bind(&name, t.clone());
2335 let target = ident(&name);
2336 return self.assign_from(&init.expr, &target, Some(&t));
2337 }
2338
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago2339 // `let it = xs.chunks_exact(k)` binds an iterator, not a value.
2340 if is_iterator_expr(&init.expr) {
2341 let it = self.resolve_iter(&init.expr)?;
2342 self.bind_alias(&name, Alias::Iterator(Box::new(it)));
2343 return Ok(());
2344 }
2345
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2346 let v = self.expr_at(&init.expr, ann.as_ref())?;
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 9h ago2347
2348 // `let s = &buf[..n]` binds a view of a place that is already in
2349 // scope. Nim's borrow checker will not let a `let` borrow out of a
2350 // local, and there is nothing to materialise anyway -- a view is a
2351 // reference. Binding it as an alias substitutes the same expression at
2352 // each use, which re-evaluates nothing because the initialiser is a
2353 // place expression with no side effects.
2354 if v.window.is_none()
2355 && matches!(v.ty, Some(Nim::OpenArray(_)))
2356 && is_pure_place(&init.expr)
2357 {
2358 let t = v.ty.clone().unwrap();
2359 let elem = match &t {
2360 Nim::OpenArray(e) => Some((**e).clone()),
2361 _ => None,
2362 };
2363 self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) });
2364 let _ = elem;
2365 return Ok(());
2366 }
2367
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2368 if let Some(w) = v.window.clone() {
2369 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
2370 // view into the caller's buffer. Copying it into a `seq` would
2371 // still print the right bytes but would stop writes reaching the
2372 // caller, so it is bound as an alias.
2373 if v.guard.is_some() && v.guard_err.is_some() {
2374 return Err(format!(
2375 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
2376 which Nim cannot represent; apply `?` or `unwrap()` to it \
2377 in the same expression"
2378 ));
2379 }
2380 self.bind_alias(&name, w);
2381 return Ok(());
2382 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago2383 // A `let` binding a borrow keeps the view: `let res = encode(..)?`
2384 // names the caller's buffer, and copying it into a `seq` would still
2385 // print the right bytes while silently breaking the aliasing.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2386 let t = match (ann, &v.ty) {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago2387 (Some(a), _) => a.unvar(),
2388 (None, Some(t)) => t.clone().unvar(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2389 (None, None) => {
2390 return Err(format!(
2391 "cannot infer the type of `let {name}`; annotate it — \
2392 guessing here would change integer width, and with it the \
2393 meaning of any arithmetic on `{name}`"
2394 ))
2395 }
2396 };
2397
2398 if name == "_" {
2399 let code = v.code.clone();
2400 self.line(&format!("discard {code}"));
2401 return Ok(());
2402 }
2403 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
2404 // 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 ago2405 //
2406 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
2407 // Rust may write through it, and Nim only accepts a `var` where a
2408 // `var` parameter is wanted, so the binding has to be one.
2409 let mutable = mutable || is_mut_borrow(&init.expr);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2410 let kw = if mutable { "var" } else { "let" };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago2411 // Inside a generic proc the binding's type may mention a parameter Nim
2412 // will infer; naming it in an annotation would not resolve.
2413 let line = if self.mentions_type_param(&t) {
2414 format!("{} {} = {}", kw, ident(&name), v.code)
2415 } else {
2416 format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)
2417 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2418 self.line(&line);
2419 self.bind(&name, t);
2420 Ok(())
2421 }
2422
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago2423 /// `let (a, b) = ..` — tuple destructuring.
2424 fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> {
2425 let Some(init) = &l.init else {
2426 return Err("a destructuring `let` needs an initialiser".into());
2427 };
2428 let names: Vec<(String, bool)> = t
2429 .elems
2430 .iter()
2431 .map(|p| match p {
2432 Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())),
2433 Pat::Wild(_) => Ok(("_".to_string(), false)),
2434 _ => Err("only plain identifiers are supported in a destructuring `let`"),
2435 })
2436 .collect::<Result<_, _>>()?;
2437
2438 // `split_at` hands back two *views* of the same slice. Nim has no
2439 // tuple of views, and there is nothing to materialise anyway, so each
2440 // name becomes a window into the original.
2441 if let Expr::MethodCall(m) = &*init.expr {
2442 let mname = m.method.to_string();
2443 if (mname == "split_at" || mname == "split_at_mut")
2444 && m.args.len() == 1
2445 && names.len() == 2
2446 {
2447 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
2448 let at = self.expr(&m.args[0])?;
2449 let cut = self.fresh("Cut");
2450 self.line(&format!("let {}: int = int({})", cut, at.code));
2451 self.bind_alias(
2452 &names[0].0,
2453 Alias::Window {
2454 code: code.clone(),
2455 off: base.clone(),
2456 len: cut.clone(),
2457 elem: elem.clone(),
2458 },
2459 );
2460 self.bind_alias(
2461 &names[1].0,
2462 Alias::Window {
2463 code,
2464 off: format!("({} + {})", base, cut),
2465 len: format!("({} - {})", len, cut),
2466 elem,
2467 },
2468 );
2469 return Ok(());
2470 }
2471 }
2472
2473 let v = self.expr(&init.expr)?;
2474 let tys = match &v.ty {
2475 Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(),
2476 _ => {
2477 return Err(format!(
2478 "cannot destructure this into {} bindings: its type is not a \
2479 tuple of that many elements",
2480 names.len()
2481 ))
2482 }
2483 };
2484 let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" };
2485 let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect();
2486 self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code));
2487 for ((n, _), t) in names.iter().zip(tys) {
2488 self.bind(n, t);
2489 }
2490 Ok(())
2491 }
2492
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2493 /// Expressions that are statements in Rust and statements in Nim too
2494 /// (control flow). Returns `None` when it emitted lines itself.
2495 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
2496 match e {
2497 Expr::If(_) => {
2498 self.if_stmt(e)?;
2499 Ok(None)
2500 }
2501 Expr::While(w) => {
2502 if w.label.is_some() {
2503 return Err("loop labels are not implemented yet".into());
2504 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2505 self.in_loop_cond = true;
2506 let c = self.expr(&w.cond);
2507 self.in_loop_cond = false;
2508 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2509 self.line(&format!("while {}:", c.code));
2510 let saved = self.target.take();
2511 self.nested_block(&w.body)?;
2512 self.target = saved;
2513 Ok(None)
2514 }
2515 Expr::Loop(l) => {
2516 if l.label.is_some() {
2517 return Err("loop labels are not implemented yet".into());
2518 }
2519 self.line("while true:");
2520 let saved = self.target.take();
2521 self.nested_block(&l.body)?;
2522 self.target = saved;
2523 Ok(None)
2524 }
2525 Expr::ForLoop(f) => {
2526 self.for_loop(f)?;
2527 Ok(None)
2528 }
2529 Expr::Block(b) => {
2530 if b.label.is_some() {
2531 return Err("block labels are not implemented yet".into());
2532 }
2533 self.line("block:");
2534 self.nested_block(&b.block)?;
2535 Ok(None)
2536 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2537 Expr::Unsafe(u) => {
2538 // Transparent in statement position too, for the same reason.
2539 self.nested_block_flat(&u.block)?;
2540 Ok(None)
2541 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2542 Expr::Match(_) => {
2543 self.match_stmt(e)?;
2544 Ok(None)
2545 }
2546 Expr::Return(r) => {
2547 match &r.expr {
2548 Some(e) => {
2549 let want = self.ret.clone();
2550 let v = self.expr_at(e, want.as_ref())?;
2551 self.line(&format!("return {}", v.code));
2552 }
2553 None => self.line("return"),
2554 }
2555 Ok(None)
2556 }
2557 Expr::Break(b) => {
2558 if b.expr.is_some() || b.label.is_some() {
2559 return Err("`break` with a value or a label is not implemented yet".into());
2560 }
2561 self.line("break");
2562 Ok(None)
2563 }
2564 Expr::Continue(c) => {
2565 if c.label.is_some() {
2566 return Err("labelled `continue` is not implemented yet".into());
2567 }
2568 self.line("continue");
2569 Ok(None)
2570 }
2571 Expr::Assign(a) => {
2572 let lhs = self.expr(&a.left)?;
2573 if !expressible(&a.right) {
2574 let target = lhs.code.clone();
2575 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
2576 }
2577 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
2578 self.line(&format!("{} = {}", lhs.code, rhs.code));
2579 Ok(None)
2580 }
2581 Expr::Binary(b) if is_compound(&b.op) => {
2582 let lhs = self.expr(&b.left)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago2583 // A compound assignment on a user type goes to that type's own
2584 // `impl OpAssign`, not to Nim's built-in operator.
2585 if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) {
2586 // The impl's own parameter type types the right operand,
2587 // so `b_vec *= 4` takes 4 at the width the impl declares.
2588 let want = self.op_param(&lhs.ty, compound_symbol(&b.op));
2589 let rhs = self.expr_at(&b.right, want.as_ref())?;
2590 self.line(&format!("{}({}, {})", f, lhs.code, rhs.code));
2591 return Ok(None);
2592 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2593 // `i += 1` must widen the literal to `i`'s type, not to the
2594 // i32 an unconstrained Rust literal would default to.
2595 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
2596 let op = self.bin_op(&b.op, &lhs, &rhs)?;
2597 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
2598 // both languages, so the expanded form is always correct.
2599 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
2600 Ok(None)
2601 }
2602 Expr::Macro(m) => {
2603 let line = self.macro_call(&m.mac)?;
2604 self.line(&line);
2605 Ok(None)
2606 }
2607 _ => Ok(Some(self.expr(e)?)),
2608 }
2609 }
2610
2611 /// Lower `e` in statement position, assigning each arm's value to
2612 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
2613 /// the trip when their arms are too big for a Nim `if`-expression.
2614 fn assign_from(
2615 &mut self,
2616 e: &Expr,
2617 target: &str,
2618 expect: Option<&Nim>,
2619 ) -> Result<(), String> {
2620 let saved = self.target.replace((target.to_string(), expect.cloned()));
2621 let r = match e {
2622 Expr::If(_) => self.if_stmt(e),
2623 Expr::Match(_) => self.match_stmt(e),
2624 other => {
2625 let v = self.expr_at(other, expect)?;
2626 self.line(&format!("{} = {}", target, v.code));
2627 Ok(())
2628 }
2629 };
2630 self.target = saved;
2631 r
2632 }
2633
2634 /// Emit a block's value into the active assignment target, if there is
2635 /// one, or discard it if there is not.
2636 fn emit_tail(&mut self, v: Option<Val>) {
2637 let Some(v) = v else { return };
2638 match self.target.clone() {
2639 Some((t, _)) => {
2640 let code = v.code.clone();
2641 self.line(&format!("{t} = {code}"));
2642 }
2643 None => {
2644 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2645 let code = v.code.clone();
2646 if needs {
2647 self.line(&format!("discard {code}"));
2648 } else if !code.is_empty() {
2649 self.line(&code);
2650 }
2651 }
2652 }
2653 }
2654
2655 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
2656 let Expr::If(i) = e else { unreachable!() };
2657 if let Expr::Let(_) = &*i.cond {
2658 return Err("`if let` is not implemented yet".into());
2659 }
2660 let c = self.expr(&i.cond)?;
2661 self.line(&format!("if {}:", c.code));
2662 self.nested_block(&i.then_branch)?;
2663 match &i.else_branch {
2664 None => {}
2665 Some((_, els)) => match &**els {
2666 Expr::If(_) => {
2667 // Nim needs `elif`; splice the nested `if` in as one.
2668 let mark = self.out.len();
2669 self.if_stmt(els)?;
2670 let tail = self.out.split_off(mark);
2671 let indent = " ".repeat(self.indent);
2672 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
2673 }
2674 Expr::Block(b) => {
2675 self.line("else:");
2676 self.nested_block(&b.block)?;
2677 }
2678 _ => return Err("unsupported `else` form".into()),
2679 },
2680 }
2681 Ok(())
2682 }
2683
2684 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
2685 if f.label.is_some() {
2686 return Err("loop labels are not implemented yet".into());
2687 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2688 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2689
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2690 // One index loop drives the whole chain. Rust's adaptors are lazy and
2691 // compose; resolving them to an index and binding each name to an
2692 // lvalue reproduces that without materialising anything.
2693 let i = self.fresh("Idx");
2694 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
2695 self.indent += 1;
2696 self.push_scope();
2697 let before = self.out.len();
2698
2699 self.bind_pattern(&f.pat, &it, &i)?;
2700
2701 let saved = self.target.take();
2702 if let Some(v) = self.block_body(&f.body)? {
2703 let code = v.code.clone();
2704 self.line(&format!("discard {code}"));
2705 }
2706 self.target = saved;
2707 if self.out.len() == before {
2708 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2709 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2710 self.pop_scope();
2711 self.indent -= 1;
2712 Ok(())
2713 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2714
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2715 /// Resolve a chain of iterator adaptors into a single `Iter`.
2716 ///
2717 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
2718 /// `filter`, `take_while` and friends are rejected rather than partially
2719 /// honoured: silently dropping an adaptor would change which elements the
2720 /// loop visits.
2721 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
2722 match e {
2723 Expr::Reference(r) => self.resolve_iter(&r.expr),
2724 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2725 Expr::Range(r) => {
2726 let lo = match &r.start {
2727 Some(e) => self.expr(e)?,
2728 None => return Err("a `for` over `..n` needs a start bound".into()),
2729 };
2730 let hi = match &r.end {
2731 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2732 None => {
2733 return Err("a `for` over an unbounded range would not terminate".into())
2734 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2735 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2736 let ty = lo.ty.clone().or(hi.ty.clone());
2737 Ok(Iter::Range {
2738 lo: lo.code,
2739 hi: hi.code,
2740 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
2741 ty,
2742 })
2743 }
2744 Expr::MethodCall(m) => {
2745 let name = m.method.to_string();
2746 match name.as_str() {
2747 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
2748 let mut it = self.resolve_iter(&m.receiver)?;
2749 if name == "iter_mut" {
2750 if let Iter::Elems { mutable, .. } = &mut it {
2751 *mutable = true;
2752 }
2753 }
2754 Ok(it)
2755 }
2756 "enumerate" if m.args.is_empty() => {
2757 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
2758 }
2759 "zip" if m.args.len() == 1 => {
2760 let a = self.resolve_iter(&m.receiver)?;
2761 let b = self.resolve_iter(&m.args[0])?;
2762 Ok(Iter::Zip(Box::new(a), Box::new(b)))
2763 }
2764 "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 10h ago2765 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 ago2766 let k = self.expr(&m.args[0])?;
2767 Ok(Iter::Chunks {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2768 code,
2769 base,
2770 len,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2771 k: k.code,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2772 elem,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2773 mutable: name.ends_with("_mut"),
2774 })
2775 }
2776 "windows" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2777 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 ago2778 let k = self.expr(&m.args[0])?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2779 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 ago2780 }
2781 other => Err(format!(
2782 "iterator adaptor `.{other}()` is not implemented; it has \
2783 no index-loop equivalent here, and dropping it would \
2784 change which elements the loop visits"
2785 )),
2786 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2787 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago2788 Expr::Path(p) => {
2789 let n = path_name(&p.path);
2790 if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) {
2791 return Ok((*it).clone());
2792 }
2793 if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) {
2794 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
2795 }
2796 let v = self.expr(e)?;
2797 Ok(Iter::Elems {
2798 len: format!("{}.len", v.code),
2799 elem: elem_of(&v.ty),
2800 code: v.code,
2801 off: "0".into(),
2802 mutable: false,
2803 })
2804 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2805 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2806 // A `for` binding that is itself a window iterates that window,
2807 // not the whole container it points into.
2808 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 10h ago2809 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 ago2810 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2811 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2812 Ok(Iter::Elems {
2813 len: format!("{}.len", v.code),
2814 elem: elem_of(&v.ty),
2815 code: v.code,
2816 off: "0".into(),
2817 mutable: false,
2818 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2819 }
2820 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2821 }
2822
2823 /// Bind a `for` pattern against a resolved iterator at index `i`.
2824 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
2825 match (p, it) {
2826 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
2827 self.bind_pattern(&t.elems[0], a, i)?;
2828 self.bind_pattern(&t.elems[1], b, i)
2829 }
2830 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
2831 if let Pat::Ident(id) = &t.elems[0] {
2832 let n = id.ident.to_string();
2833 // Rust's `enumerate` counts in `usize`.
2834 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
2835 self.bind(&n, Nim::Prim("uint".into()));
2836 }
2837 self.bind_pattern(&t.elems[1], inner, i)
2838 }
2839 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
2840 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
2841 ),
2842 (Pat::Wild(_), _) => Ok(()),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2843 // `for &byte in xs` — the `&` destructures the reference, which in
2844 // Nim is already the value.
2845 (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
2846 (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 ago2847 (Pat::Ident(id), _) => {
2848 let name = id.ident.to_string();
2849 match it {
2850 Iter::Range { lo, ty, .. } => {
2851 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
2852 // The loop counts from zero; the range's own start is
2853 // added back so the binding has Rust's value and type.
2854 self.line(&format!(
2855 "let {}: {} = {}({}) + {}",
2856 ident(&name),
2857 t.render(),
2858 t.render(),
2859 i,
2860 lo
2861 ));
2862 self.bind(&name, t);
2863 Ok(())
2864 }
2865 Iter::Elems { code, off, elem, mutable, .. } => {
2866 let access = if off == "0" {
2867 format!("{}[{}]", code, i)
2868 } else {
2869 format!("{}[{} + {}]", code, off, i)
2870 };
2871 if *mutable {
2872 // An alias, not a copy: assigning through the
2873 // binding must reach the original element.
2874 self.bind_alias(
2875 &name,
2876 Alias::Value { code: access, ty: elem.clone() },
2877 );
2878 } else {
2879 let t = elem
2880 .clone()
2881 .ok_or("cannot infer the element type of this `for`")?;
2882 self.line(&format!(
2883 "let {}: {} = {}",
2884 ident(&name),
2885 t.render(),
2886 access
2887 ));
2888 self.bind(&name, t);
2889 }
2890 Ok(())
2891 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2892 Iter::Chunks { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2893 self.bind_alias(
2894 &name,
2895 Alias::Window {
2896 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2897 off: format!("({} + {} * int({}))", base, i, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2898 len: format!("int({})", k),
2899 elem: elem.clone(),
2900 },
2901 );
2902 Ok(())
2903 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2904 Iter::Windows { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2905 self.bind_alias(
2906 &name,
2907 Alias::Window {
2908 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago2909 off: format!("({} + {})", base, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago2910 len: format!("int({})", k),
2911 elem: elem.clone(),
2912 },
2913 );
2914 Ok(())
2915 }
2916 // Handled above: a zip or enumerate needs a tuple pattern,
2917 // and binding one name to the pair is not supported.
2918 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
2919 }
2920 }
2921 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2922 }
2923 }
2924
2925 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
2926 let Expr::Match(m) = e else { unreachable!() };
2927 let scrut = self.expr(&m.expr)?;
2928 let t = scrut
2929 .ty
2930 .clone()
2931 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2932 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2933 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2934
2935 // A `match` whose arms neither bind nor guard is a Nim `case`, which
2936 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
2937 // an if/elif chain, because Nim's `case` cannot destructure.
2938 let plain = m.arms.iter().all(|a| {
2939 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
2940 });
2941 if plain {
2942 self.match_case(m, &name, &t)
2943 } else {
2944 self.match_chain(m, &name, &t)
2945 }
2946 }
2947
2948 fn match_case(
2949 &mut self,
2950 m: &syn::ExprMatch,
2951 name: &str,
2952 t: &Nim,
2953 ) -> Result<(), String> {
2954 // A variant object is discriminated by its `kind` field.
2955 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
2956 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 ago2957
2958 let mut saw_wild = false;
2959 for arm in &m.arms {
2960 match &arm.pat {
2961 Pat::Wild(_) => {
2962 saw_wild = true;
2963 self.line("else:");
2964 }
2965 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2966 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago2967 self.line(&format!("of {}:", labels.join(", ")));
2968 }
2969 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago2970 self.arm_body(&arm.body)?;
2971 }
2972 if !saw_wild && !self.case_is_total(t, m) {
2973 // Rust checked exhaustiveness already, but Nim cannot always see
2974 // it -- an integer `case` needs every value covered -- so make the
2975 // unreachable arm explicit rather than leave a compile error.
2976 self.line("else:");
2977 self.line(" rsPanic(\"unreachable match arm\")");
2978 }
2979 Ok(())
2980 }
2981
2982 /// Whether a Nim `case` over this type is already total, in which case
2983 /// adding an `else` would be a compile error rather than a safety net.
2984 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
2985 let Nim::Named(n, _) = t else { return false };
2986 let Some(def) = self.enums.get(n) else { return false };
2987 def.variants.len() == m.arms.len()
2988 }
2989
2990 /// The if/elif form, for arms that bind or destructure.
2991 fn match_chain(
2992 &mut self,
2993 m: &syn::ExprMatch,
2994 name: &str,
2995 t: &Nim,
2996 ) -> Result<(), String> {
2997 let mut first = true;
2998 let mut closed = false;
2999 for arm in &m.arms {
3000 let (pat, guard) = match &arm.pat {
3001 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
3002 p => (p, None),
3003 };
3004 if guard.is_some() && binds(pat) {
3005 return Err("a `match` guard on a binding pattern is not \
3006 implemented yet"
3007 .into());
3008 }
3009 let test = self.pat_test(pat, name, t)?;
3010 let test = match (test, guard) {
3011 (Some(t), Some(g)) => {
3012 let g = self.expr(g)?;
3013 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3014 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3015 (None, Some(g)) => Some(self.expr(g)?.code),
3016 (t, None) => t,
3017 };
3018 match test {
3019 Some(test) => {
3020 self.line(&format!(
3021 "{} {}:",
3022 if first { "if" } else { "elif" },
3023 test
3024 ));
3025 first = false;
3026 }
3027 None => {
3028 // An irrefutable pattern: everything left falls here.
3029 if first {
3030 self.line("block:");
3031 } else {
3032 self.line("else:");
3033 }
3034 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3035 }
3036 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3037 self.indent += 1;
3038 self.push_scope();
3039 let before = self.out.len();
3040 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3041 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3042 self.arm_body_at(&arm.body, before)?;
3043 self.pop_scope();
3044 if closed {
3045 break;
3046 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3047 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3048 if !closed {
3049 // Rust proved this unreachable; Nim cannot see that, and leaving
3050 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3051 self.line("else:");
3052 self.line(" rsPanic(\"unreachable match arm\")");
3053 }
3054 Ok(())
3055 }
3056
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3057 /// The condition that selects this arm, or `None` if it always matches.
3058 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
3059 Ok(match p {
3060 Pat::Wild(_) => None,
3061 Pat::Ident(i) if i.subpat.is_none() => None,
3062 Pat::Or(o) => {
3063 let mut parts = Vec::new();
3064 for c in &o.cases {
3065 match self.pat_test(c, name, t)? {
3066 Some(x) => parts.push(x),
3067 None => return Ok(None),
3068 }
3069 }
3070 Some(format!("({})", parts.join(" or ")))
3071 }
3072 Pat::Lit(_) | Pat::Range(_) => {
3073 let labels = self.pat_labels(p, Some(t))?;
3074 Some(match p {
3075 Pat::Range(_) => format!("({} in {})", name, labels[0]),
3076 _ => format!("({} == {})", name, labels[0]),
3077 })
3078 }
3079 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
3080 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
3081 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
3082 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
3083 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
3084 _ => return Err("unsupported `match` pattern".into()),
3085 })
3086 }
3087
3088 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
3089 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
3090 let last = path_name(path);
3091 match last.as_str() {
3092 "Ok" => return Ok(format!("{name}.ok")),
3093 "Err" => return Ok(format!("(not {name}.ok)")),
3094 "Some" => return Ok(format!("{name}.has")),
3095 "None" => return Ok(format!("(not {name}.has)")),
3096 _ => {}
3097 }
3098 let Some((def, v)) = self.resolve_variant(path) else {
3099 return Err(format!(
3100 "`{last}` in a pattern is not a known enum variant; if it names \
3101 an enum declared in another module, that is not implemented yet"
3102 ));
3103 };
3104 if let Nim::Named(n, _) = t {
3105 if *n != def.name {
3106 return Err(format!(
3107 "pattern `{}::{}` does not match the scrutinee type `{}`",
3108 def.name, v, n
3109 ));
3110 }
3111 }
3112 Ok(if def.simple {
3113 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
3114 } else {
3115 format!("({}.kind == {})", name, def.kind_ident(&v))
3116 })
3117 }
3118
3119 /// Emit the `let`s that a pattern's bindings introduce.
3120 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
3121 match p {
3122 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
3123 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
3124 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
3125 Pat::Ident(i) if i.subpat.is_none() => {
3126 let b = i.ident.to_string();
3127 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
3128 self.bind(&b, t.clone());
3129 Ok(())
3130 }
3131 Pat::TupleStruct(ts) => {
3132 let fields = self.variant_fields(&ts.path, t)?;
3133 for (i, sub) in ts.elems.iter().enumerate() {
3134 let Some((fname, fty)) = fields.get(i) else {
3135 return Err(format!(
3136 "pattern binds {} field(s) but the variant has {}",
3137 ts.elems.len(),
3138 fields.len()
3139 ));
3140 };
3141 let access = format!("{}.{}", name, ident(fname));
3142 self.pat_bind(sub, &access, fty)?;
3143 }
3144 Ok(())
3145 }
3146 Pat::Struct(st) => {
3147 let fields = self.variant_fields(&st.path, t)?;
3148 for f in &st.fields {
3149 let syn::Member::Named(m) = &f.member else {
3150 return Err("unsupported struct pattern field".into());
3151 };
3152 let m = m.to_string();
3153 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
3154 return Err(format!("unknown field `{m}` in pattern"));
3155 };
3156 let access = format!("{}.{}", name, ident(fname));
3157 self.pat_bind(&f.pat, &access, fty)?;
3158 }
3159 Ok(())
3160 }
3161 _ => Err("unsupported `match` pattern".into()),
3162 }
3163 }
3164
3165 /// The payload fields a variant pattern destructures.
3166 fn variant_fields(
3167 &self,
3168 path: &syn::Path,
3169 t: &Nim,
3170 ) -> Result<Vec<(String, Nim)>, String> {
3171 let last = path_name(path);
3172 // `Ok`/`Err`/`Some` read the prelude's own field names.
3173 if let Nim::Named(n, a) = t {
3174 match (n.as_str(), last.as_str()) {
3175 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
3176 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
3177 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
3178 _ => {}
3179 }
3180 }
3181 let Some((def, v)) = self.resolve_variant(path) else {
3182 return Err(format!("`{last}` is not a known enum variant"));
3183 };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3184 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
3185 // The variant's payload is declared in the enum's own parameters; the
3186 // scrutinee says what they are here.
3187 Ok(fields
3188 .into_iter()
3189 .map(|(n, ft)| (n, self.subst_type_args(&def.name, t, ft)))
3190 .collect())
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3191 }
3192
3193 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
3194 self.indent += 1;
3195 let before = self.out.len();
3196 self.indent -= 1;
3197 self.arm_body_at(body, before)
3198 }
3199
3200 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
3201 match body {
3202 Expr::Block(b) => self.nested_block(&b.block)?,
3203 other => {
3204 self.indent += 1;
3205 // An arm's value is the `match`'s value, so it is typed by
3206 // whatever the `match` is being assigned to -- without which
3207 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
3208 let want = self.target.clone().and_then(|(_, t)| t);
3209 let v = match (want, expressible(other)) {
3210 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
3211 _ => self.expr_stmt(other)?,
3212 };
3213 self.emit_tail(v);
3214 self.indent -= 1;
3215 }
3216 }
3217 if self.out.len() == before {
3218 self.indent += 1;
3219 self.line("discard");
3220 self.indent -= 1;
3221 }
3222 Ok(())
3223 }
3224
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3225 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
3226 match p {
3227 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
3228 Pat::Or(o) => {
3229 let mut out = Vec::new();
3230 for p in &o.cases {
3231 out.extend(self.pat_labels(p, expect)?);
3232 }
3233 Ok(out)
3234 }
3235 Pat::Range(r) => {
3236 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
3237 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
3238 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
3239 let op = match r.limits {
3240 syn::RangeLimits::HalfOpen(_) => "..<",
3241 syn::RangeLimits::Closed(_) => "..",
3242 };
3243 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
3244 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3245 Pat::Path(pp) => {
3246 if let Some((def, v)) = self.resolve_variant(&pp.path) {
3247 return Ok(vec![if def.simple {
3248 format!("{}.{}", ident(&def.name), ident(&v))
3249 } else {
3250 def.kind_ident(&v)
3251 }]);
3252 }
3253 Ok(vec![ident(&path_name(&pp.path))])
3254 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3255 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3256 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3257 .into()),
3258 }
3259 }
3260
3261 // --------------------------------------------------------- expressions
3262
3263 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
3264 self.expr_at(e, None)
3265 }
3266
3267 /// Lower `e`, with the type the surrounding code expects of it.
3268 ///
3269 /// Rust infers an unsuffixed integer literal's type from its context and
3270 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
3271 /// expected type down to the literal is what makes `let x: u8 = 255` and
3272 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
3273 /// widths silently diverge, which is exactly the class of bug this
3274 /// project refuses to ship.
3275 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
3276 match e {
3277 Expr::Lit(l) => self.lit_at(&l.lit, expect),
3278 Expr::Path(p) => {
3279 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3280 if name == "None" {
3281 return Ok(Val::new(self.none_of(expect), expect.cloned()));
3282 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago3283 // `log::Level` and `log::LevelFilter` come from the facade
3284 // shim, under names no crate can collide with.
3285 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3286 if (q == "Level" || q == "LevelFilter") && !self.enums.contains_key(&q) {
3287 let pre = if q == "Level" { "rsLvl" } else { "rsFlt" };
3288 let t = if q == "Level" { "RsLogLevel" } else { "RsLogFilter" };
3289 if q == "LevelFilter" && name == "Off" {
3290 return Ok(Val::new("rsFltOff", Some(Nim::Prim(t.into()))));
3291 }
3292 if matches!(name.as_str(), "Error" | "Warn" | "Info" | "Debug" | "Trace") {
3293 return Ok(Val::new(
3294 format!("{pre}{name}"),
3295 Some(Nim::Prim(t.into())),
3296 ));
3297 }
3298 }
3299 }
3300
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago3301 // `Perms::READ`: a constant of a `bitflags!` type.
3302 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3303 let q = if q == "Self" {
3304 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3305 } else {
3306 q
3307 };
3308 if let Some(c) = self.flag_consts.get(&(q.clone(), name.clone())) {
3309 return Ok(Val::new(c.clone(), Some(Nim::Named(q, vec![]))));
3310 }
3311 }
3312
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago3313 // `Grid::BORDER`: a `const` declared inside an `impl`.
3314 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3315 let q = if q == "Self" {
3316 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3317 } else {
3318 q
3319 };
3320 if let Some((nim, t)) = self.assoc_consts.get(&(q, name.clone())) {
3321 return Ok(Val::new(nim.clone(), Some(t.clone())));
3322 }
3323 }
3324
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3325 // `i32::MAX` and friends: an associated const on a primitive.
3326 if matches!(name.as_str(), "MAX" | "MIN") {
3327 if let Some(q) = p.path.segments.iter().rev().nth(1) {
3328 if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) {
3329 if t.is_integer() {
3330 let f = if name == "MAX" { "high" } else { "low" };
3331 return Ok(Val::new(
3332 format!("{}({})", f, t.render()),
3333 Some(t),
3334 ));
3335 }
3336 }
3337 }
3338 }
3339
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3340 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
3341 // declared here. In Nim that is a constructor call.
3342 if p.path.segments.len() > 1 {
3343 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
3344 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
3345 if n == "FmtError" {
3346 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
3347 }
3348 }
3349 }
3350 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
3351 return Ok(Val::new(
3352 format!("{}()", ident(&name)),
3353 Some(Nim::Named(name.clone(), vec![])),
3354 ));
3355 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3356 // A unit enum variant used as a value: `Error::InvalidLength`.
3357 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3358 let (ty, targs) = self.variant_type(&def, expect)?;
3359 return Ok(if def.simple && targs.is_empty() {
3360 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), Some(ty))
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3361 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3362 // A unit variant of a generic enum has no argument to
3363 // infer the parameters from, so they are written out.
3364 Val::new(
3365 format!("{}{}()", def.ctor_ident(&v), targs),
3366 Some(ty),
3367 )
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3368 });
3369 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3370 // A `for` binding that stands for an element of the container
3371 // it came from: using it must read (and assigning through it
3372 // must write) that element, not a copy.
3373 if let Some(a) = self.lookup_alias(&name) {
3374 return Ok(match a {
3375 Alias::Value { code, ty } => Val::new(code, ty),
3376 // A window *is* a slice; as a value it is the view it
3377 // denotes, which is what Rust's `&[T]` means too.
3378 Alias::Window { code, off, len, elem } => Val::new(
3379 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
3380 elem.map(|e| Nim::OpenArray(Box::new(e))),
3381 ),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago3382 // An iterator is not a value here: it is consumed by a
3383 // `for`, or asked for its `.remainder()`.
3384 Alias::Iterator(_) => {
3385 return Err(format!(
3386 "`{name}` is an iterator; it can be iterated or asked \
3387 for its `remainder()`, but not used as a value"
3388 ))
3389 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3390 });
3391 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3392 if let Some(t) = self.lookup(&name) {
3393 return Ok(Val::new(ident(&name), Some(t)));
3394 }
3395 // A top-level function used as a value, e.g. passed to a
3396 // parameter of `impl Fn(..)` type.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago3397 if let Some(k) = self.resolve_fn(&p.path) {
3398 let sig = &self.fns[&k];
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3399 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 10h ago3400 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 ago3401 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3402 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3403 }
3404 Expr::Paren(p) => {
3405 let v = self.expr_at(&p.expr, expect)?;
3406 Ok(Val::new(format!("({})", v.code), v.ty))
3407 }
3408 Expr::Group(g) => self.expr_at(&g.expr, expect),
3409 // `&x` is a value in Nim; `&mut x` in an argument position binds to
3410 // a `var` parameter, which is also just `x` at the call site.
3411 Expr::Reference(r) => self.expr_at(&r.expr, expect),
3412 Expr::Unary(u) => self.unary(u, expect),
3413 Expr::Binary(b) => self.binary(b, expect),
3414 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3415 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
3416 let Expr::Range(r) = &*i.index else { unreachable!() };
3417 let base = self.expr(&i.expr)?;
3418 let lo = match &r.start {
3419 Some(e) => format!("int({})", self.expr(e)?.code),
3420 None => "0".into(),
3421 };
3422 // Nim's `toOpenArray` takes an inclusive upper bound.
3423 let hi = match (&r.end, r.limits) {
3424 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
3425 format!("int({}) - 1", self.expr(e)?.code)
3426 }
3427 (Some(e), syn::RangeLimits::Closed(_)) => {
3428 format!("int({})", self.expr(e)?.code)
3429 }
3430 (None, _) => format!("{}.len - 1", base.code),
3431 };
3432 let elem = elem_of(&base.ty)
3433 .ok_or("cannot infer the element type of this slice")?;
3434 Ok(Val::new(
3435 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
3436 Some(Nim::OpenArray(Box::new(elem))),
3437 ))
3438 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3439 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3440 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
3441 let idx = self.expr(&i.index)?;
3442 return Ok(Val::new(
3443 format!("{}[{} + int({})]", code, off, idx.code),
3444 elem,
3445 ));
3446 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3447 let base = self.expr(&i.expr)?;
3448 let idx = self.expr(&i.index)?;
3449 // Rust indexes with usize; Nim wants an `int`, and a `uint`
3450 // index is a type error there rather than a silent conversion.
3451 let idx_code = match &idx.ty {
3452 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
3453 _ => idx.code.clone(),
3454 };
3455 let elem = match base.ty.clone() {
3456 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
3457 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
3458 _ => None,
3459 };
3460 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
3461 }
3462 Expr::Field(f) => {
3463 let base = self.expr(&f.base)?;
3464 let name = match &f.member {
3465 syn::Member::Named(n) => n.to_string(),
3466 syn::Member::Unnamed(i) => format!("f{}", i.index),
3467 };
3468 let t = match &base.ty {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3469 Some(bt @ Nim::Named(s, _)) => self
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3470 .structs
3471 .get(s)
3472 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3473 .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 ago3474 _ => None,
3475 };
3476 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
3477 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago3478 // `unsafe` is a permission marker, not a semantic change: it does
3479 // not alter what the enclosed operations mean. So the block is
3480 // transparent here, and each operation inside still goes through
3481 // the ordinary lowering -- and is still rejected if it has no
3482 // faithful mapping.
3483 Expr::Unsafe(u) => match single_expr(&u.block) {
3484 Some(e) => self.expr_at(e, expect),
3485 None => Err("an `unsafe` block used as a value must be a single \
3486 expression"
3487 .into()),
3488 },
3489 Expr::Closure(c) => self.closure(c, expect),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3490 Expr::Try(t) => self.try_op(t),
3491 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago3492 Expr::MethodCall(m) => self.method(m, expect),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago3493 Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
3494 // `vec![..]`'s elements take their type from the annotation on
3495 // the binding, exactly as Rust's would.
3496 let want = match expect {
3497 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
3498 _ => None,
3499 };
3500 let saved = std::mem::replace(&mut self.vec_expect, want.clone());
3501 let code = self.macro_call(&m.mac);
3502 self.vec_expect = saved;
3503 let code = code?;
3504 let ty = match want {
3505 Some(e) => Some(Nim::Seq(Box::new(e))),
3506 None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
3507 };
3508 Ok(Val::new(code, ty))
3509 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3510 Expr::Macro(m) => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago3511 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 ago3512 let code = self.macro_call(&m.mac)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago3513 // A formatter write is a statement that appends, not a value.
3514 let ty = if is_write { Some(Nim::Unit) } else { None };
3515 Ok(Val::new(code, ty))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3516 }
3517 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3518 if s.rest.is_some() {
3519 return Err("struct update syntax `..rest` is not implemented yet".into());
3520 }
3521 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
3522 // which is constructed positionally in Nim.
3523 if let Some((def, v)) = self.resolve_variant(&s.path) {
3524 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
3525 let mut args = vec![String::new(); fields.len()];
3526 for f in &s.fields {
3527 let syn::Member::Named(m) = &f.member else {
3528 return Err("unsupported enum variant field".into());
3529 };
3530 let want = format!("{}_{}", v, m);
3531 let i = fields
3532 .iter()
3533 .position(|(n, _)| *n == want)
3534 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
3535 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
3536 }
3537 if let Some(i) = args.iter().position(|a| a.is_empty()) {
3538 return Err(format!(
3539 "`{}::{}` is missing field `{}`",
3540 def.name, v, fields[i].0
3541 ));
3542 }
3543 return Ok(Val::new(
3544 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
3545 Some(Nim::Named(def.name.clone(), vec![])),
3546 ));
3547 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3548 // `Self { .. }` inside an `impl` names the type being
3549 // implemented, and its fields are that type's fields.
3550 let name = match path_name(&s.path).as_str() {
3551 "Self" => self
3552 .self_ty
3553 .as_ref()
3554 .map(type_name)
3555 .ok_or("`Self` outside an `impl` block")?,
3556 other => other.to_string(),
3557 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3558 let mut parts = Vec::new();
3559 for f in &s.fields {
3560 let fname = match &f.member {
3561 syn::Member::Named(n) => n.to_string(),
3562 syn::Member::Unnamed(i) => format!("f{}", i.index),
3563 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3564 let want = self
3565 .structs
3566 .get(&name)
3567 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
3568 .map(|(_, t)| t.clone());
3569 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 ago3570 parts.push(format!("{}: {}", ident(&fname), v.code));
3571 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3572 // Nim cannot infer an object's generic parameters from a
3573 // constructor's field values, so they are written out.
3574 let gp = self.type_generics.get(&name).cloned().unwrap_or_default();
3575 let ty = if gp.is_empty() {
3576 Nim::Named(name.clone(), vec![])
3577 } else {
3578 match expect {
3579 Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => {
3580 Nim::Named(name.clone(), a.clone())
3581 }
3582 _ => {
3583 return Err(format!(
3584 "`{name} {{ .. }}` is generic, and Nim cannot infer \
3585 its parameters from the field values; annotate the \
3586 binding or the return type"
3587 ))
3588 }
3589 }
3590 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3591 Ok(Val::new(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago3592 format!("{}({})", ty.render(), parts.join(", ")),
3593 Some(ty),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3594 ))
3595 }
3596 Expr::Array(a) => {
3597 let mut parts = Vec::new();
3598 let mut elem = match expect {
3599 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
3600 Some((**t).clone())
3601 }
3602 _ => None,
3603 };
3604 for e in &a.elems {
3605 let want = elem.clone();
3606 let v = self.expr_at(e, want.as_ref())?;
3607 elem = elem.or(v.ty.clone());
3608 parts.push(v.code);
3609 }
3610 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
3611 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
3612 }
3613 Expr::Repeat(r) => {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago3614 // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size
3615 // array from a `seq`, so the expected type decides which, and
3616 // an array needs its elements written out.
3617 let want_elem = match expect {
3618 Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => {
3619 Some((**e).clone())
3620 }
3621 _ => None,
3622 };
3623 let v = self.expr_at(&r.expr, want_elem.as_ref())?;
3624 if let Some(Nim::Array(n, _)) = expect {
3625 let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect();
3626 let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t)));
3627 return Ok(Val::new(format!("[{}]", elems.join(", ")), t));
3628 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3629 let n = self.expr(&r.len)?;
3630 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
3631 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
3632 }
3633 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
3634 Expr::Tuple(t) => {
3635 let mut parts = Vec::new();
3636 let mut tys = Vec::new();
3637 for e in &t.elems {
3638 let v = self.expr(e)?;
3639 tys.push(v.ty.clone());
3640 parts.push(v.code);
3641 }
3642 let ty = tys
3643 .iter()
3644 .cloned()
3645 .collect::<Option<Vec<_>>>()
3646 .map(Nim::Tuple);
3647 Ok(Val::new(format!("({})", parts.join(", ")), ty))
3648 }
3649 // `if` and `match` are expressions in both languages, but only
3650 // when every arm is itself a single expression.
3651 Expr::If(i) => self.if_expr(i, expect),
Expand `$(..)` repetition in macro_rules 3d4a7d2 nandithebull 6h ago3652 // A block with statements used as a value: the statements are
3653 // emitted ahead of the line being built and the block's trailing
3654 // expression becomes the value. Every caller lowers its
3655 // sub-expressions before emitting its own line, which is what
3656 // makes that ordering hold.
3657 Expr::Block(b)
3658 if b.label.is_none()
3659 && b.block.stmts.len() > 1
3660 && matches!(b.block.stmts.last(), Some(Stmt::Expr(_, None))) =>
3661 {
3662 if self.in_loop_cond {
3663 return Err("a block expression in a loop condition is not \
3664 implemented yet: its statements would run once, \
3665 before the loop"
3666 .into());
3667 }
3668 // Nim's `block:` is an expression too, so the statements get
3669 // their own scope rather than being hoisted into the enclosing
3670 // one -- which would collide if the same block is written
3671 // twice, as a macro expanded at two call sites is.
3672 let tmp = self.fresh("Blk");
3673 self.line(&format!("let {} = block:", tmp));
3674 self.indent += 1;
3675 self.push_scope();
3676 let saved = self.target.take();
3677 let before = self.out.len();
3678 let tail = self.block_body_at(&b.block, expect)?;
3679 let ty = tail.as_ref().and_then(|v| v.ty.clone());
3680 match tail {
3681 Some(v) => {
3682 let code = v.code.clone();
3683 self.line(&code);
3684 }
3685 None => {
3686 return Err(
3687 "a block used as a value needs a trailing expression".into()
3688 )
3689 }
3690 }
3691 let _ = before;
3692 self.target = saved;
3693 self.pop_scope();
3694 self.indent -= 1;
3695 Ok(Val::new(tmp, ty))
3696 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3697 Expr::Block(b) if b.block.stmts.len() == 1 => {
3698 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
3699 self.expr_at(e, expect)
3700 } else {
3701 Err("block expression with statements in value position is not implemented yet".into())
3702 }
3703 }
3704 other => Err(format!(
3705 "unsupported expression in value position: {}",
3706 expr_kind(other)
3707 )),
3708 }
3709 }
3710
3711 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
3712 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
3713 return Err(
3714 "an `if` used as a value must have an `else` and single-expression arms".into(),
3715 );
3716 };
3717 let c = self.expr(&i.cond)?;
3718 let t = self.expr_at(then, expect)?;
3719 let want = expect.cloned().or_else(|| t.ty.clone());
3720 let e = match &**els {
3721 Expr::Block(b) => match single_expr(&b.block) {
3722 Some(x) => self.expr_at(x, want.as_ref())?,
3723 None => return Err("an `if` used as a value must have single-expression arms".into()),
3724 },
3725 other => self.expr_at(other, want.as_ref())?,
3726 };
3727 let ty = t.ty.clone().or(e.ty.clone());
3728 Ok(Val::new(
3729 format!("(if {}: {} else: {})", c.code, t.code, e.code),
3730 ty,
3731 ))
3732 }
3733
3734 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
3735 match l {
3736 Lit::Int(i) => {
3737 let suffix = i.suffix();
3738 if let Some(why) = ty::rejected(suffix) {
3739 return Err(format!("integer literal `{}`: {}", i, why));
3740 }
3741 let digits = i.base10_digits().to_string();
3742 // Rust's default for an unconstrained integer literal is i32.
3743 // Nim's is `int` (64-bit). Making the width explicit is what
3744 // keeps overflow behaviour the same on both sides.
3745 let t = if suffix.is_empty() {
3746 match expect {
3747 Some(t) if t.is_integer() => t.clone(),
3748 // Rust's fallback for an otherwise-unconstrained
3749 // integer literal.
3750 _ => Nim::Prim("int32".into()),
3751 }
3752 } else {
3753 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
3754 };
3755 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
3756 }
3757 Lit::Float(f) => {
3758 let t = match f.suffix() {
3759 "" => match expect {
3760 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
3761 _ => Nim::Prim("float64".into()),
3762 },
3763 "f64" => Nim::Prim("float64".into()),
3764 "f32" => Nim::Prim("float32".into()),
3765 s => return Err(format!("unknown float suffix `{s}`")),
3766 };
3767 let d = f.base10_digits();
3768 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
3769 Ok(Val::new(d, Some(t)))
3770 }
3771 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
3772 Lit::Str(s) => Ok(Val::new(
3773 fmt::nim_str(&s.value()),
3774 Some(Nim::Prim("string".into())),
3775 )),
3776 Lit::Char(c) => Ok(Val::new(
3777 format!("Rune({})", c.value() as u32),
3778 Some(Nim::Prim("Rune".into())),
3779 )),
3780 Lit::Byte(b) => Ok(Val::new(
3781 format!("{}'u8", b.value()),
3782 Some(Nim::Prim("uint8".into())),
3783 )),
3784 Lit::ByteStr(b) => {
3785 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
3786 Ok(Val::new(
3787 format!("@[{}]", bytes.join(", ")),
3788 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
3789 ))
3790 }
3791 other => Err(format!("unsupported literal: {other:?}")),
3792 }
3793 }
3794
3795 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
3796 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
3797 // the positive half of the range before the negation runs. Folding the
3798 // sign into the literal keeps `i8::MIN` and friends expressible.
3799 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
3800 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
3801 let v = self.lit_at(&l.lit, expect)?;
3802 return Ok(Val::new(format!("-{}", v.code), v.ty));
3803 }
3804 }
3805 let v = self.expr_at(&u.expr, expect)?;
3806 match u.op {
3807 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
3808 // Rust's `!` is logical on bool and bitwise-complement on integers.
3809 // Nim spells those `not` and `not` as well, so one mapping covers
3810 // both — but only because Nim overloads `not` the same way.
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago3811 UnOp::Not(_) => {
3812 if let Some(f) = self.op_proc(&v.ty, "not") {
3813 return Ok(Val::new(format!("{}({})", f, v.code), v.ty));
3814 }
3815 Ok(Val::new(format!("(not {})", v.code), v.ty))
3816 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3817 UnOp::Deref(_) => Ok(v),
3818 _ => Err("unsupported unary operator".into()),
3819 }
3820 }
3821
3822 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
3823 // A comparison's operands are unrelated to the `bool` it produces, so
3824 // the outer expectation is not passed through to them.
3825 let down = match b.op {
3826 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3827 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
3828 _ => expect,
3829 };
3830 let mut l = self.expr_at(&b.left, down)?;
3831 // Rust unifies the two operand types; propagating whichever side is
3832 // known to the other reproduces that, and disagreement then surfaces
3833 // as a Nim type error rather than as a silent width change.
3834 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
3835 if l.ty.is_none() && r.ty.is_some() {
3836 l = self.expr_at(&b.left, r.ty.as_ref())?;
3837 }
3838 let r = std::mem::replace(&mut r, Val::untyped(""));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago3839 // A binary operator on a user type goes to that type's own impl.
3840 if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) {
3841 let want = self.op_param(&l.ty, binary_symbol(&b.op));
3842 let r = self.expr_at(&b.right, want.as_ref())?;
3843 let ret = self
3844 .methods
3845 .get(&(
3846 type_name(l.ty.as_ref().unwrap()),
3847 op_method(binary_symbol(&b.op)).to_string(),
3848 ))
3849 .map(|s| s.ret.clone());
3850 return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret));
3851 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3852 let op = self.bin_op(&b.op, &l, &r)?;
3853 let ty = match b.op {
3854 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3855 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
3856 // Rust's shift takes its result type from the *left* operand, and
3857 // the right may be a different width entirely.
3858 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
3859 _ => l.ty.clone().or(r.ty.clone()),
3860 };
3861 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
3862 }
3863
3864 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
3865 Ok(match op {
3866 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
3867 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
3868 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
3869 BinOp::Div(_) | BinOp::DivAssign(_) => {
3870 // Nim spells integer division `div`. Both languages truncate
3871 // toward zero, so once the right operator is chosen the
3872 // semantics match, including for negative operands.
3873 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3874 "cannot tell integer from float division here; annotate the operands",
3875 )?;
3876 if t.is_integer() { "div" } else { "/" }
3877 }
3878 BinOp::Rem(_) | BinOp::RemAssign(_) => {
3879 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3880 "cannot tell integer from float remainder here; annotate the operands",
3881 )?;
3882 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
3883 }
3884 BinOp::And(_) => "and",
3885 BinOp::Or(_) => "or",
3886 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
3887 // bools, exactly as Rust's `&`/`|`/`^` are.
3888 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
3889 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
3890 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
3891 // Settled empirically: Nim's `shr` on a signed integer is
3892 // arithmetic, matching Rust. See DESIGN.md.
3893 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
3894 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
3895 BinOp::Eq(_) => "==",
3896 BinOp::Ne(_) => "!=",
3897 BinOp::Lt(_) => "<",
3898 BinOp::Le(_) => "<=",
3899 BinOp::Gt(_) => ">",
3900 BinOp::Ge(_) => ">=",
3901 other => return Err(format!("unsupported binary operator {other:?}")),
3902 })
3903 }
3904
3905 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
3906 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3907 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3908 let from = v.ty.clone().ok_or_else(|| {
3909 format!(
3910 "cannot lower `as {}`: the source type is unknown, and `as` \
3911 truncates, so the source width decides the result",
3912 to.render()
3913 )
3914 })?;
3915
3916 let code = match (&from, &to) {
3917 (f, t) if f.is_integer() && t.is_integer() => {
3918 // Rust's `as` between integers is a pure bit-width truncation
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 9h ago3919 // or sign-extension, never a range check. `cast` says exactly
3920 // that. (Nim's `T(x)` turns out to truncate here as well --
3921 // see DESIGN.md item 5 -- but `cast` is the spelling that
3922 // 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 ago3923 format!("cast[{}]({})", t.render(), v.code)
3924 }
3925 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
3926 format!("{}({})", p, v.code)
3927 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago3928 // The facade's level enums carry their Rust discriminants, so
3929 // `Level::Info as usize` is the ordinal.
3930 (Nim::Prim(p), t)
3931 if t.is_integer() && (p == "RsLogLevel" || p == "RsLogFilter") =>
3932 {
3933 format!("{}(ord({}))", t.render(), v.code)
3934 }
3935 // A C-like enum's `as` yields its discriminant, which is its
3936 // ordinal in Nim.
3937 (Nim::Named(n, _), t) if t.is_integer() && self.enums.get(n).is_some_and(|d| d.simple) => {
3938 format!("{}(ord({}))", t.render(), v.code)
3939 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3940 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
3941 format!("{}(ord({}))", t.render(), v.code)
3942 }
3943 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
3944 format!("cast[{}](int32({}))", t.render(), v.code)
3945 }
3946 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
3947 format!("Rune(int32({}))", v.code)
3948 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago3949 // Pointer-to-pointer, and integer-to-pointer, are reinterpretations
3950 // in both languages.
3951 (Nim::Ptr(_) | Nim::ConstPtr(_), Nim::Ptr(_) | Nim::ConstPtr(_)) => {
3952 format!("cast[{}]({})", to.render(), v.code)
3953 }
3954 (f, Nim::Ptr(_) | Nim::ConstPtr(_)) if f.is_integer() => {
3955 format!("cast[{}]({})", to.render(), v.code)
3956 }
3957 (Nim::Ptr(_) | Nim::ConstPtr(_), t) if t.is_integer() => {
3958 format!("cast[{}]({})", t.render(), v.code)
3959 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago3960 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
3961 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
3962 // Rust saturates float->int casts; Nim rounds and range-errors.
3963 // Not the same operation, so it is refused rather than mapped.
3964 return Err(format!(
3965 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
3966 no faithful mapping is implemented",
3967 t.render()
3968 ));
3969 }
3970 (f, t) => {
3971 return Err(format!(
3972 "unsupported cast from `{}` to `{}`",
3973 f.render(),
3974 t.render()
3975 ))
3976 }
3977 };
3978 Ok(Val::new(code, Some(to)))
3979 }
3980
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago3981 /// Rust's `?`: return early on the error branch, otherwise yield the value.
3982 ///
3983 /// The early return is statements, not an expression, so they are emitted
3984 /// ahead of the line being built. Every caller lowers its sub-expressions
3985 /// 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 10h ago3986 /// The container, start offset, length and element type an expression
3987 /// denotes as a slice. A window alias contributes its own offset, so
3988 /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
3989 /// into the original buffer rather than through a rebuilt view.
3990 fn slice_parts(
3991 &mut self,
3992 e: &Expr,
3993 ) -> Result<(String, String, String, Option<Nim>), String> {
3994 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
3995 return Ok((code, off, len, elem));
3996 }
3997 let v = self.expr(e)?;
3998 let len = format!("{}.len", v.code);
3999 Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
4000 }
4001
4002 /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
4003 fn map_closure(
4004 &mut self,
4005 what: &str,
4006 recv: &Val,
4007 kind: &str,
4008 targs: &[Nim],
4009 c: &syn::ExprClosure,
4010 ) -> Result<Val, String> {
4011 if c.capture.is_some() {
4012 return Err("a `move` closure captures by value; Nim's closures \
4013 capture by reference, and the two are not the same"
4014 .into());
4015 }
4016 if c.inputs.len() != 1 {
4017 return Err(format!("`.{what}()` takes a one-argument closure"));
4018 }
4019 let pname = match &c.inputs[0] {
4020 Pat::Ident(i) => i.ident.to_string(),
4021 Pat::Wild(_) => "unused0".into(),
4022 _ => return Err("only plain identifier closure parameters are supported".into()),
4023 };
4024
4025 let is_opt = kind == "Option";
4026 let tmp = self.fresh("Map");
4027 let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
4028 self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
4029
4030 let body = match &*c.body {
4031 Expr::Block(b) => single_expr(&b.block)
4032 .ok_or("a closure body with statements is not implemented yet")?,
4033 other => other,
4034 };
4035 self.push_scope();
4036 // The parameter names the payload itself, so a view stays a view.
4037 self.bind_alias(
4038 &pname,
4039 Alias::Value {
4040 code: format!("{}.val", tmp),
4041 ty: Some(targs[0].clone()),
4042 },
4043 );
4044 let v = self.expr(body)?;
4045 self.pop_scope();
4046
4047 let inner = v
4048 .ty
4049 .clone()
4050 .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
4051 // `and_then`'s closure already returns the wrapped type; `map`'s does
4052 // not and has to be re-wrapped.
4053 let (test, some_branch, none_branch, out_ty) = if is_opt {
4054 let out = if what == "map" {
4055 Nim::Named("Option".into(), vec![inner.clone()])
4056 } else {
4057 inner.clone()
4058 };
4059 let body_code = if what == "map" {
4060 format!("rsSome[{}]({})", inner.render(), v.code)
4061 } else {
4062 v.code.clone()
4063 };
4064 (
4065 format!("{}.has", tmp),
4066 body_code,
4067 format!("rsNone[{}]()", elem_arg(&out).render()),
4068 out,
4069 )
4070 } else {
4071 let e = targs[1].clone();
4072 let out = if what == "map" {
4073 Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
4074 } else {
4075 inner.clone()
4076 };
4077 let ok_ty = elem_arg(&out);
4078 let body_code = if what == "map" {
4079 format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
4080 } else {
4081 v.code.clone()
4082 };
4083 (
4084 format!("{}.ok", tmp),
4085 body_code,
4086 format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
4087 out,
4088 )
4089 };
4090 Ok(Val::new(
4091 format!("(if {}: {} else: {})", test, some_branch, none_branch),
4092 Some(out_ty),
4093 ))
4094 }
4095
4096 /// `|x| x + 1` -> a Nim anonymous proc.
4097 ///
4098 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
4099 /// A `move` closure captures by value, which is a different thing, so it
4100 /// is rejected rather than lowered to the same construct.
4101 fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
4102 if c.capture.is_some() {
4103 return Err("a `move` closure captures by value; Nim's closures \
4104 capture by reference, and the two are not the same"
4105 .into());
4106 }
4107 let want: Option<&Vec<Nim>> = match expect {
4108 Some(Nim::Proc(a, _)) => Some(a),
4109 _ => None,
4110 };
4111
4112 self.push_scope();
4113 let mut parts = Vec::new();
4114 let mut ptys = Vec::new();
4115 for (i, p) in c.inputs.iter().enumerate() {
4116 let (name, ann) = match p {
4117 Pat::Ident(id) => (id.ident.to_string(), None),
4118 Pat::Type(t) => match &*t.pat {
4119 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
4120 _ => return Err("only plain identifier closure parameters are supported".into()),
4121 },
4122 Pat::Wild(_) => (format!("unused{i}"), None),
4123 _ => return Err("only plain identifier closure parameters are supported".into()),
4124 };
4125 let t = ann
4126 .or_else(|| want.and_then(|w| w.get(i).cloned()))
4127 .ok_or_else(|| {
4128 format!(
4129 "cannot infer the type of closure parameter `{name}`; \
4130 annotate it"
4131 )
4132 })?;
4133 parts.push(format!("{}: {}", ident(&name), t.render()));
4134 self.bind(&name, t.clone());
4135 ptys.push(t);
4136 }
4137
4138 let ret_ann = match &c.output {
4139 ReturnType::Default => None,
4140 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
4141 };
4142 let body = match &*c.body {
4143 Expr::Block(b) => single_expr(&b.block)
4144 .ok_or("a closure body with statements is not implemented yet")?,
4145 other => other,
4146 };
4147 let v = self.expr_at(body, ret_ann.as_ref())?;
4148 self.pop_scope();
4149
4150 let ret = ret_ann
4151 .or_else(|| v.ty.clone())
4152 .ok_or("cannot infer a closure's return type; annotate it")?;
4153 Ok(Val::new(
4154 format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
4155 Some(Nim::Proc(ptys, Box::new(ret))),
4156 ))
4157 }
4158
4159 /// Lower a block's statements at the current indentation, without opening
4160 /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
4161 /// of its own in the generated code.
4162 fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
4163 self.push_scope();
4164 let tail = self.block_body(b)?;
4165 self.emit_tail(tail);
4166 self.pop_scope();
4167 Ok(())
4168 }
4169
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4170 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
4171 if self.in_loop_cond {
4172 return Err("`?` in a loop condition is not implemented yet: the \
4173 early-return it expands to would be evaluated once, \
4174 before the loop, rather than on each iteration"
4175 .into());
4176 }
4177 let v = self.expr(&t.expr)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4178 if self.fmt_param.is_some() {
4179 // Writing into a string cannot fail, so `?` on a formatter write
4180 // is a no-op. `?` on anything else can fail, and `format!` panics
4181 // when a formatting impl returns an error -- so that is what the
4182 // error branch does here, with std's own message.
4183 if v.ty.as_ref() == Some(&Nim::Unit) {
4184 return Ok(v);
4185 }
4186 if let Some(Nim::Named(n, a)) = v.ty.clone() {
4187 if n == "Result" && a.len() == 2 {
4188 let tmp = self.fresh("Fmt");
4189 self.line(&format!(
4190 "let {}: {} = {}",
4191 tmp,
4192 Nim::Named(n, a.clone()).render(),
4193 v.code
4194 ));
4195 self.line(&format!("if not {}.ok:", tmp));
4196 self.line(
4197 " rsPanic(\"a formatting trait implementation returned an error\")",
4198 );
4199 return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone())));
4200 }
4201 }
4202 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4203 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
4204 // An `Option`/`Result` of a view: the check is emitted here and the
4205 // view itself survives as an alias, since it has no value form.
4206 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
4207 let err = v.guard_err.clone().ok_or(
4208 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
4209 )?;
4210 let Nim::Named(n, ra) = &ret else {
4211 return Err(format!("`?` in a function returning `{}`", ret.render()));
4212 };
4213 if n != "Result" || ra.len() != 2 {
4214 return Err(format!("`?` in a function returning `{}`", ret.render()));
4215 }
4216 self.line(&format!("if not {}:", guard));
4217 self.line(&format!(
4218 " return rsErr[{}, {}]({})",
4219 ra[0].render(),
4220 ra[1].render(),
4221 err
4222 ));
4223 let mut out = Val::new(String::new(), None);
4224 out.window = Some(w);
4225 return Ok(out);
4226 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4227 let vt = v.ty.clone().ok_or(
4228 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
4229 )?;
4230 let ret = self
4231 .ret
4232 .clone()
4233 .ok_or("`?` outside a function with a return type")?;
4234 let tmp = self.fresh("Try");
4235 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
4236
4237 match (&vt, &ret) {
4238 (Nim::Named(a, ai), Nim::Named(b, bi))
4239 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
4240 {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4241 // Rust inserts a `From::from` on the error here. Where the
4242 // types differ we call the crate's own `impl From`; we never
4243 // assume the conversion is the identity.
4244 let err = if ai[1] == bi[1] {
4245 format!("{}.err", tmp)
4246 } else {
4247 let key = (type_name(&ai[1]), type_name(&bi[1]));
4248 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4249 format!(
4250 "`?` needs `From<{}> for {}` to convert the error, and \
4251 no such `impl` is in scope; assuming the conversion is \
4252 the identity would be a guess",
4253 key.0, key.1
4254 )
4255 })?;
4256 format!("{}({}.err)", f, tmp)
4257 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4258 self.line(&format!("if not {}.ok:", tmp));
4259 self.line(&format!(
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4260 " return rsErr[{}, {}]({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4261 bi[0].render(),
4262 bi[1].render(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4263 err
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4264 ));
4265 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
4266 }
4267 (Nim::Named(a, ai), Nim::Named(b, bi))
4268 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
4269 {
4270 self.line(&format!("if not {}.has:", tmp));
4271 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
4272 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
4273 }
4274 _ => Err(format!(
4275 "`?` on `{}` in a function returning `{}` is not a supported \
4276 combination",
4277 vt.render(),
4278 ret.render()
4279 )),
4280 }
4281 }
4282
4283 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 ago4284 let Expr::Path(p) = &*c.func else {
4285 return Err("only calls to named functions are supported".into());
4286 };
4287 let name = path_name(&p.path);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4288 let target = self.resolve_fn(&p.path);
4289 let ptys: Vec<Nim> = target
4290 .as_ref()
4291 .and_then(|k| self.fns.get(k))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4292 .map(|s| s.params.clone())
4293 .unwrap_or_default();
4294 let mut args = Vec::new();
4295 for (i, a) in c.args.iter().enumerate() {
4296 let want = ptys.get(i).cloned();
4297 args.push(self.expr_at(a, want.as_ref())?);
4298 }
4299 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
4300
4301 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4302 // `Ok`/`Err` must name the *whole* Result type, not just the half
4303 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
4304 match name.as_str() {
4305 "Some" => {
4306 let inner = match expect {
4307 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
4308 _ => {
4309 return Err("`Some(..)` needs a known `Option<T>` type here; \
4310 annotate the binding or the return type"
4311 .into())
4312 }
4313 };
4314 return Ok(Val::new(
4315 format!("rsSome[{}]({})", inner, codes.join(", ")),
4316 expect.cloned(),
4317 ));
4318 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4319 "Ok" if self.fmt_param.is_some()
4320 && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) =>
4321 {
4322 // `Ok(())` ends a `fmt` body: nothing more is written.
4323 return Ok(Val::new(String::new(), Some(Nim::Unit)));
4324 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4325 "Ok" | "Err" => {
4326 let (t, e) = match expect {
4327 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4328 (a[0].render(), a[1].render())
4329 }
4330 _ => {
4331 return Err(format!(
4332 "`{name}(..)` needs a known `Result<T, E>` type here; \
4333 annotate the binding or the return type"
4334 ))
4335 }
4336 };
4337 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
4338 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
4339 return Ok(Val::new(
4340 format!("{}[{}, {}]({})", ctor, t, e, arg),
4341 expect.cloned(),
4342 ));
4343 }
4344 _ => {}
4345 }
4346
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4347 // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's
4348 // object constructor names its fields even when Rust's does not.
4349 if let Some(fields) = self.structs.get(&name).cloned() {
4350 if fields.len() == c.args.len() {
4351 let mut parts = Vec::new();
4352 for (i, a) in c.args.iter().enumerate() {
4353 let v = self.expr_at(a, Some(&fields[i].1))?;
4354 parts.push(format!("{}: {}", ident(&fields[i].0), v.code));
4355 }
4356 return Ok(Val::new(
4357 format!("{}({})", ident(&name), parts.join(", ")),
4358 Some(Nim::Named(name.clone(), vec![])),
4359 ));
4360 }
4361 }
4362
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago4363 // `log::set_max_level` / `log::max_level`.
4364 if name == "set_max_level" && codes.len() == 1 {
4365 return Ok(Val::new(
4366 format!("rsLogMaxLevel = int({})", codes[0]),
4367 Some(Nim::Unit),
4368 ));
4369 }
4370 if name == "max_level" && codes.is_empty() {
4371 return Ok(Val::new(
4372 "RsLogFilter(rsLogMaxLevel)",
4373 Some(Nim::Prim("RsLogFilter".into())),
4374 ));
4375 }
4376
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4377 // `Spacing::from(d)`: a `From` impl called through its target type.
4378 // Rust picks the impl by the argument's type, and so do we -- Nim
4379 // cannot overload on return type, so each impl has its own proc name.
4380 if name == "from" && codes.len() == 1 {
4381 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
4382 let q = if q == "Self" {
4383 self.self_ty.as_ref().map(type_name).unwrap_or(q)
4384 } else {
4385 q
4386 };
4387 if let Some(src) = args[0].ty.as_ref().map(type_name) {
4388 if let Some(f) = self.from_impls.get(&(src, q.clone())).cloned() {
4389 return Ok(Val::new(
4390 format!("{}({})", f, codes[0]),
4391 Some(Nim::Named(q, vec![])),
4392 ));
4393 }
4394 }
4395 }
4396 }
4397
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago4398 // `u32::from(b)`: `From` between primitives is lossless by definition
4399 // -- it is the widening direction only -- so a plain Nim conversion is
4400 // exact. (The truncating direction is `as`, which is `cast`.)
4401 if name == "from" && codes.len() == 1 {
4402 if let Some(q) = p.path.segments.iter().rev().nth(1) {
4403 if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) {
4404 return Ok(Val::new(
4405 format!("{}({})", t, codes[0]),
4406 Some(Nim::Prim(t)),
4407 ));
4408 }
4409 }
4410 }
4411
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4412 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
4413 // string view; no copy, no validation, same memory.
4414 if name == "from_utf8_unchecked" && codes.len() == 1 {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4415 // `String::from_utf8_unchecked(v)` takes ownership and yields an
4416 // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields
4417 // a view. Same name, different operations -- the qualifier says
4418 // which, and an unqualified call is ambiguous.
4419 let q = p
4420 .path
4421 .segments
4422 .iter()
4423 .rev()
4424 .nth(1)
4425 .map(|s| s.ident.to_string());
4426 return match q.as_deref() {
4427 Some("String") => Ok(Val::new(
4428 format!("rsStringOf({})", codes[0]),
4429 Some(Nim::Prim("string".into())),
4430 )),
4431 Some("str") => Ok(Val::new(
4432 format!("rsStrView({})", codes[0]),
4433 Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
4434 )),
4435 _ => Err(
4436 "`from_utf8_unchecked` must be written as `str::..` (a \
4437 borrowed view) or `String::..` (an owned string); the two \
4438 are different operations"
4439 .into(),
4440 ),
4441 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4442 }
4443
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4444 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
4445 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4446 let (ty, _) = self.variant_type(&def, expect)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4447 return Ok(Val::new(
4448 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4449 Some(ty),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4450 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4451 }
4452
4453 // A bare path that names a primitive type is Rust's tuple-struct-like
4454 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4455 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
4456 // is invoked.
4457 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
4458 return Ok(Val::new(
4459 format!("{}({})", ident(&name), codes.join(", ")),
4460 Some((*ret).clone()),
4461 ));
4462 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago4463 // `Adler32::new()` / `Adler32::default()`: a method called through
4464 // its type rather than through a receiver.
4465 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
4466 // `Self::new()` inside an `impl` names the type being implemented.
4467 let q = if q == "Self" {
4468 self.self_ty.as_ref().map(type_name).unwrap_or(q)
4469 } else {
4470 q
4471 };
4472 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago4473 // Re-lower the arguments with the declared parameter types, so
4474 // a literal takes the width the signature asks for.
4475 let declared = sig.params.clone();
4476 let mut args = args.clone();
4477 let mut codes = codes.clone();
4478 for (i, a) in c.args.iter().enumerate() {
4479 if let Some(want) = declared.get(i) {
4480 let want = want.clone().unvar();
4481 args[i] = self.expr_at(a, Some(&want))?;
4482 codes[i] = args[i].code.clone();
4483 }
4484 }
4485 let sig = &self.methods[&(q.clone(), name.clone())];
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4486 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
4487 let ret = Self::instantiate(sig, &arg_tys);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago4488 let nim = self
4489 .statics
4490 .get(&(q.clone(), name.clone()))
4491 .cloned()
4492 .unwrap_or_else(|| ident(&name));
4493 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
4494 }
4495 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4496 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
4497 let ret = target
4498 .as_ref()
4499 .and_then(|k| self.fns.get(k))
4500 .map(|sig| Self::instantiate(sig, &arg_tys));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4501 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 ago4502 return Err(format!(
4503 "call to unknown function `{name}`; only functions defined in \
4504 this file and the supported standard-library subset can be lowered"
4505 ));
4506 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4507 let nim = match &target {
4508 Some((m, n)) => self.fn_name(m, n),
4509 None => ident(&name),
4510 };
4511 Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4512 }
4513
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4514 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 ago4515 let name = m.method.to_string();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago4516 // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield.
4517 if name == "remainder" && m.args.is_empty() {
4518 if let Expr::Path(p) = &*m.receiver {
4519 if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) {
4520 if let Iter::Chunks { code, base, len, k, elem, .. } = &*it {
4521 let kept = format!("(({} div int({})) * int({}))", len, k, k);
4522 let mut v = Val::new(
4523 String::new(),
4524 elem.clone().map(|e| Nim::OpenArray(Box::new(e))),
4525 );
4526 v.window = Some(Alias::Window {
4527 code: code.clone(),
4528 off: format!("({} + {})", base, kept),
4529 len: format!("({} - {})", len, kept),
4530 elem: elem.clone(),
4531 });
4532 return Ok(v);
4533 }
4534 return Err(
4535 "`.remainder()` is only defined for a `chunks_exact` iterator".into(),
4536 );
4537 }
4538 }
4539 return Err("`.remainder()` needs an iterator bound by `let`".into());
4540 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4541 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
4542 match name.as_str() {
4543 "len" => {
4544 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
4545 }
4546 "is_empty" => {
4547 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
4548 }
4549 other => {
4550 return Err(format!(
4551 "`.{other}()` on a slice window from `chunks_exact`/\
4552 `windows` is not implemented; only indexing and \
4553 `len()` are"
4554 ))
4555 }
4556 }
4557 }
4558 let recv = self.expr(&m.receiver)?;
4559 let rt0 = recv.ty.clone();
4560
4561// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
4562 // way to put a view in an object, so instead of materialising an
4563 // Option the view and its validity condition travel together until
4564 // an `ok_or`/`?`/`unwrap` resolves them.
4565 if matches!(name.as_str(), "get" | "get_mut")
4566 && matches!(m.args.first(), Some(Expr::Range(_)))
4567 {
4568 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 10h ago4569 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 ago4570 let lo = match &r.start {
4571 Some(e) => format!("int({})", self.expr(e)?.code),
4572 None => "0".into(),
4573 };
4574 let len = match (&r.end, r.limits) {
4575 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
4576 format!("(int({}) - {})", self.expr(e)?.code, lo)
4577 }
4578 (Some(e), syn::RangeLimits::Closed(_)) => {
4579 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
4580 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4581 (None, _) => format!("({} - {})", blen, lo),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4582 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4583 // Hoisted, so the bounds are computed once -- as Rust computes
4584 // them once -- and cannot be re-evaluated later in a scope where
4585 // the names they mention have been shadowed by a loop pattern.
4586 let off_t = self.fresh("Off");
4587 let len_t = self.fresh("Len");
4588 self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
4589 self.line(&format!("let {}: int = {}", len_t, len));
4590 let elem = belem
4591 .or_else(|| elem_of(&rt0))
4592 .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 ago4593 let mut v = Val::new(
4594 String::new(),
4595 Some(Nim::Named(
4596 "Option".into(),
4597 vec![Nim::OpenArray(Box::new(elem.clone()))],
4598 )),
4599 );
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4600 v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4601 v.window = Some(Alias::Window {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4602 code,
4603 off: off_t,
4604 len: len_t,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4605 elem: Some(elem),
4606 });
4607 return Ok(v);
4608 }
4609
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4610 // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
4611 // parameter type comes from the receiver, so they are handled before
4612 // the arguments are lowered. The closure is expanded inline, with its
4613 // parameter aliased to the payload: that keeps the whole thing an
4614 // expression and avoids handing a view to a generic proc.
4615 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
4616 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
4617 (recv.ty.clone(), &m.args[0])
4618 {
4619 if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
4620 {
4621 return self.map_closure(&name, &recv, &kind, &targs, c);
4622 }
4623 }
4624 }
4625
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4626 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
4627 // own type; `v.push(e)` takes the element type.
4628 let arg_want = match (name.as_str(), &recv.ty) {
4629 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
4630 (_, t) => t.clone(),
4631 };
4632 let mut args = Vec::new();
4633 for a in &m.args {
4634 args.push(self.expr_at(a, arg_want.as_ref())?);
4635 }
4636 let a0 = args.first().map(|a| a.code.clone());
4637 let rt = recv.ty.clone();
4638
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 8h ago4639 // A method the input defines wins over our model of the standard
4640 // library: `is_empty` on a `bitflags!` type is that type's, not the
4641 // sequence one. Rust resolves inherent methods the same way.
4642 if let Some(t) = &rt {
4643 let key = (type_name(t), name.clone());
4644 if self.methods.contains_key(&key) {
4645 let declared = self.methods[&key].params.clone();
4646 let skip = usize::from(declared.len() == m.args.len() + 1);
4647 for (i, a) in m.args.iter().enumerate() {
4648 if let Some(want) = declared.get(i + skip) {
4649 let want = want.clone().unvar();
4650 args[i] = self.expr_at(a, Some(&want))?;
4651 }
4652 }
4653 let mut arg_tys: Vec<Option<Nim>> = vec![rt.clone()];
4654 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
4655 let ret = Self::instantiate(&self.methods[&key], &arg_tys);
4656 let nim = self
4657 .statics
4658 .get(&key)
4659 .cloned()
4660 .unwrap_or_else(|| ident(&name));
4661 let mut all = vec![recv.code.clone()];
4662 all.extend(args.iter().map(|a| a.code.clone()));
4663 return Ok(Val::new(format!("{}({})", nim, all.join(", ")), Some(ret)));
4664 }
4665 }
4666
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4667 let (code, ty) = match name.as_str() {
4668 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
4669 // explicit so that a `usize` binding type-checks on the Nim side.
4670 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
4671 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
4672 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
4673 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
4674 | "into_iter" => (recv.code.clone(), rt.clone()),
4675 "unwrap" | "expect" => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4676 // Expanded inline rather than called as a generic proc: when
4677 // the payload is a view, Nim can only borrow from a path
4678 // expression, which a proc body containing the panic is not.
4679 let (kind, inner) = match &rt {
4680 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
4681 ("Option", a[0].clone())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4682 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4683 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4684 ("Result", a[0].clone())
4685 }
4686 _ => {
4687 return Err(format!(
4688 "`.{name}()` needs a known `Option`/`Result` receiver type"
4689 ))
4690 }
4691 };
4692 if self.in_loop_cond {
4693 return Err(format!(
4694 "`.{name}()` in a loop condition is not implemented yet: the \
4695 check it expands to would run once, before the loop"
4696 ));
4697 }
4698 let tmp = self.fresh("Unwrap");
4699 let rty = rt.clone().unwrap();
4700 self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code));
4701 let (test, msg) = if kind == "Option" {
4702 (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value")
4703 } else {
4704 (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 ago4705 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago4706 let msg = if name == "expect" {
4707 args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg))
4708 } else {
4709 fmt::nim_str(msg)
4710 };
4711 self.line(&format!("if not {}:", test));
4712 self.line(&format!(" rsPanic({})", msg));
4713 // If the payload is a view, hand back an alias rather than a
4714 // value: Nim will not let a `let` borrow out of a local, and a
4715 // view is a reference anyway, so there is nothing to bind.
4716 // `{tmp}.val` is a plain field access, so substituting it at
4717 // each use re-evaluates nothing.
4718 if matches!(inner, Nim::OpenArray(_)) {
4719 let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone()));
4720 v.window = Some(Alias::Value {
4721 code: format!("{}.val", tmp),
4722 ty: Some(inner),
4723 });
4724 return Ok(v);
4725 }
4726 (format!("{}.val", tmp), Some(inner))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4727 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4728 "ok_or" if recv.guard.is_some() => {
4729 let e = args.first().ok_or("`ok_or` takes one argument")?;
4730 let ety = e.ty.clone();
4731 let mut v = recv.clone();
4732 v.guard_err = Some(e.code.clone());
4733 v.ty = match (&recv.ty, ety) {
4734 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
4735 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
4736 }
4737 _ => None,
4738 };
4739 return Ok(v);
4740 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago4741 "ok_or" => {
4742 let inner = match &rt {
4743 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
4744 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
4745 };
4746 let e = args.first().ok_or("`ok_or` takes one argument")?;
4747 let ety = e
4748 .ty
4749 .clone()
4750 .ok_or("`ok_or` needs a known error type for its argument")?;
4751 (
4752 format!(
4753 "rsOkOr[{}, {}]({}, {})",
4754 inner.render(),
4755 ety.render(),
4756 recv.code,
4757 e.code
4758 ),
4759 Some(Nim::Named("Result".into(), vec![inner, ety])),
4760 )
4761 }
4762 "unwrap_or" => {
4763 let inner = match &rt {
4764 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
4765 Some(a[0].clone())
4766 }
4767 _ => None,
4768 };
4769 (
4770 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
4771 inner,
4772 )
4773 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4774 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
4775 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
4776 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
4777 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
4778
4779 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
4780 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
4781 // Nim raises OverflowDefect, so the operation is routed through
4782 // the unsigned view of the same width, which is what Rust's
4783 // wrapping_* is defined to compute.
4784 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
4785 let op = match name.as_str() {
4786 "wrapping_add" => "+",
4787 "wrapping_sub" => "-",
4788 _ => "*",
4789 };
4790 let t = rt.clone().ok_or_else(|| {
4791 format!("`{name}` needs a known receiver type to pick the wrapping width")
4792 })?;
4793 if !t.is_integer() {
4794 return Err(format!("`{name}` on a non-integer type"));
4795 }
4796 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
4797 if t.is_unsigned() {
4798 (format!("({} {} {})", recv.code, op, arg), Some(t))
4799 } else {
4800 let u = unsigned_peer(&t)?;
4801 (
4802 format!(
4803 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
4804 t.render(), u, recv.code, op, u, arg
4805 ),
4806 Some(t),
4807 )
4808 }
4809 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4810 // Inside a formatting impl, a write through the `Formatter` *is*
4811 // 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 ago4812 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => {
4813 let a = args.first().ok_or("`write_str` takes one argument")?;
4814 // A `&str` argument is a character view, not a Nim string.
4815 let text = match &a.ty {
4816 Some(Nim::Prim(p)) if p == "string" => a.code.clone(),
4817 _ => format!("rsDisplay({})", a.code),
4818 };
4819 (format!("result.add({})", text), Some(Nim::Unit))
4820 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4821 "saturating_add" | "saturating_sub" | "saturating_mul" | "checked_add"
4822 | "checked_sub" | "checked_mul" => {
4823 let t = rt
4824 .clone()
4825 .filter(|t| t.is_integer())
4826 .ok_or_else(|| format!("`{name}` needs a known integer receiver"))?;
4827 let arg = args
4828 .first()
4829 .ok_or_else(|| format!("`{name}` takes one argument"))?;
4830 let f = match name.as_str() {
4831 "saturating_add" => "rsSatAdd",
4832 "saturating_sub" => "rsSatSub",
4833 "saturating_mul" => "rsSatMul",
4834 "checked_add" => "rsChkAdd",
4835 "checked_sub" => "rsChkSub",
4836 _ => "rsChkMul",
4837 };
4838 let out = if name.starts_with("checked") {
4839 Nim::Named("Option".into(), vec![t])
4840 } else {
4841 t
4842 };
4843 (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
4844 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago4845 // `as_ptr` hands a C function the address of the first element,
4846 // which is what Rust's does. An empty slice has no first element
4847 // in either language, and reading through the pointer would be
4848 // undefined in both.
4849 "as_ptr" | "as_mut_ptr" => {
4850 let elem = elem_of(&rt)
4851 .ok_or("`as_ptr` needs a known element type")?;
4852 (
4853 format!(
4854 "(if {r}.len == 0: nil else: cast[ptr {e}](addr {r}[0]))",
4855 r = recv.code,
4856 e = elem.render()
4857 ),
4858 Some(Nim::Ptr(Box::new(elem))),
4859 )
4860 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4861 "abs" => (format!("abs({})", recv.code), rt.clone()),
4862 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4863 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4864 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
4865 "as_bytes" | "into_bytes" => (
4866 format!("rsBytes({})", recv.code),
4867 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
4868 ),
4869
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4870 "into" => {
4871 // `.into()` resolves through the `impl From` declarations, and
4872 // needs the target type to pick one.
4873 let from = rt
4874 .clone()
4875 .ok_or("`.into()` needs a known receiver type")?;
4876 let to = expect
4877 .ok_or("`.into()` needs a known target type; annotate the binding")?;
4878 let key = (type_name(&from), type_name(to));
4879 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4880 format!(
4881 "no `impl From<{}> for {}` in this file, so `.into()` has \
4882 no conversion to call",
4883 key.0, key.1
4884 )
4885 })?;
4886 (format!("{}({})", f, recv.code), Some(to.clone()))
4887 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4888 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4889 // A method defined in this file via `impl`, found by the
4890 // receiver's type rather than by name alone.
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago4891 // A receiver whose type is a generic parameter is resolved by
4892 // Nim at instantiation, so the call just needs the right proc
4893 // name. Every impl of a trait method shares one, so any
4894 // candidate with this method name gives it.
4895 let generic_recv = rt
4896 .as_ref()
4897 .map(type_name)
4898 .is_some_and(|n| self.fn_generics.iter().any(|g| *g == n));
4899 let key = if generic_recv {
4900 let mut names: Vec<String> = self
4901 .statics
4902 .iter()
4903 .filter(|((_, m), _)| *m == name)
4904 .map(|(_, v)| v.clone())
4905 .collect();
4906 names.sort();
4907 names.dedup();
4908 match names.len() {
4909 1 => {
4910 let k = self
4911 .methods
4912 .keys()
4913 .find(|(_, m)| *m == name)
4914 .cloned();
4915 k
4916 }
4917 0 => None,
4918 _ => {
4919 return Err(format!(
4920 "`.{name}()` on a generic receiver is ambiguous: \
4921 {} different procs are named for it",
4922 names.len()
4923 ))
4924 }
4925 }
4926 } else {
4927 rt.as_ref().map(|t| (type_name(t), name.clone()))
4928 };
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 8h ago4929 // Re-lower the arguments with the declared parameter types:
4930 // a method's own signature says what width its literals are,
4931 // which the receiver's type does not.
4932 let declared: Option<Vec<Nim>> = key
4933 .as_ref()
4934 .and_then(|k| self.methods.get(k))
4935 .map(|s| s.params.clone());
4936 if let Some(d) = &declared {
4937 // params[0] is the receiver for a method with `self`.
4938 let skip = usize::from(d.len() == m.args.len() + 1);
4939 for (i, a) in m.args.iter().enumerate() {
4940 if let Some(want) = d.get(i + skip) {
4941 let want = want.clone().unvar();
4942 args[i] = self.expr_at(a, Some(&want))?;
4943 }
4944 }
4945 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4946 let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
4947 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago4948 let sig = key
4949 .as_ref()
4950 .and_then(|k| self.methods.get(k))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 8h ago4951 .map(|s| Self::instantiate(s, &arg_tys));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago4952 if let Some(ret) = sig {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago4953 // Use the name the proc was actually emitted under: an
4954 // inherent method is qualified by its module, a trait
4955 // method by its trait.
4956 let nim = key
4957 .and_then(|k| self.statics.get(&k).cloned())
4958 .unwrap_or_else(|| ident(&name));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4959 let mut all = vec![recv.code.clone()];
4960 all.extend(args.iter().map(|a| a.code.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago4961 (format!("{}({})", nim, all.join(", ")), Some(ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago4962 } else {
4963 return Err(format!(
4964 "unsupported method `.{name}()`; it is neither defined in \
4965 this file nor part of the standard-library subset that \
4966 has a verified Nim equivalent"
4967 ));
4968 }
4969 }
4970 };
4971 Ok(Val::new(code, ty))
4972 }
4973
4974 // -------------------------------------------------------------- macros
4975
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago4976 /// The element type of a `vec![..]`, from its first element.
4977 fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
4978 let body = mac.tokens.to_string();
4979 if body.trim().is_empty() {
4980 return Ok(None);
4981 }
4982 let first: Option<Expr> = if body.contains(';') {
4983 // The whole body must be consumed or the parse fails, so the
4984 // length is parsed too even though only the element is wanted.
4985 mac.parse_body_with(|input: syn::parse::ParseStream| {
4986 let v: Expr = input.parse()?;
4987 input.parse::<syn::Token![;]>()?;
4988 let _len: Expr = input.parse()?;
4989 Ok(v)
4990 })
4991 .ok()
4992 } else {
4993 mac.parse_body_with(
4994 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4995 )
4996 .ok()
4997 .and_then(|p| p.into_iter().next())
4998 };
4999 match first {
5000 Some(e) => Ok(self.expr(&e)?.ty),
5001 None => Ok(None),
5002 }
5003 }
5004
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5005 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
5006 let name = path_name(&mac.path);
5007 match name.as_str() {
5008 "println" | "print" | "eprintln" | "eprint" => {
5009 let s = self.format_args(mac)?;
5010 let nl = name.ends_with("ln");
5011 Ok(match (name.starts_with('e'), nl) {
5012 (false, true) => format!("echo {s}"),
5013 (false, false) => format!("stdout.write({s})"),
5014 (true, true) => format!("stderr.writeLine({s})"),
5015 (true, false) => format!("stderr.write({s})"),
5016 })
5017 }
5018 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago5019 "write" | "writeln" => {
5020 // `write!(f, "..", ..)` inside a formatting impl: the first
5021 // argument is the sink, the rest is an ordinary format call.
5022 let args: Vec<Expr> = mac
5023 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
5024 .map_err(|e| format!("write!: {e}"))?
5025 .into_iter()
5026 .collect();
5027 let sink = args.first().ok_or("`write!` needs a sink")?;
5028 if !self.is_fmt_param(sink) {
5029 return Err("`write!` to anything but the `Formatter` of the \
5030 enclosing formatting impl is not implemented"
5031 .into());
5032 }
5033 let s = self.format_pieces(&args[1..])?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago5034 let s = if name == "writeln" {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago5035 format!("({} & \"\\n\")", s)
5036 } else {
5037 s
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago5038 };
5039 Ok(format!("result.add({})", s))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago5040 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5041 "panic" => {
5042 let s = self.format_args(mac)?;
5043 Ok(format!("rsPanic({s})"))
5044 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago5045 // `debug_assert*` fires in debug builds, which is the profile
5046 // this project models, so it lowers the same as `assert*`.
5047 "assert" | "debug_assert" => {
5048 let args: Vec<Expr> = mac
5049 .parse_body_with(
5050 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
5051 )
5052 .map_err(|e| format!("{name}!: {e}"))?
5053 .into_iter()
5054 .collect();
5055 let cond = args.first().ok_or("`assert!` needs a condition")?;
5056 let v = self.expr(cond)?;
5057 let msg = if args.len() > 1 {
5058 self.format_pieces(&args[1..])?
5059 } else {
5060 fmt::nim_str("assertion failed")
5061 };
5062 Ok(format!("(if not ({}): rsPanic({}))", v.code, msg))
5063 }
5064 "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => {
5065 let args: Vec<Expr> = mac
5066 .parse_body_with(
5067 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
5068 )
5069 .map_err(|e| format!("{name}!: {e}"))?
5070 .into_iter()
5071 .collect();
5072 if args.len() < 2 {
5073 return Err(format!("`{name}!` takes two operands"));
5074 }
5075 let a = self.expr(&args[0])?;
5076 let b = self.expr_at(&args[1], a.ty.as_ref())?;
5077 let ne = name.ends_with("_ne");
5078 let op = if ne { "!=" } else { "==" };
5079 // Rust's message shows both sides; reproducing it keeps a
5080 // failing assertion as informative as the original.
5081 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 ago5082 Ok(format!(
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago5083 "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))",
5084 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 ago5085 ))
5086 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 7h ago5087 // The `log` facade. See `src/prelude.nim` for why these are
5088 // lowered directly rather than expanded. The enabled check wraps
5089 // the whole thing because Rust does not evaluate a log record's
5090 // arguments when the level is disabled.
5091 "error" | "warn" | "info" | "debug" | "trace" | "log" => {
5092 let args: Vec<Expr> = mac
5093 .parse_body_with(
5094 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
5095 )
5096 .map_err(|e| format!("`{name}!`: {e}"))?
5097 .into_iter()
5098 .collect();
5099 let (level, rest) = if name == "log" {
5100 let first = args.first().ok_or("`log!` needs a level")?;
5101 (self.log_level_of(first)?, &args[1..])
5102 } else {
5103 (
5104 match name.as_str() {
5105 "error" => "rsLvlError",
5106 "warn" => "rsLvlWarn",
5107 "info" => "rsLvlInfo",
5108 "debug" => "rsLvlDebug",
5109 _ => "rsLvlTrace",
5110 }
5111 .to_string(),
5112 &args[..],
5113 )
5114 };
5115 let msg = self.format_pieces(rest)?;
5116 let target = fmt::nim_str(&self.cur_mod.clone());
5117 Ok(format!(
5118 "(if rsLogEnabled({lvl}): rsLog({lvl}, {target}, {msg}))",
5119 lvl = level
5120 ))
5121 }
5122 "log_enabled" => {
5123 let e: Expr = mac
5124 .parse_body()
5125 .map_err(|e| format!("`log_enabled!`: {e}"))?;
5126 let l = self.log_level_of(&e)?;
5127 Ok(format!("rsLogEnabled({l})"))
5128 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5129 "vec" => {
5130 let body = mac.tokens.to_string();
5131 if body.trim().is_empty() {
5132 return Ok("@[]".into());
5133 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago5134 // `vec![elem; n]` is the repeat form, not a list. The macro
5135 // body has no brackets, so it is parsed directly.
5136 if body.contains(';') {
5137 let (v, n) = mac
5138 .parse_body_with(|input: syn::parse::ParseStream| {
5139 let v: Expr = input.parse()?;
5140 input.parse::<syn::Token![;]>()?;
5141 let n: Expr = input.parse()?;
5142 Ok((v, n))
5143 })
5144 .map_err(|e| format!("vec![elem; n]: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago5145 let want = self.vec_expect.clone();
5146 let v = self.expr_at(&v, want.as_ref())?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago5147 let n = self.expr(&n)?;
5148 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
5149 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5150 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
5151 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
5152 .map_err(|e| format!("vec!: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago5153 let want = self.vec_expect.clone();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5154 let mut parts = Vec::new();
5155 for e in &elems {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago5156 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 ago5157 }
5158 Ok(format!("@[{}]", parts.join(", ")))
5159 }
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 7h ago5160 other => {
5161 if let Some(why) = self.mrules_bad.get(other) {
5162 return Err(format!("`{other}!` cannot be expanded: {why}"));
5163 }
5164 if let Some(def) = self.mrules.get(other).cloned() {
Expand multi-rule and recursive macro_rules d459281 nandithebull 6h ago5165 // A multi-rule macro commonly recurses, shedding one
5166 // argument per step. The depth limit turns a macro that
5167 // does not terminate into an error rather than a hang.
5168 if self.macro_depth > 64 {
5169 return Err(format!(
5170 "`{other}!` expanded more than 64 levels deep; it \
5171 does not appear to terminate"
5172 ));
5173 }
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 7h ago5174 let expanded = def
5175 .expand(mac.tokens.clone())
5176 .map_err(|e| format!("expanding `{other}!`: {e}"))?;
5177 // The expansion is ordinary Rust, lowered in a context
5178 // where its types are known.
5179 let e: Expr = syn::parse2(expanded.clone()).map_err(|_| {
5180 format!(
5181 "`{other}!` expands to something that is not an \
5182 expression: `{}`",
5183 expanded
5184 )
5185 })?;
Expand multi-rule and recursive macro_rules d459281 nandithebull 6h ago5186 self.macro_depth += 1;
5187 let r = self.expr(&e);
5188 self.macro_depth -= 1;
5189 return Ok(r?.code);
Expand `macro_rules!` rather than translating it to a Nim template 04eb29f nandithebull 7h ago5190 }
5191 Err(format!(
5192 "unsupported macro `{other}!`; a macro whose expansion is not \
5193 known cannot be lowered faithfully"
5194 ))
5195 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5196 }
5197 }
5198
5199 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
5200 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 ago5201 let args: Vec<Expr> = mac
5202 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
5203 .map_err(|e| format!("format arguments: {e}"))?
5204 .into_iter()
5205 .collect();
5206 self.format_pieces(&args)
5207 }
5208
5209 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
5210 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
5211 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 ago5212 if args.is_empty() {
5213 return Ok("\"\"".into());
5214 }
5215 return Err("the first argument must be a literal format string".into());
5216 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago5217 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5218
5219 let pieces = fmt::parse(&s.value())?;
5220 let mut parts: Vec<String> = Vec::new();
5221 let mut next = 0usize;
5222 let mut used = vec![false; rest.len()];
5223 for p in &pieces {
5224 match p {
5225 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
5226 fmt::Piece::Arg { r#ref, spec } => {
5227 let v = match r#ref {
5228 fmt::Ref::Next => {
5229 let e = rest.get(next).ok_or("too few arguments for format string")?;
5230 used[next] = true;
5231 next += 1;
5232 self.expr(e)?
5233 }
5234 fmt::Ref::Index(i) => {
5235 let e = rest.get(*i).ok_or("format index out of range")?;
5236 used[*i] = true;
5237 self.expr(e)?
5238 }
5239 fmt::Ref::Named(n) => {
5240 let t = self.lookup(n).ok_or_else(|| {
5241 format!("`{{{n}}}` captures `{n}`, which is not in scope")
5242 })?;
5243 Val::new(ident(n), Some(t))
5244 }
5245 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 9h ago5246 let integer = v.ty.as_ref().is_some_and(|t| t.is_integer());
5247 if spec.radix.is_some() && !integer && v.ty.is_none() {
5248 return Err(
5249 "a radix format (`{:x}`, `{:b}`, ...) needs a known \
5250 argument type: on an integer it formats the bit \
5251 pattern, on anything else it calls that type's own \
5252 impl"
5253 .into(),
5254 );
5255 }
5256 parts.push(fmt::render_arg(&v.code, spec, integer));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5257 }
5258 }
5259 }
5260 // Rust rejects an argument that no `{}` consumes; so do we, rather
5261 // than dropping it from the output.
5262 if let Some(i) = used.iter().position(|u| !u) {
5263 return Err(format!(
5264 "argument {} is never used by the format string",
5265 i + 1
5266 ));
5267 }
5268 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
5269 }
5270}
5271
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago5272/// Whether a pattern introduces a binding.
5273fn binds(p: &Pat) -> bool {
5274 match p {
5275 Pat::Ident(_) => true,
5276 Pat::Guard(g) => binds(&g.pat),
5277 Pat::Paren(x) => binds(&x.pat),
5278 Pat::Reference(r) => binds(&r.pat),
5279 Pat::Or(o) => o.cases.iter().any(binds),
5280 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
5281 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
5282 _ => false,
5283 }
5284}
5285
5286/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
5287fn destructures(p: &Pat) -> bool {
5288 matches!(
5289 p,
5290 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
5291 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
5292 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
5293 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
5294}
5295
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5296/// Whether an expression has a direct Nim expression form.
5297///
5298/// Nim's `if` is an expression only when every arm is a single expression, and
5299/// its `case` is never one here. Anything else has to be lowered as statements
5300/// that assign into a target.
5301fn expressible(e: &Expr) -> bool {
5302 match e {
5303 Expr::If(i) => {
5304 let Some(then) = single_expr(&i.then_branch) else { return false };
5305 if !expressible(then) {
5306 return false;
5307 }
5308 match &i.else_branch {
5309 None => false,
5310 Some((_, els)) => match &**els {
5311 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
5312 other => expressible(other),
5313 },
5314 }
5315 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 7h ago5316 // `unsafe { .. }` is transparent, so it is an expression exactly when
5317 // its block is one.
5318 Expr::Unsafe(u) => single_expr(&u.block).is_some_and(expressible),
Expand `$(..)` repetition in macro_rules 3d4a7d2 nandithebull 6h ago5319 Expr::Block(b) => {
5320 b.label.is_none() && matches!(b.block.stmts.last(), Some(Stmt::Expr(_, None)))
5321 }
5322 Expr::Match(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5323 _ => true,
5324 }
5325}
5326
5327/// The single expression a block consists of, if that is all it is. An `if`
5328/// can only be lowered as a Nim `if`-expression when both arms are this shape.
5329fn single_expr(b: &syn::Block) -> Option<&Expr> {
5330 match (b.stmts.len(), b.stmts.first()) {
5331 (1, Some(Stmt::Expr(e, None))) => Some(e),
5332 _ => None,
5333 }
5334}
5335
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago5336/// Substitute `params[i] -> args[i]` through a type. Enough of the type
5337/// grammar is covered to expand the aliases we accept; anything else is left
5338/// alone and will be reported by `ty::map` if it is unsupported.
5339fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
5340 use syn::Type;
5341 match t {
5342 Type::Path(p) => {
5343 if p.qself.is_none() && p.path.segments.len() == 1 {
5344 let seg = &p.path.segments[0];
5345 if seg.arguments.is_empty() {
5346 let name = seg.ident.to_string();
5347 if let Some(i) = params.iter().position(|x| *x == name) {
5348 return args[i].clone();
5349 }
5350 }
5351 }
5352 let mut p = p.clone();
5353 for seg in &mut p.path.segments {
5354 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
5355 for g in &mut a.args {
5356 if let syn::GenericArgument::Type(t) = g {
5357 *t = substitute(t, params, args);
5358 }
5359 }
5360 }
5361 }
5362 Type::Path(p)
5363 }
5364 Type::Reference(r) => {
5365 let mut r = r.clone();
5366 r.elem = Box::new(substitute(&r.elem, params, args));
5367 Type::Reference(r)
5368 }
5369 Type::Slice(sl) => {
5370 let mut sl = sl.clone();
5371 sl.elem = Box::new(substitute(&sl.elem, params, args));
5372 Type::Slice(sl)
5373 }
5374 Type::Array(a) => {
5375 let mut a = a.clone();
5376 a.elem = Box::new(substitute(&a.elem, params, args));
5377 Type::Array(a)
5378 }
5379 Type::Tuple(tp) => {
5380 let mut tp = tp.clone();
5381 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
5382 Type::Tuple(tp)
5383 }
5384 Type::Paren(p) => substitute(&p.elem, params, args),
5385 Type::Group(g) => substitute(&g.elem, params, args),
5386 other => other.clone(),
5387 }
5388}
5389
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5390// --------------------------------------------------------------- utilities
5391
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago5392/// Whether a return type is a borrow of one of the arguments, which Nim
5393/// models with a view rather than with an owned copy.
5394fn returns_borrow(t: &syn::Type) -> bool {
5395 match t {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago5396 syn::Type::Reference(r) => match &*r.elem {
5397 syn::Type::Slice(_) => true,
5398 // `&str` is a borrow of someone else's bytes too, and returning it
5399 // means returning a view, not an owned string.
5400 syn::Type::Path(p) => p.path.is_ident("str"),
5401 _ => false,
5402 },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago5403 syn::Type::Paren(p) => returns_borrow(&p.elem),
5404 syn::Type::Group(g) => returns_borrow(&g.elem),
5405 _ => false,
5406 }
5407}
5408
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 10h ago5409/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
5410/// to the crate root, which is where a flattened module's items live unless
5411/// they came from one of the extra input files.
5412fn module_of(prefix: &[String]) -> String {
5413 match prefix.last() {
5414 Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
5415 _ => String::new(),
5416 }
5417}
5418
5419/// The first type argument of an `Option[T]` / `Result[T, E]`.
5420fn elem_arg(t: &Nim) -> Nim {
5421 match t {
5422 Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
5423 other => other.clone(),
5424 }
5425}
5426
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago5427/// The element type of a sequence-like Nim type.
5428fn elem_of(t: &Option<Nim>) -> Option<Nim> {
5429 match t {
5430 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
5431 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
5432 _ => None,
5433 }
5434}
5435
5436/// The short name a Nim type is known by, for keying method tables.
5437fn type_name(t: &Nim) -> String {
5438 match t {
5439 Nim::Named(n, _) => n.clone(),
5440 Nim::Prim(p) => p.clone(),
5441 other => other.render(),
5442 }
5443}
5444
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago5445/// `(trait, operator)` for every operator trait we dispatch.
5446const OPERATOR_TRAITS: &[(&str, &str)] = &[
5447 ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"),
5448 ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"),
5449 ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="),
5450 ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="),
5451 ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="),
5452 ("Neg", "neg"), ("Not", "not"),
5453];
5454
5455/// `(operator, trait method name)`.
5456const OP_METHOD: &[(&str, &str)] = &[
5457 ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"),
5458 ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"),
5459 ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"),
5460 ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"),
5461 ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"),
5462 (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"),
5463];
5464
5465fn op_method(op: &str) -> &'static str {
5466 OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("")
5467}
5468
5469/// The operator symbol a compound assignment applies.
5470fn compound_symbol(op: &BinOp) -> &'static str {
5471 match op {
5472 BinOp::AddAssign(_) => "+=",
5473 BinOp::SubAssign(_) => "-=",
5474 BinOp::MulAssign(_) => "*=",
5475 BinOp::DivAssign(_) => "/=",
5476 BinOp::RemAssign(_) => "%=",
5477 BinOp::BitAndAssign(_) => "&=",
5478 BinOp::BitOrAssign(_) => "|=",
5479 BinOp::BitXorAssign(_) => "^=",
5480 BinOp::ShlAssign(_) => "<<=",
5481 BinOp::ShrAssign(_) => ">>=",
5482 _ => "",
5483 }
5484}
5485
5486fn binary_symbol(op: &BinOp) -> &'static str {
5487 match op {
5488 BinOp::Add(_) => "+",
5489 BinOp::Sub(_) => "-",
5490 BinOp::Mul(_) => "*",
5491 BinOp::Div(_) => "/",
5492 BinOp::Rem(_) => "%",
5493 BinOp::BitAnd(_) => "&",
5494 BinOp::BitOr(_) => "|",
5495 BinOp::BitXor(_) => "^",
5496 BinOp::Shl(_) => "<<",
5497 BinOp::Shr(_) => ">>",
5498 _ => "",
5499 }
5500}
5501
5502/// The operator a trait overloads, if it is one of the operator traits.
5503fn operator_trait(t: &str) -> Option<&'static str> {
5504 Some(match t {
5505 "Add" => "+",
5506 "Sub" => "-",
5507 "Mul" => "*",
5508 "Div" => "/",
5509 "Rem" => "%",
5510 "BitAnd" => "&",
5511 "BitOr" => "|",
5512 "BitXor" => "^",
5513 "Shl" => "<<",
5514 "Shr" => ">>",
5515 "AddAssign" => "+=",
5516 "SubAssign" => "-=",
5517 "MulAssign" => "*=",
5518 "DivAssign" => "/=",
5519 "RemAssign" => "%=",
5520 "BitAndAssign" => "&=",
5521 "BitOrAssign" => "|=",
5522 "BitXorAssign" => "^=",
5523 "ShlAssign" => "<<=",
5524 "ShrAssign" => ">>=",
5525 "Neg" => "neg",
5526 "Not" => "not",
5527 _ => return None,
5528 })
5529}
5530
Lower argument-position `impl Trait`; it was a generic parameter all along 4e4d09d nandithebull 6h ago5531/// The Nim proc name for a trait method. Qualified by the *trait* but not by
5532/// the type: every impl of one trait method shares a name and Nim overloads on
5533/// the first parameter, which is how a call on a generic receiver resolves at
5534/// instantiation. Two traits declaring the same method name still cannot
5535/// collide, because the trait is in the name.
5536fn trait_method_name(ty: &str, tr: &str, m: &str, has_self: bool) -> String {
5537 if has_self {
5538 format!("rs{}_{}", tr, m)
5539 } else {
5540 // No receiver to overload on -- `Default::default()` takes none -- so
5541 // two impls would differ only in return type, which Nim cannot
5542 // resolve. The type goes in the name instead.
5543 format!("rs{}_{}_{}", tr, ty, m)
5544 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago5545}
5546
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 10h ago5547fn is_fmt_trait(t: &str) -> bool {
5548 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
5549}
5550
5551/// The prelude proc a formatting trait's output is produced by.
5552fn fmt_proc(t: &str) -> &'static str {
5553 match t {
5554 "Display" => "rsDisplay",
5555 "Debug" => "rsDebug",
5556 "LowerHex" => "rsLowerHex",
5557 "UpperHex" => "rsUpperHex",
5558 "Binary" => "rsBinary",
5559 _ => "rsOctal",
5560 }
5561}
5562
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 9h ago5563/// Whether an expression is an iterator-producing chain rather than a value.
5564fn is_iterator_expr(e: &Expr) -> bool {
5565 match e {
5566 Expr::MethodCall(m) => matches!(
5567 m.method.to_string().as_str(),
5568 "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact"
5569 | "chunks_exact_mut" | "windows"
5570 ),
5571 Expr::Paren(p) => is_iterator_expr(&p.expr),
5572 _ => false,
5573 }
5574}
5575
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 9h ago5576/// Whether an expression denotes a place -- a variable, a field, or an index
5577/// or slice of one -- and so may be re-evaluated with no side effect.
5578fn is_pure_place(e: &Expr) -> bool {
5579 match e {
5580 Expr::Path(_) => true,
5581 Expr::Field(f) => is_pure_place(&f.base),
5582 Expr::Index(i) => {
5583 is_pure_place(&i.expr)
5584 && match &*i.index {
5585 Expr::Range(r) => {
5586 r.start.as_deref().map_or(true, is_pure_place)
5587 && r.end.as_deref().map_or(true, is_pure_place)
5588 }
5589 other => is_pure_place(other),
5590 }
5591 }
5592 Expr::Lit(_) => true,
5593 Expr::Reference(r) => is_pure_place(&r.expr),
5594 Expr::Paren(p) => is_pure_place(&p.expr),
5595 Expr::Group(g) => is_pure_place(&g.expr),
5596 // Arithmetic on places is still side-effect free, so a bound like
5597 // `..want - 1` does not stop the binding being an alias.
5598 Expr::Binary(b) if !is_compound(&b.op) => {
5599 is_pure_place(&b.left) && is_pure_place(&b.right)
5600 }
5601 Expr::Unary(u) => is_pure_place(&u.expr),
5602 Expr::Cast(c) => is_pure_place(&c.expr),
5603 _ => false,
5604 }
5605}
5606
5607/// Whether an expression is a `&mut` borrow, directly or through parens.
5608fn is_mut_borrow(e: &Expr) -> bool {
5609 match e {
5610 Expr::Reference(r) => r.mutability.is_some(),
5611 Expr::Paren(p) => is_mut_borrow(&p.expr),
5612 Expr::Group(g) => is_mut_borrow(&g.expr),
5613 _ => false,
5614 }
5615}
5616
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5617fn takes_self(sig: &syn::Signature) -> bool {
5618 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
5619}
5620
5621fn path_name(p: &syn::Path) -> String {
5622 p.segments
5623 .last()
5624 .map(|s| s.ident.to_string())
5625 .unwrap_or_default()
5626}
5627
5628fn is_compound(op: &BinOp) -> bool {
5629 matches!(
5630 op,
5631 BinOp::AddAssign(_)
5632 | BinOp::SubAssign(_)
5633 | BinOp::MulAssign(_)
5634 | BinOp::DivAssign(_)
5635 | BinOp::RemAssign(_)
5636 | BinOp::BitAndAssign(_)
5637 | BinOp::BitOrAssign(_)
5638 | BinOp::BitXorAssign(_)
5639 | BinOp::ShlAssign(_)
5640 | BinOp::ShrAssign(_)
5641 )
5642}
5643
5644/// The Nim literal suffix for an integer type (`5'i32`).
5645fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
5646 let Nim::Prim(p) = t else {
5647 return Err("not a primitive integer".into());
5648 };
5649 Ok(match p.as_str() {
5650 "int8" => "i8",
5651 "int16" => "i16",
5652 "int32" => "i32",
5653 "int64" => "i64",
5654 "int" => "i",
5655 "uint8" => "u8",
5656 "uint16" => "u16",
5657 "uint32" => "u32",
5658 "uint64" => "u64",
5659 "uint" => "u",
5660 other => return Err(format!("no Nim literal suffix for `{other}`")),
5661 })
5662}
5663
5664/// The unsigned integer type of the same width, used to spell `wrapping_*`.
5665fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
5666 let Nim::Prim(p) = t else {
5667 return Err("not a primitive integer".into());
5668 };
5669 Ok(match p.as_str() {
5670 "int8" => "uint8",
5671 "int16" => "uint16",
5672 "int32" => "uint32",
5673 "int64" => "uint64",
5674 "int" => "uint",
5675 other => return Err(format!("`{other}` has no unsigned peer")),
5676 })
5677}
5678
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 10h ago5679fn quote_meta(m: &syn::Meta) -> String {
5680 match m {
5681 syn::Meta::Path(p) => path_name(p),
5682 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
5683 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
5684 }
5685}
5686
5687fn item_attrs(i: &Item) -> &[syn::Attribute] {
5688 match i {
5689 Item::Fn(f) => &f.attrs,
5690 Item::Struct(s) => &s.attrs,
5691 Item::Enum(e) => &e.attrs,
5692 Item::Impl(x) => &x.attrs,
5693 Item::Const(c) => &c.attrs,
5694 Item::Type(t) => &t.attrs,
5695 Item::Mod(m) => &m.attrs,
5696 Item::Use(u) => &u.attrs,
5697 Item::ExternCrate(e) => &e.attrs,
5698 Item::Static(s) => &s.attrs,
5699 _ => &[],
5700 }
5701}
5702
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 11h ago5703fn item_kind(i: &Item) -> &'static str {
5704 match i {
5705 Item::Trait(_) => "`trait`",
5706 Item::Static(_) => "`static`",
5707 Item::Macro(_) => "macro definition",
5708 Item::Union(_) => "`union`",
5709 _ => "item",
5710 }
5711}
5712
5713fn expr_kind(e: &Expr) -> &'static str {
5714 match e {
5715 Expr::Async(_) => "`async` block",
5716 Expr::Await(_) => "`.await`",
5717 Expr::Try(_) => "`?`",
5718 Expr::Range(_) => "range",
5719 Expr::Match(_) => "`match` (only statement position is implemented)",
5720 Expr::Let(_) => "`let` expression",
5721 Expr::Unsafe(_) => "`unsafe` block",
5722 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
5723 _ => "expression",
5724 }
5725}