nandi/rustnimpublic Fork 0
12c0a01b02392b18af24b583ba4ff65ad040e86a
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 · 5382 lines · 228.0 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1//! Rust AST -> Nim source.
2//!
3//! The governing rule is in DESIGN.md and it shapes every function here:
4//! anything whose Rust semantics cannot be reproduced exactly in Nim returns
5//! `Err` with a reason. Nothing is emitted on a guess. Where a construct maps
6//! one-to-one (signed `shr`, unsigned wrapping, truncating `div`/`mod`) the
7//! mapping is direct and there is a comment saying why that is safe.
8
9use crate::fmt;
10use crate::ty::{self, Nim};
11use std::collections::HashMap;
12use syn::{
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago13 BinOp, Expr, FnArg, GenericArgument, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago14};
15
16// --------------------------------------------------------------- vocabulary
17
18/// Nim keywords. Rust code may legally use any of these as an identifier.
19const NIM_KEYWORDS: &[&str] = &[
20 "addr", "and", "as", "asm", "bind", "block", "break", "case", "cast",
21 "concept", "const", "continue", "converter", "defer", "discard", "distinct",
22 "div", "do", "elif", "else", "end", "enum", "except", "export", "finally",
23 "for", "from", "func", "if", "import", "in", "include", "interface", "is",
24 "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not",
25 "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref",
26 "return", "shl", "shr", "static", "template", "try", "tuple", "type",
27 "using", "var", "when", "while", "xor", "result", "echo",
28];
29
30fn ident(name: &str) -> String {
31 if NIM_KEYWORDS.contains(&name) {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago32 return format!("{name}_r");
33 }
34 // Nim identifiers may not begin with an underscore, and may not contain
35 // two in a row. Rust uses both freely (`_unused`, `__private`).
36 let mut out = String::new();
37 let mut last_us = false;
38 for (i, c) in name.chars().enumerate() {
39 if c == '_' {
40 if i == 0 {
41 out.push('u');
42 out.push('_');
43 last_us = true;
44 continue;
45 }
46 if last_us {
47 continue;
48 }
49 last_us = true;
50 out.push('_');
51 } else {
52 last_us = false;
53 out.push(c);
54 }
55 }
56 if out.ends_with('_') {
57 out.push('x');
58 }
59 out
60}
61
62/// A `for`-loop source, resolved from a chain of iterator adaptors.
63///
64/// Rust's slice iterators are lazy and compose; Nim's `for` is over one
65/// sequence. So a chain is resolved into this shape and then emitted as a
66/// single index loop, with each binding becoming an *lvalue* into the original
67/// container. That is what makes `*dst = v` through `iter_mut()` write back to
68/// the caller's slice rather than to a copy.
69#[derive(Clone, Debug)]
70enum Iter {
71 /// `a..b` / `a..=b`.
72 Range { lo: String, hi: String, closed: bool, ty: Option<Nim> },
73 /// `for x in a`, `a.iter()`, `a.iter_mut()`. `off`/`len` let the same
74 /// shape cover a subslice view. `mutable` only affects whether the binding
75 /// may be assigned through.
76 Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool },
77 /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of
78 /// `k` elements starting at `k * i`.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago79 Chunks { code: String, base: String, len: String, k: String, elem: Option<Nim>, mutable: bool },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago80 /// `a.windows(k)`: like `Chunks` but advancing one element at a time.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago81 Windows { code: String, base: String, len: String, k: String, elem: Option<Nim> },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago82 /// `.enumerate()` — the index is the first half of the pair.
83 Enumerate(Box<Iter>),
84 /// `.zip(other)` — stops at the shorter, as Rust's does.
85 Zip(Box<Iter>, Box<Iter>),
86}
87
88impl Iter {
89 /// The number of iterations, as a Nim expression in terms of the loop's
90 /// own containers.
91 fn len(&self) -> String {
92 match self {
93 Iter::Range { lo, hi, closed, .. } => {
94 let n = format!("(int({hi}) - int({lo}))");
95 if *closed { format!("({n} + 1)") } else { n }
96 }
97 Iter::Elems { len, .. } => len.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago98 Iter::Chunks { k, len, .. } => format!("({} div int({}))", len, k),
99 Iter::Windows { len, k, .. } => format!("(max(0, {} - int({}) + 1))", len, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago100 Iter::Enumerate(i) => i.len(),
101 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
102 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago103 }
104}
105
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago106/// How a `for`-loop pattern name refers back into the container it came from.
107#[derive(Clone, Debug)]
108enum Alias {
109 /// The name stands for this Nim lvalue expression.
110 Value { code: String, ty: Option<Nim> },
111 /// The name stands for a window: `code[off .. off + len - 1]`.
112 Window { code: String, off: String, len: String, elem: Option<Nim> },
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h 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 18h ago117}
118
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h 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 18h 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 19h 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 18h 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 19h ago144 }
145 fn untyped(code: impl Into<String>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago146 Val::new(code, None)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h 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 16h 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 19h ago156}
157
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h 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 15h 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 19h 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 19h 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 18h 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 18h 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 17h 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 16h 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 16h 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 `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago219 /// Types declared by a `bitflags!` invocation.
220 bitflags: std::collections::HashSet<String>,
221 /// `(type, flag) -> nim const name`.
222 flag_consts: HashMap<(String, String), String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago223 /// `use` brings a name into scope from another module. Flattening loses
224 /// the module structure, so the mapping is recorded and consulted when a
225 /// bare call is resolved.
226 use_map: HashMap<String, String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago227 /// struct name -> (field, type)
228 structs: HashMap<String, Vec<(String, Nim)>>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago229 enums: HashMap<String, EnumDef>,
230 /// variant name -> enums declaring it. A variant named by more than one
231 /// enum must be written qualified, or it is rejected as ambiguous.
232 variant_owner: HashMap<String, Vec<String>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago233 /// `(receiver type, method) -> signature`. Keyed by type because two
234 /// types may define the same method name, and Nim tells them apart by
235 /// overload resolution on the first parameter.
236 methods: HashMap<(String, String), Sig>,
237 /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
238 /// on a user type can be checked rather than assumed.
239 fmt_impls: HashMap<(String, String), ()>,
240 /// `(from, to)` conversions declared by `impl From<A> for B`.
241 from_impls: HashMap<(String, String), String>,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago242 /// Operator traits implemented for a type, so `a += b` on a user type can
243 /// be dispatched to the impl rather than to Nim's built-in operator.
244 op_impls: HashMap<(String, String), ()>,
245 /// `(type, method) -> nim name`, for calls written as `Type::method(..)`.
246 statics: HashMap<(String, String), String>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago247 /// Forward declarations, emitted between the type definitions and the
248 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
249 /// proc is declared up front rather than the input being reordered --
250 /// which would not work for mutual recursion anyway.
251 forwards: Vec<String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago252 /// Element type a `vec![..]` should build, from the binding's annotation.
253 vec_expect: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago254 /// While lowering a formatting impl: the `Formatter` parameter's name.
255 /// Writes through it produce the proc's string result.
256 fmt_param: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago257 /// `type X<T> = ...`, expanded before any type is mapped.
258 aliases: HashMap<String, (Vec<String>, syn::Type)>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago259 /// Module names supplied as separate input files. A `mod x;` naming one
260 /// of these is satisfied by that file having been passed in.
261 pub modules: Vec<String>,
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 17h ago262 /// How many items were actually translated. If this is zero the input
263 /// produced nothing but the prelude, and reporting success for that is
264 /// the precise failure this project exists to avoid -- see `findings/`.
265 emitted: usize,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago266 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
267 /// evaluated against these exactly as rustc would, so an item that is
268 /// dropped here is genuinely not part of the program being compiled.
269 pub features: Vec<String>,
270 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago271 /// Return type of the proc being lowered, so `return e` and a trailing
272 /// expression can type their literals the way Rust's inference would.
273 ret: Option<Nim>,
274 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
275 /// statement must assign their value to.
276 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago277 /// Set while lowering a `while` condition, which Nim re-evaluates each
278 /// iteration and so cannot have statements hoisted out of it.
279 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago280 tmp: usize,
281}
282
283impl Lowerer {
284 pub fn new() -> Self {
285 Lowerer {
286 out: String::new(),
287 indent: 0,
288 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago289 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago290 fns: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago291 cur_mod: String::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago292 self_ty: None,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago293 impl_generics: Vec::new(),
294 fn_generics: Vec::new(),
295 type_generics: HashMap::new(),
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago296 assoc: HashMap::new(),
297 assoc_consts: HashMap::new(),
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago298 bitflags: std::collections::HashSet::new(),
299 flag_consts: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago300 use_map: HashMap::new(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago301 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago302 enums: HashMap::new(),
303 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago304 methods: HashMap::new(),
305 fmt_impls: HashMap::new(),
306 from_impls: HashMap::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago307 op_impls: HashMap::new(),
308 statics: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago309 fmt_param: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago310 vec_expect: None,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago311 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago312 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago313 modules: Vec::new(),
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 17h ago314 emitted: 0,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago315 features: Vec::new(),
316 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago317 ret: None,
318 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago319 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago320 tmp: 0,
321 }
322 }
323
324 // ------------------------------------------------------------ emission
325
326 fn line(&mut self, s: &str) {
327 for _ in 0..self.indent {
328 self.out.push_str(" ");
329 }
330 self.out.push_str(s);
331 self.out.push('\n');
332 }
333
334 fn blank(&mut self) {
335 self.out.push('\n');
336 }
337
338 fn fresh(&mut self, hint: &str) -> String {
339 self.tmp += 1;
340 format!("rsTmp{}{}", hint, self.tmp)
341 }
342
343 // --------------------------------------------------------------- scope
344
345 fn push_scope(&mut self) {
346 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago347 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago348 }
349 fn pop_scope(&mut self) {
350 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago351 self.alias_scopes.pop();
352 }
353 fn bind_alias(&mut self, name: &str, a: Alias) {
354 self.alias_scopes
355 .last_mut()
356 .unwrap()
357 .insert(name.to_string(), a);
358 }
359 fn lookup_alias(&self, name: &str) -> Option<Alias> {
360 self.alias_scopes
361 .iter()
362 .rev()
363 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago364 }
365 fn bind(&mut self, name: &str, t: Nim) {
366 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
367 }
368 fn lookup(&self, name: &str) -> Option<Nim> {
369 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
370 }
371
372 // ---------------------------------------------------------------- file
373
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago374 pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago375 self.out.push_str(include_str!("prelude.nim"));
376 self.blank();
377
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago378 // Pass 0: type aliases. A signature in one file may use an alias
379 // declared in another, and inputs are given in whatever order suits
380 // the caller, so aliases are registered before anything is mapped.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago381 for (m, f) in files {
382 self.cur_mod = m.clone();
383 for item in &f.items {
384 self.collect_aliases(item)?;
385 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago386 }
387
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago388 // Pass 1: signatures and struct shapes, so that a call can be typed
389 // regardless of declaration order (Rust has no forward declarations).
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago390 for (m, f) in files {
391 self.cur_mod = m.clone();
392 for item in &f.items {
393 self.collect(item)?;
394 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago395 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago396 // Pass 2: type definitions, which every signature may mention.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago397 for (m, f) in files {
398 self.cur_mod = m.clone();
399 for item in &f.items {
400 self.item_types(item)?;
401 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago402 }
403
404 // Pass 3: forward declarations. Rust imposes no declaration order and
405 // Nim does, so everything is declared before any body is emitted;
406 // reordering the input would not handle mutual recursion anyway.
407 if !self.forwards.is_empty() {
408 for f in self.forwards.clone() {
409 self.line(&f);
410 }
411 self.blank();
412 }
413
414 // Pass 4: bodies.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago415 for (m, f) in files {
416 self.cur_mod = m.clone();
417 for item in &f.items {
418 self.item(item)?;
419 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago420 }
421
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 17h ago422 // An input that translates to nothing is a failure, however plausible
423 // the output file looks. The prelude alone is not a translation.
424 if self.emitted == 0 {
425 return Err(format!(
426 "nothing was translated: the input has no items this lowering \
427 emits{}. Writing a file containing only the prelude would \
428 report success for work that was not done",
429 if self.dropped_by_cfg > 0 {
430 format!(
431 " ({} item(s) were dropped by `#[cfg]`; enable them with \
432 `--cfg feature=<name>`)",
433 self.dropped_by_cfg
434 )
435 } else {
436 String::new()
437 }
438 ));
439 }
440
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago441 if self.fns.contains_key(&(String::new(), "main".to_string())) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago442 self.blank();
443 self.line("when isMainModule:");
444 self.indent += 1;
445 self.line("try:");
446 self.line(" main()");
447 // Rust's panic exits 101 with a message on stderr. Nim's Defects
448 // exit 1. Mapping them here is what keeps the differential runner's
449 // exit-status comparison meaningful for panicking programs.
450 self.line("except RustPanic as e:");
451 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
452 self.line(" quit(101)");
453 self.line("except Defect as e:");
454 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
455 self.line(" quit(101)");
456 self.indent -= 1;
457 }
458 Ok(std::mem::take(&mut self.out))
459 }
460
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago461 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
462 if !self.cfg_keeps(item_attrs(item))? {
463 return Ok(());
464 }
465 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago466 Item::Use(u) => self.collect_use(&u.tree, &[]),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago467 Item::Type(t) => {
468 let params: Vec<String> = t
469 .generics
470 .params
471 .iter()
472 .filter_map(|g| match g {
473 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
474 _ => None,
475 })
476 .collect();
477 self.aliases
478 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
479 }
480 Item::Mod(m) if m.content.is_some() => {
481 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
482 for i in &items {
483 self.collect_aliases(i)?;
484 }
485 }
486 _ => {}
487 }
488 Ok(())
489 }
490
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago491 /// Record what a `use` brings into scope, as `name -> module`.
492 fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
493 use syn::UseTree;
494 match t {
495 UseTree::Path(p) => {
496 let mut pre = prefix.to_vec();
497 pre.push(p.ident.to_string());
498 self.collect_use(&p.tree, &pre);
499 }
500 UseTree::Group(g) => {
501 for t in &g.items {
502 self.collect_use(t, prefix);
503 }
504 }
505 UseTree::Name(n) => {
506 let m = module_of(prefix);
507 self.use_map.insert(n.ident.to_string(), m);
508 }
509 UseTree::Rename(r) => {
510 let m = module_of(prefix);
511 self.use_map.insert(r.rename.to_string(), m);
512 }
513 // A glob brings in an unknown set of names; resolution falls back
514 // to the current module and the root, as it would without it.
515 UseTree::Glob(_) => {}
516 }
517 }
518
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago519 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago520 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
521 // silently would change what the program does; picking a feature set
522 // on the user's behalf would be a guess. So it is reported, except on
523 // items that carry no runtime meaning here anyway.
524 if !self.cfg_keeps(item_attrs(item))? {
525 self.dropped_by_cfg += 1;
526 return Ok(());
527 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago528 match item {
529 Item::Fn(f) => {
530 let (params, ret) = self.signature(&f.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago531 let gen_names = Self::generics_of(&f.sig.generics);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago532 let name = f.sig.ident.to_string();
533 let nim = self.fn_name(&self.cur_mod, &name);
534 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
535 self.fns
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago536 .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 19h ago537 }
538 Item::Struct(s) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago539 if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
540 return Err(format!(
541 "`struct {}` has a const generic parameter, which Nim has \
542 no equivalent for",
543 s.ident
544 ));
545 }
546 let g = Self::generics_of(&s.generics);
547 // The parameters must be in scope while the field types are
548 // mapped, so that `T` resolves to itself rather than to an
549 // unknown named type.
550 self.type_generics.insert(s.ident.to_string(), g);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago551 let mut fields = Vec::new();
552 for (i, f) in s.fields.iter().enumerate() {
553 let name = match &f.ident {
554 Some(id) => id.to_string(),
555 None => format!("f{i}"), // tuple struct
556 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago557 // A field of `&[T]` / `&str` type is a borrow, and Nim's
558 // view types allow it as an object field, so it stays a
559 // view rather than being copied into a `seq`.
560 let t = self.map_ty(&f.ty)?;
561 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
562 fields.push((name, t));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago563 }
564 self.structs.insert(s.ident.to_string(), fields);
565 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago566 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => {
567 self.collect_bitflags(&m.mac)?;
568 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago569 Item::Mod(m) if m.content.is_some() => {
570 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
571 for i in &items {
572 self.collect(i)?;
573 }
574 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago575 Item::Type(t) => {
576 let params: Vec<String> = t
577 .generics
578 .params
579 .iter()
580 .filter_map(|g| match g {
581 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
582 _ => None,
583 })
584 .collect();
585 self.aliases
586 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
587 }
588 Item::Enum(e) => {
589 let name = e.ident.to_string();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago590 if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
591 return Err(format!(
592 "`enum {name}` has a const generic parameter, which Nim \
593 has no equivalent for"
594 ));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago595 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago596 self.type_generics
597 .insert(name.clone(), Self::generics_of(&e.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago598 let mut variants = Vec::new();
599 for v in &e.variants {
600 let vname = v.ident.to_string();
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 15h ago601 let discriminant = match &v.discriminant {
602 Some((_, e)) => Some(self.expr(e)?.code),
603 None => None,
604 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago605 let mut fields = Vec::new();
606 for (i, f) in v.fields.iter().enumerate() {
607 // Nim requires the branches of a variant object to have
608 // distinct field names, so each is prefixed.
609 let fname = match &f.ident {
610 Some(id) => format!("{vname}_{id}"),
611 None => format!("{vname}_f{i}"),
612 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago613 let t = self.map_ty(&f.ty)?;
614 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
615 fields.push((fname, t));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago616 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 15h ago617 variants.push(Variant { name: vname, discriminant, fields });
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago618 }
619 let simple = variants.iter().all(|v| v.fields.is_empty());
620 for v in &variants {
621 self.variant_owner
622 .entry(v.name.clone())
623 .or_default()
624 .push(name.clone());
625 }
626 self.enums.insert(
627 name.clone(),
628 EnumDef { name, simple, variants },
629 );
630 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago631 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago632 let outer_g =
633 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 19h ago634 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago635 let outer_self = self.self_ty.replace(self_ty.clone());
636 let r = self.collect_impl(im, &self_ty);
637 self.self_ty = outer_self;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago638 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago639 return r;
640 }
641 _ => {}
642 }
643 Ok(())
644 }
645
646 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
647 {
648 let self_ty = self_ty.clone();
649 let tyname = type_name(&self_ty);
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago650 // Associated types first: a signature in the same block may name
651 // one, and it has to resolve by the time that signature is mapped.
652 for it in &im.items {
653 if let syn::ImplItem::Type(t) = it {
654 let v = self.map_ty(&t.ty)?;
655 self.assoc.insert((tyname.clone(), t.ident.to_string()), v);
656 }
657 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago658 if let Some((path, _)) = &im.trait_ {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago659 let tr = path_name(path);
660 if im.items.is_empty() {
661 // A marker trait with no items. We do not model trait
662 // resolution at all, so it generates nothing; any use
663 // that actually needed the trait (a `dyn`, a bound) is
664 // rejected where it appears.
665 return Ok(());
666 }
667 if is_fmt_trait(&tr) {
668 self.forwards.push(format!(
669 "proc {}*(self: {}): string",
670 fmt_proc(&tr),
671 self_ty.render()
672 ));
673 self.fmt_impls.insert((tyname, tr), ());
674 return Ok(());
675 }
676 if tr == "From" {
677 let syn::ImplItem::Fn(m) = &im.items[0] else {
678 return Err("`impl From` must contain `fn from`".into());
679 };
680 let (params, _) = self.signature(&m.sig)?;
681 let src = params
682 .first()
683 .ok_or("`fn from` takes one argument")?
684 .clone();
685 let name = format!("rsFrom{}{}", tyname, type_name(&src));
686 self.forwards.push(self.head_of(&name, &m.sig, None)?);
687 self.from_impls
688 .insert((type_name(&src), tyname), name);
689 return Ok(());
690 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago691 // Any other trait: its methods are emitted as procs on
692 // the type, named after the trait so two traits declaring
693 // the same method name do not collide. The *trait* is not
694 // modelled -- no dynamic dispatch, no bounds -- and a use
695 // that needs it is rejected where it appears.
696 if let Some(op) = operator_trait(&tr) {
697 self.op_impls.insert((tyname.clone(), op.to_string()), ());
698 }
699 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago700 // Already recorded above; a const is emitted with the
701 // bodies.
702 if matches!(it, syn::ImplItem::Type(_) | syn::ImplItem::Const(_)) {
703 continue;
704 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago705 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago706 return Err(format!(
707 "unsupported item in `impl {tr}`: only `fn`, \
708 `type` and `const` are implemented"
709 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago710 };
711 let mname = m.sig.ident.to_string();
712 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago713 let mut gen_names = self.impl_generics.clone();
714 gen_names.extend(Self::generics_of(&m.sig.generics));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago715 let recv = if takes_self(&m.sig) {
716 params.insert(0, self_ty.clone());
717 Some(self_ty.clone())
718 } else {
719 None
720 };
721 let nim = trait_method_name(&tyname, &tr, &mname);
722 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
723 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago724 .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 17h ago725 self.statics.insert((tyname.clone(), mname), nim);
726 }
727 return Ok(());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago728 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago729 for it in &im.items {
730 if let syn::ImplItem::Fn(m) = it {
731 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago732 let mut gen_names = self.impl_generics.clone();
733 gen_names.extend(Self::generics_of(&m.sig.generics));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago734 if takes_self(&m.sig) {
735 params.insert(0, self_ty.clone());
736 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago737 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 17h ago738 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
739 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 18h ago740 self.forwards.push(head);
741 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago742 .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 17h ago743 self.statics
744 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago745 }
746 }
747 }
748 Ok(())
749 }
750
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago751 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
752 ///
753 /// This is evaluation, not approximation: rustc does the same thing, and
754 /// an item whose predicate is false is not part of the compiled program.
755 /// A predicate that cannot be evaluated is reported rather than assumed.
756 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
757 for a in attrs {
758 if a.path().is_ident("cfg") {
759 let pred: syn::Meta = a
760 .parse_args()
761 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
762 if !self.cfg_eval(&pred)? {
763 return Ok(false);
764 }
765 }
766 }
767 Ok(true)
768 }
769
770 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
771 match m {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago772 // Bare flags whose value is determined by the profile this project
773 // models: a normal (non-`--test`) debug build, not a docs build.
774 // Anything platform-specific stays rejected, since we would be
775 // picking a target on the user's behalf.
776 syn::Meta::Path(p) if p.is_ident("test") => Ok(false),
777 syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true),
778 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 17h ago779 syn::Meta::Path(p) if p.is_ident("doctest") || p.is_ident("miri") => Ok(false),
780 // Host facts. The generated Nim is compiled for this machine, so
781 // these are known rather than chosen. See DESIGN.md item 10: it
782 // does make the output host-shaped.
783 syn::Meta::Path(p) if p.is_ident("unix") => Ok(cfg!(unix)),
784 syn::Meta::Path(p) if p.is_ident("windows") => Ok(cfg!(windows)),
785 syn::Meta::NameValue(nv)
786 if nv.path.is_ident("target_os")
787 || nv.path.is_ident("target_arch")
788 || nv.path.is_ident("target_family")
789 || nv.path.is_ident("target_vendor") =>
790 {
791 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
792 return Err("this `cfg` key expects a string".into());
793 };
794 let key = nv.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
795 Ok(s.value()
796 == match key.as_str() {
797 "target_os" => std::env::consts::OS,
798 "target_arch" => std::env::consts::ARCH,
799 "target_family" => std::env::consts::FAMILY,
800 _ => "unknown",
801 })
802 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago803 // The generated Nim is compiled for the same machine, so the
804 // target's word size and endianness are known rather than
805 // guessed. This does mean the output is host-shaped: a crate that
806 // branches on pointer width has had that branch decided here.
807 syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => {
808 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
809 return Err("`target_pointer_width = ..` expects a string".into());
810 };
811 Ok(s.value() == (usize::BITS).to_string())
812 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 15h ago813 // Every integer width and pointer-sized atomic exists on the
814 // targets Nim builds for here; like the other host facts this is
815 // read off the machine rather than chosen.
816 syn::Meta::NameValue(nv) if nv.path.is_ident("target_has_atomic") => {
817 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
818 return Err("`target_has_atomic = ..` expects a string".into());
819 };
820 Ok(matches!(
821 s.value().as_str(),
822 "8" | "16" | "32" | "64" | "ptr"
823 ))
824 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago825 syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => {
826 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
827 return Err("`target_endian = ..` expects a string".into());
828 };
829 Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" })
830 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago831 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
832 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
833 return Err("`feature = ..` expects a string".into());
834 };
835 Ok(self.features.iter().any(|f| *f == s.value()))
836 }
837 syn::Meta::List(l) if l.path.is_ident("not") => {
838 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
839 Ok(!self.cfg_eval(&inner)?)
840 }
841 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
842 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
843 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
844 .map_err(|e| e.to_string())?;
845 let all = l.path.is_ident("all");
846 let mut acc = all;
847 for i in &items {
848 let v = self.cfg_eval(i)?;
849 acc = if all { acc && v } else { acc || v };
850 }
851 Ok(acc)
852 }
853 other => Err(format!(
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 17h ago854 "`#[cfg({})]` is not a predicate rustnim can evaluate. \
855 Features (`--cfg feature=..`), host facts (`unix`, `windows`, \
856 `target_os`, `target_arch`, `target_family`, \
857 `target_pointer_width`, `target_endian`), `doc`/`doctest`/\
858 `miri`, and `not`/`all`/`any` over those are. A custom or \
859 build-script `cfg` has no value we could know",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago860 quote_meta(other)
861 )),
862 }
863 }
864
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago865 /// 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 19h ago866 /// lowering goes through here rather than calling `ty::map` directly, so
867 /// an alias cannot be missed in one position and honoured in another.
868 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 16h ago869 // `Self::Item` names an associated type of the enclosing `impl`.
870 if let syn::Type::Path(p) = t {
871 let segs: Vec<String> =
872 p.path.segments.iter().map(|s| s.ident.to_string()).collect();
873 if segs.len() == 2 {
874 let owner = if segs[0] == "Self" {
875 self.self_ty.as_ref().map(type_name)
876 } else {
877 Some(segs[0].clone())
878 };
879 if let Some(o) = owner {
880 if let Some(a) = self.assoc.get(&(o, segs[1].clone())) {
881 return Ok(a.clone());
882 }
883 }
884 }
885 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago886 let n = ty::map(&self.expand(t, 0)?)?;
887 Ok(self.subst_self(n))
888 }
889
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago890 /// Substitute a generic type's parameters with the arguments the use site
891 /// supplies: a field of `Holder<T>` read through a `Holder<i32>` is `i32`.
892 fn subst_type_args(&self, name: &str, used_as: &Nim, field: Nim) -> Nim {
893 let Some(params) = self.type_generics.get(name) else { return field };
894 if params.is_empty() {
895 return field;
896 }
897 let Nim::Named(n, args) = used_as else { return field };
898 if n != name || args.len() != params.len() {
899 return field;
900 }
901 let map: HashMap<String, Nim> =
902 params.iter().cloned().zip(args.iter().cloned()).collect();
903 Self::subst(&field, &map)
904 }
905
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago906 /// `Self` inside an `impl` block names the type being implemented.
907 fn subst_self(&self, t: Nim) -> Nim {
908 let Some(me) = &self.self_ty else { return t };
909 match t {
910 Nim::Named(n, _) if n == "Self" => me.clone(),
911 Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))),
912 Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))),
913 Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))),
914 Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))),
915 Nim::Named(n, a) => {
916 Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect())
917 }
918 other => other,
919 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago920 }
921
922 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
923 if depth > 16 {
924 return Err("type alias expansion did not terminate; is it cyclic?".into());
925 }
926 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
927 // Only an unqualified name can be one of this file's aliases.
928 // `fmt::Result` and `core::result::Result` are different types that
929 // merely end in the same segment.
930 if p.path.segments.len() != 1 {
931 return Ok(t.clone());
932 }
933 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
934 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
935 return Ok(t.clone());
936 };
937 let args: Vec<syn::Type> = match &seg.arguments {
938 syn::PathArguments::AngleBracketed(a) => a
939 .args
940 .iter()
941 .filter_map(|g| match g {
942 GenericArgument::Type(t) => Some(t.clone()),
943 _ => None,
944 })
945 .collect(),
946 _ => vec![],
947 };
948 if args.len() != params.len() {
949 // Flattening several files into one module can bring a crate's own
950 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
951 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
952 // module; here they are told apart by arity, and a use that fits
953 // neither is left for `ty::map` to report.
954 return Ok(t.clone());
955 }
956 self.expand(&substitute(target, params, &args), depth + 1)
957 }
958
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago959 /// The type parameters a generic item declares.
960 ///
961 /// Trait bounds and `where` clauses are dropped. Nim instantiates a
962 /// generic structurally: an operation the bound would have permitted
963 /// either exists for the instantiated type or is a compile error at the
964 /// instantiation site. So dropping a bound cannot make an accepted
965 /// program mean something different — it only makes rustnim accept some
966 /// programs rustc would have rejected, which does not matter when the
967 /// input is known-good Rust.
968 fn generics_of(g: &syn::Generics) -> Vec<String> {
969 g.params
970 .iter()
971 .filter_map(|p| match p {
972 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
973 _ => None,
974 })
975 .collect()
976 }
977
978 /// Bind a signature's type parameters by matching its declared parameter
979 /// types against the actual argument types, then substitute into `ret`.
980 ///
981 /// This is the small amount of inference a call site needs: Nim will
982 /// resolve the instantiation itself, but the *binding* still has to be
983 /// annotated with a concrete type, and `T` is not one.
984 fn instantiate(sig: &Sig, args: &[Option<Nim>]) -> Nim {
985 if sig.generics.is_empty() {
986 return sig.ret.clone();
987 }
988 let mut bound: HashMap<String, Nim> = HashMap::new();
989 for (decl, actual) in sig.params.iter().zip(args) {
990 if let Some(a) = actual {
991 Self::unify(decl, a, &sig.generics, &mut bound);
992 }
993 }
994 Self::subst(&sig.ret, &bound)
995 }
996
997 fn unify(decl: &Nim, actual: &Nim, params: &[String], out: &mut HashMap<String, Nim>) {
998 match (decl, actual) {
999 (Nim::Named(n, da), _) if params.iter().any(|p| p == n) && da.is_empty() => {
1000 out.entry(n.clone()).or_insert_with(|| actual.clone());
1001 }
1002 (Nim::Named(_, da), Nim::Named(_, aa)) if da.len() == aa.len() => {
1003 for (d, a) in da.iter().zip(aa) {
1004 Self::unify(d, a, params, out);
1005 }
1006 }
1007 (Nim::Seq(d), Nim::Seq(a))
1008 | (Nim::OpenArray(d), Nim::OpenArray(a))
1009 | (Nim::Seq(d), Nim::OpenArray(a))
1010 | (Nim::OpenArray(d), Nim::Seq(a))
1011 | (Nim::Var(d), Nim::Var(a))
1012 | (Nim::Array(_, d), Nim::Array(_, a)) => Self::unify(d, a, params, out),
1013 (Nim::Var(d), a) => Self::unify(d, a, params, out),
1014 (d, Nim::Var(a)) => Self::unify(d, a, params, out),
1015 (Nim::Tuple(d), Nim::Tuple(a)) if d.len() == a.len() => {
1016 for (d, a) in d.iter().zip(a) {
1017 Self::unify(d, a, params, out);
1018 }
1019 }
1020 _ => {}
1021 }
1022 }
1023
1024 fn subst(t: &Nim, m: &HashMap<String, Nim>) -> Nim {
1025 match t {
1026 Nim::Named(n, a) if a.is_empty() => m.get(n).cloned().unwrap_or_else(|| t.clone()),
1027 Nim::Named(n, a) => {
1028 Nim::Named(n.clone(), a.iter().map(|x| Self::subst(x, m)).collect())
1029 }
1030 Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))),
1031 Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))),
1032 Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))),
1033 Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))),
1034 Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()),
1035 other => other.clone(),
1036 }
1037 }
1038
1039 /// Whether a type mentions a type parameter that is in scope here. Such a
1040 /// type cannot be used as a Nim annotation at an instantiation site: Nim
1041 /// infers it, and writing `T` would name something that is not bound.
1042 fn mentions_type_param(&self, t: &Nim) -> bool {
1043 match t {
1044 Nim::Named(n, a) => {
1045 self.fn_generics.iter().any(|g| g == n)
1046 || a.iter().any(|x| self.mentions_type_param(x))
1047 }
1048 Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) => {
1049 self.mentions_type_param(e)
1050 }
1051 Nim::Tuple(ts) => ts.iter().any(|x| self.mentions_type_param(x)),
1052 Nim::Proc(a, r) => {
1053 a.iter().any(|x| self.mentions_type_param(x)) || self.mentions_type_param(r)
1054 }
1055 _ => false,
1056 }
1057 }
1058
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 15h ago1059 /// The Nim name of a `log::Level` written as a path.
1060 fn log_level_of(&mut self, e: &Expr) -> Result<String, String> {
1061 let Expr::Path(p) = e else {
1062 return Err("a log level must be written as `Level::Info`".into());
1063 };
1064 let last = path_name(&p.path);
1065 Ok(match last.as_str() {
1066 "Error" => "rsLvlError",
1067 "Warn" => "rsLvlWarn",
1068 "Info" => "rsLvlInfo",
1069 "Debug" => "rsLvlDebug",
1070 "Trace" => "rsLvlTrace",
1071 other => return Err(format!("`Level::{other}` is not a log level")),
1072 }
1073 .to_string())
1074 }
1075
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1076 /// `[T, U]`, or empty.
1077 fn gen_list(params: &[String]) -> String {
1078 if params.is_empty() {
1079 String::new()
1080 } else {
1081 format!("[{}]", params.join(", "))
1082 }
1083 }
1084
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1085 /// The Nim name for a function, qualified by its module.
1086 fn fn_name(&self, module: &str, name: &str) -> String {
1087 if module.is_empty() {
1088 ident(name)
1089 } else {
1090 format!("{}_{}", module, ident(name))
1091 }
1092 }
1093
1094 /// Resolve a call path to the module and name it refers to: an explicit
1095 /// `mixed::decode`, then the current module, then the crate root.
1096 fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
1097 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1098 let last = segs.last()?.clone();
1099 if segs.len() >= 2 {
1100 let q = &segs[segs.len() - 2];
1101 if self.fns.contains_key(&(q.clone(), last.clone())) {
1102 return Some((q.clone(), last));
1103 }
1104 }
1105 let imported = self.use_map.get(&last).cloned();
1106 for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
1107 .into_iter()
1108 .flatten()
1109 {
1110 if self.fns.contains_key(&(m.clone(), last.clone())) {
1111 return Some((m, last));
1112 }
1113 }
1114 None
1115 }
1116
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1117 /// The Nim `proc` head for a Rust signature, used both for the forward
1118 /// declaration and for the definition, so the two cannot drift apart.
1119 fn head_of(
1120 &self,
1121 name: &str,
1122 sig: &syn::Signature,
1123 recv: Option<&Nim>,
1124 ) -> Result<String, String> {
1125 let (ptys, ret) = self.signature(sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1126 // A method inside `impl<T> Foo<T>` is generic in the impl's
1127 // parameters as well as its own.
1128 let mut params = self.impl_generics.clone();
1129 for g in Self::generics_of(&sig.generics) {
1130 if !params.contains(&g) {
1131 params.push(g);
1132 }
1133 }
1134 let gens = Self::gen_list(&params);
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1135 let mut parts = Vec::new();
1136 if let Some(self_ty) = recv {
1137 let mutable = matches!(
1138 sig.inputs.first(),
1139 Some(FnArg::Receiver(r))
1140 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1141 );
1142 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1143 parts.push(format!("self: {}", t.render()));
1144 }
1145 let typed: Vec<&syn::PatType> = sig
1146 .inputs
1147 .iter()
1148 .filter_map(|a| match a {
1149 FnArg::Typed(t) => Some(t),
1150 _ => None,
1151 })
1152 .collect();
1153 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
1154 let pname = match &*p.pat {
1155 Pat::Ident(id) => id.ident.to_string(),
1156 Pat::Wild(_) => format!("unused{}", parts.len()),
1157 _ => return Err("only plain identifier parameters are supported".into()),
1158 };
1159 let _ = i;
1160 parts.push(format!("{}: {}", ident(&pname), t.render()));
1161 }
1162 Ok(if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1163 format!("proc {}*{}({})", ident(name), gens, parts.join(", "))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1164 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1165 format!(
1166 "proc {}*{}({}): {}",
1167 ident(name),
1168 gens,
1169 parts.join(", "),
1170 ret.render()
1171 )
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1172 })
1173 }
1174
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1175 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1176 // `unsafe fn` marks a contract for callers; it does not change what
1177 // the body means, so it lowers like any other proc.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1178 if sig.asyncness.is_some() {
1179 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
1180 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1181 // 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 16h ago1182 // they disappear. Type parameters become Nim generic parameters.
1183 // Const parameters have no Nim equivalent and are still rejected.
1184 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 19h ago1185 return Err(format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1186 "`fn {}` has a const generic parameter, which Nim has no \
1187 equivalent for",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1188 sig.ident
1189 ));
1190 }
1191 let mut params = Vec::new();
1192 for a in &sig.inputs {
1193 if let FnArg::Typed(t) = a {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1194 params.push(self.map_ty(&t.ty)?);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1195 }
1196 }
1197 let ret = match &sig.output {
1198 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1199 // A returned `&[T]` is a borrow of the caller's buffer, so it
1200 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
1201 // a `seq`, which `owned()` would do to both.
1202 ReturnType::Type(_, t) => {
1203 let n = self.map_ty(t)?;
1204 if returns_borrow(t) { n } else { n.owned() }
1205 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1206 };
1207 Ok((params, ret))
1208 }
1209
1210 // --------------------------------------------------------------- items
1211
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1212 /// Emit the type definitions only: they must precede every signature.
1213 fn item_types(&mut self, item: &Item) -> Result<(), String> {
1214 if !self.cfg_keeps(item_attrs(item))? {
1215 return Ok(());
1216 }
1217 match item {
1218 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago1219 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1220 Item::Mod(m) if m.content.is_some() => {
1221 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1222 for i in &items {
1223 self.item_types(i)?;
1224 }
1225 Ok(())
1226 }
1227 _ => Ok(()),
1228 }
1229 }
1230
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1231 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1232 if !self.cfg_keeps(item_attrs(item))? {
1233 return Ok(());
1234 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1235 // Types were emitted in their own pass.
1236 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
1237 return Ok(());
1238 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago1239 if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") {
1240 return Ok(()); // emitted with the types
1241 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1242 self.item_inner(item)
1243 }
1244
1245 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 17h ago1246 if !matches!(item, Item::Use(_) | Item::ExternCrate(_) | Item::Mod(_) | Item::Type(_)) {
1247 self.emitted += 1;
1248 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1249 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago1250 Item::Fn(f) => {
1251 let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
1252 self.func_named(&nim, &f.sig, &f.block, None)
1253 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1254 Item::Struct(s) => {
1255 let name = s.ident.to_string();
1256 let fields = self.structs[&name].clone();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1257 let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1258 self.line(&format!("type {}*{} = object", ident(&name), g));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1259 self.indent += 1;
1260 if fields.is_empty() {
1261 self.line("discard");
1262 }
1263 for (fname, fty) in &fields {
1264 self.line(&format!("{}*: {}", ident(fname), fty.render()));
1265 }
1266 self.indent -= 1;
1267 self.blank();
1268 Ok(())
1269 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago1270 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.emit_bitflags(&m.mac),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1271 Item::Type(_) => Ok(()), // expanded at every use site
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago1272 Item::Trait(t) => {
1273 // We do not model trait resolution, so a declaration generates
1274 // nothing and a use that needed it is rejected where it
1275 // appears. A *default body*, though, is code: dropping it
1276 // would silently remove a method the impls inherit.
1277 for it in &t.items {
1278 if let syn::TraitItem::Fn(f) = it {
1279 if f.default.is_some() {
1280 return Err(format!(
1281 "`trait {}` gives `{}` a default body; trait \
1282 resolution is not modelled, so that body has no \
1283 impl to be emitted into and dropping it would \
1284 remove code",
1285 t.ident, f.sig.ident
1286 ));
1287 }
1288 }
1289 }
1290 Ok(())
1291 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1292 Item::Enum(e) => {
1293 let def = self.enums[&e.ident.to_string()].clone();
1294 self.emit_enum(&def);
1295 Ok(())
1296 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1297 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1298 let t = self.map_ty(&c.ty)?.owned();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1299 // The annotation types the initialiser, exactly as it does for
1300 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
1301 let v = self.expr_at(&c.expr, Some(&t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1302 self.bind(&c.ident.to_string(), t.clone());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1303 // Only a top-level const is exported; `*` on a local is not
1304 // Nim syntax.
1305 let star = if self.indent == 0 { "*" } else { "" };
1306 let line = format!(
1307 "const {}{}: {} = {}",
1308 ident(&c.ident.to_string()),
1309 star,
1310 t.render(),
1311 v.code
1312 );
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1313 self.line(&line);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1314 if self.indent == 0 {
1315 self.blank();
1316 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1317 Ok(())
1318 }
1319 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1320 let outer_g =
1321 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 19h ago1322 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1323 let outer = self.self_ty.replace(self_ty.clone());
1324 let r = self.impl_body(im, &self_ty);
1325 self.self_ty = outer;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1326 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1327 r
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1328 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1329 // `use` and `extern crate` are resolution directives with no Nim
1330 // analogue once everything is one module.
1331 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
1332 Item::Mod(m) if m.content.is_some() => {
1333 // An inline `mod` is flattened; Nim has no nested modules in a
1334 // single file.
1335 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1336 for i in &items {
1337 self.item(i)?;
1338 }
1339 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1340 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1341 Item::Mod(m) => {
1342 // Satisfied if that file was passed in too; everything is one
1343 // Nim module, so the declaration itself emits nothing.
1344 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
1345 return Ok(());
1346 }
1347 Err(format!(
1348 "`mod {};` refers to another file that was not passed to \
1349 rustnim; add it to the input list",
1350 m.ident
1351 ))
1352 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1353 other => Err(format!("unsupported item: {}", item_kind(other))),
1354 }
1355 }
1356
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1357 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
1358 fn none_of(&self, expect: Option<&Nim>) -> String {
1359 match expect {
1360 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
1361 format!("rsNone[{}]()", a[0].render())
1362 }
1363 _ => "rsNone()".to_string(),
1364 }
1365 }
1366
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1367 fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
1368 if let Some((path, _)) = &im.trait_ {
1369 let tr = path_name(path);
1370 if im.items.is_empty() {
1371 return Ok(());
1372 }
1373 if is_fmt_trait(&tr) {
1374 let syn::ImplItem::Fn(m) = &im.items[0] else {
1375 return Err(format!("unsupported item in `impl {tr}`"));
1376 };
1377 return self.fmt_impl(&tr, self_ty, &m.sig, &m.block);
1378 }
1379 if tr == "From" {
1380 let syn::ImplItem::Fn(m) = &im.items[0] else {
1381 return Err("`impl From` must contain `fn from`".into());
1382 };
1383 let name = {
1384 let (params, _) = self.signature(&m.sig)?;
1385 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
1386 self.from_impls[&(type_name(&src), type_name(self_ty))].clone()
1387 };
1388 return self.func_named(&name, &m.sig, &m.block, None);
1389 }
1390 let tyname = type_name(self_ty);
1391 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago1392 if let syn::ImplItem::Const(c) = it {
1393 self.assoc_const(&tyname, c)?;
1394 continue;
1395 }
1396 if matches!(it, syn::ImplItem::Type(_)) {
1397 continue; // a type binding emits nothing
1398 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1399 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago1400 return Err(format!(
1401 "unsupported item in `impl {tr}`: only `fn`, `type` and \
1402 `const` are implemented"
1403 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1404 };
1405 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1406 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
1407 self.func_named(&nim, &m.sig, &m.block, recv)?;
1408 }
1409 return Ok(());
1410 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago1411 let tyname = type_name(self_ty);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1412 for it in &im.items {
1413 match it {
1414 syn::ImplItem::Fn(m) => {
1415 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1416 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
1417 self.func_named(&nim, &m.sig, &m.block, recv)?;
1418 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago1419 syn::ImplItem::Type(_) => {}
1420 syn::ImplItem::Const(c) => self.assoc_const(&tyname, c)?,
1421 _ => {
1422 return Err("only `fn`, `type` and `const` items are supported \
1423 inside `impl`"
1424 .into())
1425 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1426 }
1427 }
1428 Ok(())
1429 }
1430
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago1431 /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant
1432 /// namespace, so it becomes a module-level const named for both.
1433 fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {
1434 let t = self.map_ty(&c.ty)?.owned();
1435 let v = self.expr_at(&c.expr, Some(&t))?;
1436 let name = format!("{}_{}", tyname, c.ident);
1437 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
1438 self.blank();
1439 self.assoc_consts
1440 .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));
1441 Ok(())
1442 }
1443
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago1444 /// The type an operator impl declares for its right-hand operand.
1445 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
1446 let n = type_name(t.as_ref()?);
1447 let sig = self.methods.get(&(n, op_method(op).to_string()))?;
1448 sig.params.get(1).cloned().map(|t| t.unvar())
1449 }
1450
1451 /// The proc implementing `op` for a user type, if there is one.
1452 fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> {
1453 let n = type_name(t.as_ref()?);
1454 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
1455 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
1456 Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1))
1457 } else {
1458 None
1459 }
1460 }
1461
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago1462 /// Register a `bitflags!` type's operations so call sites resolve.
1463 fn collect_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> {
1464 let input: crate::macros::BitflagsInput = mac
1465 .parse_body()
1466 .map_err(|e| format!("`bitflags!`: {e}"))?;
1467 for def in &input.0 {
1468 let name = def.name.to_string();
1469 let repr = self.map_ty(&def.repr)?;
1470 if !repr.is_integer() {
1471 return Err(format!("`bitflags! {name}` needs an integer representation"));
1472 }
1473 let me = Nim::Named(name.clone(), vec![]);
1474 let b = Nim::Prim("bool".into());
1475 self.structs
1476 .insert(name.clone(), vec![("bitsField".into(), repr.clone())]);
1477 self.type_generics.insert(name.clone(), Vec::new());
1478
1479 let mut m = |n: &str, params: Vec<Nim>, ret: Nim, nim: String| {
1480 self.methods.insert(
1481 (name.clone(), n.to_string()),
1482 Sig { params, ret, generics: Vec::new() },
1483 );
1484 self.statics.insert((name.clone(), n.to_string()), nim);
1485 };
1486 let s1 = vec![me.clone()];
1487 let s2 = vec![me.clone(), me.clone()];
1488 let vs2 = vec![Nim::Var(Box::new(me.clone())), me.clone()];
1489 m("bits", s1.clone(), repr.clone(), format!("{name}_bits"));
1490 m("is_empty", s1.clone(), b.clone(), format!("{name}_is_empty"));
1491 m("is_all", s1.clone(), b.clone(), format!("{name}_is_all"));
1492 m("contains", s2.clone(), b.clone(), format!("{name}_contains"));
1493 m("intersects", s2.clone(), b.clone(), format!("{name}_intersects"));
1494 for (rust, nim) in [
1495 ("union", "union"),
1496 ("intersection", "intersection"),
1497 ("difference", "difference"),
1498 ("symmetric_difference", "symmetric_difference"),
1499 ] {
1500 m(rust, s2.clone(), me.clone(), format!("{name}_{nim}"));
1501 }
1502 for n in ["insert", "remove", "toggle"] {
1503 m(n, vs2.clone(), Nim::Unit, format!("{name}_{n}"));
1504 }
1505 m(
1506 "set",
1507 vec![Nim::Var(Box::new(me.clone())), me.clone(), b.clone()],
1508 Nim::Unit,
1509 format!("{name}_set"),
1510 );
1511 m("empty", vec![], me.clone(), format!("{name}_empty"));
1512 m("all", vec![], me.clone(), format!("{name}_all"));
1513 m(
1514 "from_bits",
1515 vec![repr.clone()],
1516 Nim::Named("Option".into(), vec![me.clone()]),
1517 format!("{name}_from_bits"),
1518 );
1519 m(
1520 "from_bits_truncate",
1521 vec![repr.clone()],
1522 me.clone(),
1523 format!("{name}_from_bits_truncate"),
1524 );
1525 m("complement", s1.clone(), me.clone(), format!("{name}_complement"));
1526
1527 // The operator forms, routed through the same dispatch that a
1528 // hand-written `impl BitOr` would use.
1529 for (op, trait_name, method) in [
1530 ("|", "BitOr", "bitor"),
1531 ("&", "BitAnd", "bitand"),
1532 ("^", "BitXor", "bitxor"),
1533 ("-", "Sub", "sub"),
1534 ("not", "Not", "not"),
1535 ] {
1536 self.op_impls.insert((name.clone(), op.to_string()), ());
1537 let params = if op == "not" { s1.clone() } else { s2.clone() };
1538 self.methods.insert(
1539 (name.clone(), method.to_string()),
1540 Sig { params, ret: me.clone(), generics: Vec::new() },
1541 );
1542 let _ = trait_name;
1543 }
1544 self.bitflags.insert(name);
1545 }
1546 Ok(())
1547 }
1548
1549 /// Emit the Nim for a `bitflags!` type. See `src/macros.rs` for why this
1550 /// is lowered directly rather than by expanding the macro.
1551 fn emit_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> {
1552 let input: crate::macros::BitflagsInput = mac
1553 .parse_body()
1554 .map_err(|e| format!("`bitflags!`: {e}"))?;
1555 for def in &input.0 {
1556 let name = def.name.to_string();
1557 let repr = self.map_ty(&def.repr)?;
1558 let r = repr.render();
1559
1560 self.line(&format!("type {name}* = object"));
1561 self.line(&format!(" bitsField*: {r}"));
1562 self.blank();
1563 self.line(&format!("proc {name}_bits*(x: {name}): {r} = x.bitsField"));
1564
1565 // The constants. A flag's value may name earlier flags, as
1566 // `const ALL = Self::READ.bits() | ..` does, so they are emitted
1567 // in order and each is in scope for the next.
1568 self.push_scope();
1569 self.bind_static_type(&name, &repr);
1570 for (fname, value) in &def.flags {
1571 let v = self.expr_at(value, Some(&repr))?;
1572 self.line(&format!(
1573 "const {}{}* = {}(bitsField: {})",
1574 name, fname, name, v.code
1575 ));
1576 self.flag_consts
1577 .insert((name.clone(), fname.to_string()), format!("{name}{fname}"));
1578 }
1579 self.pop_scope();
1580
1581 let all: Vec<String> = def
1582 .flags
1583 .iter()
1584 .map(|(f, _)| format!("{name}{f}.bitsField"))
1585 .collect();
1586 let all_bits = if all.is_empty() {
1587 format!("{}(0)", r)
1588 } else {
1589 all.join(" or ")
1590 };
1591 self.blank();
1592 self.line(&format!("const {name}AllBits: {r} = {all_bits}"));
1593 self.blank();
1594
1595 for l in [
1596 format!("proc {name}_empty*(): {name} = {name}(bitsField: {r}(0))"),
1597 format!("proc {name}_all*(): {name} = {name}(bitsField: {name}AllBits)"),
1598 format!("proc {name}_is_empty*(x: {name}): bool = x.bitsField == {r}(0)"),
1599 format!("proc {name}_is_all*(x: {name}): bool = (x.bitsField and {name}AllBits) == {name}AllBits"),
1600 format!("proc {name}_contains*(a, b: {name}): bool = (a.bitsField and b.bitsField) == b.bitsField"),
1601 format!("proc {name}_intersects*(a, b: {name}): bool = (a.bitsField and b.bitsField) != {r}(0)"),
1602 format!("proc {name}_union*(a, b: {name}): {name} = {name}(bitsField: a.bitsField or b.bitsField)"),
1603 format!("proc {name}_intersection*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and b.bitsField)"),
1604 format!("proc {name}_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and (not b.bitsField))"),
1605 format!("proc {name}_symmetric_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField xor b.bitsField)"),
1606 // `!x` complements and then masks to `all()`, which is what
1607 // bitflags does and not what a plain `not` would give.
1608 format!("proc {name}_complement*(x: {name}): {name} = {name}(bitsField: (not x.bitsField) and {name}AllBits)"),
1609 format!("proc {name}_from_bits_truncate*(b: {r}): {name} = {name}(bitsField: b and {name}AllBits)"),
1610 format!("proc {name}_from_bits*(b: {r}): Option[{name}] ="),
1611 format!(" if (b and (not {name}AllBits)) != {r}(0): rsNone[{name}]() else: rsSome({name}(bitsField: b))"),
1612 format!("proc {name}_insert*(x: var {name}, o: {name}) = x.bitsField = x.bitsField or o.bitsField"),
1613 format!("proc {name}_remove*(x: var {name}, o: {name}) = x.bitsField = x.bitsField and (not o.bitsField)"),
1614 format!("proc {name}_toggle*(x: var {name}, o: {name}) = x.bitsField = x.bitsField xor o.bitsField"),
1615 format!("proc {name}_set*(x: var {name}, o: {name}, on: bool) ="),
1616 format!(" if on: {name}_insert(x, o) else: {name}_remove(x, o)"),
1617 format!("proc rsBitOr_{name}_bitor*(a, b: {name}): {name} = {name}_union(a, b)"),
1618 format!("proc rsBitAnd_{name}_bitand*(a, b: {name}): {name} = {name}_intersection(a, b)"),
1619 format!("proc rsBitXor_{name}_bitxor*(a, b: {name}): {name} = {name}_symmetric_difference(a, b)"),
1620 format!("proc rsSub_{name}_sub*(a, b: {name}): {name} = {name}_difference(a, b)"),
1621 format!("proc rsNot_{name}_not*(a: {name}): {name} = {name}_complement(a)"),
1622 ] {
1623 self.line(&l);
1624 }
1625
1626 // Debug prints the set flag names, or `0x0` when empty -- again
1627 // matching the crate rather than a guess.
1628 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1629 self.line(&format!(" result = \"{name}(\""));
1630 self.line(" var first = true");
1631 for (fname, _) in &def.flags {
1632 self.line(&format!(
1633 " if (x.bitsField and {name}{f}.bitsField) == {name}{f}.bitsField and {name}{f}.bitsField != {r}(0):",
1634 f = fname
1635 ));
1636 self.line(" if not first: result.add(\" | \")");
1637 self.line(&format!(" result.add(\"{fname}\")"));
1638 self.line(" first = false");
1639 }
1640 self.line(" if first: result.add(\"0x0\")");
1641 self.line(" result.add(\")\")");
1642 self.blank();
1643 }
1644 Ok(())
1645 }
1646
1647 fn bind_static_type(&mut self, _name: &str, _repr: &Nim) {}
1648
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1649 fn emit_enum(&mut self, def: &EnumDef) {
1650 let name = ident(&def.name);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1651 let g = Self::gen_list(
1652 self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
1653 );
1654 if def.simple && !g.is_empty() {
1655 // A Nim `enum` cannot take parameters; an all-unit generic enum
1656 // has no payload to be generic in anyway, so this would be a
1657 // parameter that never appears.
1658 // Fall through to the object-variant form instead.
1659 }
1660 if def.simple && g.is_empty() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1661 // Every variant is a unit variant, so a plain Nim enum is an exact
1662 // fit: it compares, orders and `case`-checks like Rust's.
1663 self.line(&format!("type {name}* = enum"));
1664 self.indent += 1;
1665 for v in &def.variants {
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 15h ago1666 match &v.discriminant {
1667 Some(d) => self.line(&format!("{} = {}", ident(&v.name), d)),
1668 None => self.line(&ident(&v.name)),
1669 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1670 }
1671 self.indent -= 1;
1672 self.blank();
1673 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1674 self.indent += 1;
1675 self.line("case x");
1676 for v in &def.variants {
1677 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
1678 }
1679 self.indent -= 1;
1680 self.blank();
1681 return;
1682 }
1683
1684 // A data-carrying enum is a Nim object variant: one discriminant enum
1685 // plus a branch per variant. This is the same shape the prelude uses
1686 // for `Option` and `Result`.
1687 self.line("type");
1688 self.indent += 1;
1689 self.line(&format!("{}Kind* = enum", name));
1690 self.indent += 1;
1691 for v in &def.variants {
1692 self.line(&def.kind_ident(&v.name));
1693 }
1694 self.indent -= 1;
1695 self.blank();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1696 self.line(&format!("{}*{} = object", name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1697 self.indent += 1;
1698 self.line(&format!("case kind*: {}Kind", name));
1699 for v in &def.variants {
1700 if v.fields.is_empty() {
1701 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
1702 } else {
1703 self.line(&format!("of {}:", def.kind_ident(&v.name)));
1704 self.indent += 1;
1705 for (f, t) in &v.fields {
1706 self.line(&format!("{}*: {}", ident(f), t.render()));
1707 }
1708 self.indent -= 1;
1709 }
1710 }
1711 self.indent -= 2;
1712 self.blank();
1713
1714 for v in &def.variants {
1715 let args: Vec<String> = v
1716 .fields
1717 .iter()
1718 .enumerate()
1719 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
1720 .collect();
1721 let inits: Vec<String> = v
1722 .fields
1723 .iter()
1724 .enumerate()
1725 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
1726 .collect();
1727 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1728 all.extend(inits);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1729 let ret = format!("{}{}", name, g);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1730 self.line(&format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1731 "proc {}*{}({}): {} = {}({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1732 def.ctor_ident(&v.name),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1733 g,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1734 args.join(", "),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1735 ret,
1736 ret,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1737 all.join(", ")
1738 ));
1739 }
1740 self.blank();
1741
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1742 self.line(&format!("proc rsDebug*{}(x: {}{}): string =", g, name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1743 self.indent += 1;
1744 self.line("case x.kind");
1745 for v in &def.variants {
1746 if v.fields.is_empty() {
1747 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
1748 } else {
1749 let parts: Vec<String> = v
1750 .fields
1751 .iter()
1752 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
1753 .collect();
1754 self.line(&format!(
1755 "of {}: \"{}(\" & {} & \")\"",
1756 def.kind_ident(&v.name),
1757 v.name,
1758 parts.join(" & \", \" & ")
1759 ));
1760 }
1761 }
1762 self.indent -= 1;
1763 self.blank();
1764 }
1765
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1766 /// The concrete type an enum variant constructs, and the `[T]` list to
1767 /// spell at the constructor when the enum is generic.
1768 fn variant_type(
1769 &self,
1770 def: &EnumDef,
1771 expect: Option<&Nim>,
1772 ) -> Result<(Nim, String), String> {
1773 let params = self.type_generics.get(&def.name).cloned().unwrap_or_default();
1774 if params.is_empty() {
1775 return Ok((Nim::Named(def.name.clone(), vec![]), String::new()));
1776 }
1777 match expect {
1778 Some(Nim::Named(n, a)) if *n == def.name && a.len() == params.len() => Ok((
1779 Nim::Named(def.name.clone(), a.clone()),
1780 format!("[{}]", a.iter().map(|t| t.render()).collect::<Vec<_>>().join(", ")),
1781 )),
1782 _ => Err(format!(
1783 "`{}` is a variant of a generic enum, and its type parameters \
1784 cannot be inferred here; annotate the binding or the return type",
1785 def.name
1786 )),
1787 }
1788 }
1789
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago1790 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1791 /// to the enum that declares it.
1792 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
1793 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1794 let last = segs.last()?.clone();
1795 if segs.len() >= 2 {
1796 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
1797 if def.get(&last).is_some() {
1798 return Some((def.clone(), last));
1799 }
1800 }
1801 }
1802 // Unqualified: only unambiguous if exactly one enum declares it.
1803 match self.variant_owner.get(&last) {
1804 Some(owners) if owners.len() == 1 => {
1805 let def = self.enums.get(&owners[0])?;
1806 Some((def.clone(), last))
1807 }
1808 _ => None,
1809 }
1810 }
1811
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1812 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
1813 ///
1814 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
1815 /// observable result of `{}` is exactly the bytes written. So the method
1816 /// becomes `proc rsDisplay(self: T): string` and every write through the
1817 /// formatter produces that string. A `fmt` body that does anything else
1818 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
1819 /// because those affect the output and this model does not carry them.
1820 /// The window an expression names, if it names one.
1821 fn window_of(&self, e: &Expr) -> Option<Alias> {
1822 match e {
1823 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
1824 Some(a @ Alias::Window { .. }) => Some(a),
1825 _ => None,
1826 },
1827 Expr::Reference(r) => self.window_of(&r.expr),
1828 Expr::Paren(p) => self.window_of(&p.expr),
1829 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
1830 _ => None,
1831 }
1832 }
1833
1834 /// Whether an expression is the `Formatter` parameter of the formatting
1835 /// impl currently being lowered.
1836 fn is_fmt_param(&self, e: &Expr) -> bool {
1837 let Some(f) = &self.fmt_param else { return false };
1838 match e {
1839 Expr::Path(p) => path_name(&p.path) == *f,
1840 Expr::Reference(r) => self.is_fmt_param(&r.expr),
1841 Expr::Paren(p) => self.is_fmt_param(&p.expr),
1842 _ => false,
1843 }
1844 }
1845
1846 fn fmt_impl(
1847 &mut self,
1848 tr: &str,
1849 self_ty: &Nim,
1850 sig: &syn::Signature,
1851 body: &syn::Block,
1852 ) -> Result<(), String> {
1853 let proc_name = fmt_proc(tr);
1854 // The formatter is the parameter after `self`.
1855 let f = sig
1856 .inputs
1857 .iter()
1858 .filter_map(|a| match a {
1859 FnArg::Typed(t) => match &*t.pat {
1860 Pat::Ident(i) => Some(i.ident.to_string()),
1861 _ => None,
1862 },
1863 _ => None,
1864 })
1865 .next()
1866 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1867
1868 self.push_scope();
1869 self.bind("self", self_ty.clone());
1870 let saved = self.fmt_param.replace(f);
1871 let outer_ret = self.ret.replace(Nim::Prim("string".into()));
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago1872 // No assignment target: a formatter write *appends*, because a `fmt`
1873 // body may write repeatedly -- `UpperHex` writes once per byte in a
1874 // loop -- and assigning would keep only the last one.
1875 let outer_target = self.target.take();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1876
1877 self.line(&format!(
1878 "proc {}*(self: {}): string =",
1879 proc_name,
1880 self_ty.render()
1881 ));
1882 self.indent += 1;
1883 let before = self.out.len();
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago1884 let tail = self.block_body(body)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1885 self.emit_tail(tail);
1886 if self.out.len() == before {
1887 self.line("discard");
1888 }
1889 self.indent -= 1;
1890
1891 self.target = outer_target;
1892 self.ret = outer_ret;
1893 self.fmt_param = saved;
1894 self.pop_scope();
1895 self.blank();
1896 Ok(())
1897 }
1898
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1899 fn func(
1900 &mut self,
1901 sig: &syn::Signature,
1902 body: &syn::Block,
1903 recv: Option<Nim>,
1904 ) -> Result<(), String> {
1905 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1906 self.func_named(&name.clone(), sig, body, recv)
1907 }
1908
1909 fn func_named(
1910 &mut self,
1911 name: &str,
1912 sig: &syn::Signature,
1913 body: &syn::Block,
1914 recv: Option<Nim>,
1915 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1916 let (ptys, ret) = self.signature(sig)?;
1917
1918 self.push_scope();
1919 let mut rendered: Vec<String> = Vec::new();
1920
1921 if let Some(self_ty) = recv {
1922 // `&mut self` and `mut self` both mean the body may mutate the
1923 // receiver; only the former is observable by the caller, and a Nim
1924 // `var` parameter is the faithful spelling of that.
1925 let mutable = matches!(
1926 sig.inputs.first(),
1927 Some(FnArg::Receiver(r))
1928 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1929 );
1930 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1931 rendered.push(format!("self: {}", t.render()));
1932 self.bind("self", self_ty);
1933 }
1934
1935 let typed: Vec<&syn::PatType> = sig
1936 .inputs
1937 .iter()
1938 .filter_map(|a| match a {
1939 FnArg::Typed(t) => Some(t),
1940 _ => None,
1941 })
1942 .collect();
1943 for (p, t) in typed.iter().zip(ptys.iter()) {
1944 let pname = match &*p.pat {
1945 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago1946 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
1947 // still needs a name for it.
1948 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1949 _ => return Err("only plain identifier parameters are supported".into()),
1950 };
1951 rendered.push(format!("{}: {}", ident(&pname), t.render()));
1952 // Inside the body a `var T` parameter is used exactly like a `T`.
1953 self.bind(&pname, t.clone().owned());
1954 }
1955
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1956 let mut gparams = self.impl_generics.clone();
1957 for g in Self::generics_of(&sig.generics) {
1958 if !gparams.contains(&g) {
1959 gparams.push(g);
1960 }
1961 }
1962 let gens = Self::gen_list(&gparams);
1963 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 19h ago1964 let head = if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1965 format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1966 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago1967 format!(
1968 "proc {}*{}({}): {} =",
1969 ident(name),
1970 gens,
1971 rendered.join(", "),
1972 ret.render()
1973 )
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago1974 };
1975 self.line(&head);
1976 self.indent += 1;
1977 let outer_ret = self.ret.replace(ret.clone());
1978
1979 // A Rust fn's trailing expression is its return value. Naming Nim's
1980 // implicit `result` as the target makes that true whether the tail is
1981 // a plain expression or an `if`/`match` with statement arms.
1982 let outer_target = if ret == Nim::Unit {
1983 self.target.take()
1984 } else {
1985 self.target.replace(("result".to_string(), Some(ret.clone())))
1986 };
1987 let before = self.out.len();
1988 let tail = self.block_body_at(body, Some(&ret))?;
1989 self.target = outer_target;
1990 match tail {
1991 Some(v) if ret != Nim::Unit => {
1992 let code = v.code.clone();
1993 self.line(&format!("result = {code}"));
1994 }
1995 Some(v) => {
1996 // A trailing expression in a `()`-returning fn is evaluated for
1997 // its effect; Nim requires an explicit discard.
1998 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1999 if needs_discard && !v.code.is_empty() {
2000 let code = v.code.clone();
2001 self.line(&format!("discard {code}"));
2002 }
2003 }
2004 None => {}
2005 }
2006 if self.out.len() == before {
2007 self.line("discard");
2008 }
2009
2010 self.indent -= 1;
2011 self.ret = outer_ret;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago2012 self.fn_generics = outer_fg;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2013 self.pop_scope();
2014 self.blank();
2015 Ok(())
2016 }
2017
2018 // ---------------------------------------------------------- statements
2019
2020 /// Lower a block's statements. Returns the block's trailing expression,
2021 /// if it has one, *without* emitting it — the caller decides whether that
2022 /// value is a return value, a binding, or discarded.
2023 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
2024 self.block_body_at(b, None)
2025 }
2026
2027 fn block_body_at(
2028 &mut self,
2029 b: &syn::Block,
2030 expect: Option<&Nim>,
2031 ) -> Result<Option<Val>, String> {
2032 // An assignment target belongs to *this* block's trailing expression
2033 // only. A non-final `if` is a statement and must not assign anything.
2034 let target = self.target.take();
2035 let n = b.stmts.len();
2036 let mut tail = None;
2037 for (i, st) in b.stmts.iter().enumerate() {
2038 let last = i + 1 == n;
2039 match st {
2040 Stmt::Expr(e, None) if last && expressible(e) => {
2041 tail = Some(self.expr_at(e, expect)?)
2042 }
2043 Stmt::Expr(e, None) if last => {
2044 // A trailing `if`/`match` with statement arms, or a loop.
2045 // Lower it as statements; if this block's value is wanted,
2046 // each arm assigns it.
2047 match &target {
2048 Some((t, ty)) => {
2049 let (t, ty) = (t.clone(), ty.clone());
2050 self.assign_from(e, &t, ty.as_ref())?;
2051 }
2052 None => self.stmt(st)?,
2053 }
2054 }
2055 _ => self.stmt(st)?,
2056 }
2057 }
2058 self.target = target;
2059 Ok(tail)
2060 }
2061
2062 /// Lower a block in statement position (loop bodies, `if` arms).
2063 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
2064 self.push_scope();
2065 self.indent += 1;
2066 let before = self.out.len();
2067 let want = self.target.clone().and_then(|(_, t)| t);
2068 let tail = self.block_body_at(b, want.as_ref())?;
2069 self.emit_tail(tail);
2070 if self.out.len() == before {
2071 self.line("discard");
2072 }
2073 self.indent -= 1;
2074 self.pop_scope();
2075 Ok(())
2076 }
2077
2078 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
2079 match s {
2080 Stmt::Local(l) => self.local(l),
2081 Stmt::Expr(e, _) => {
2082 let v = self.expr_stmt(e)?;
2083 if let Some(v) = v {
2084 // A bare expression with a value must be discarded in Nim.
2085 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2086 let code = v.code.clone();
2087 if needs {
2088 self.line(&format!("discard {code}"));
2089 } else if !code.is_empty() {
2090 self.line(&code);
2091 }
2092 }
2093 Ok(())
2094 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago2095 // A `const` declared inside a function body is local to it, and
2096 // must be emitted here rather than skipped as an already-emitted
2097 // top-level type.
2098 Stmt::Item(i) => self.item_inner(i),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2099 Stmt::Macro(m) => {
2100 let line = self.macro_call(&m.mac)?;
2101 self.line(&line);
2102 Ok(())
2103 }
2104 }
2105 }
2106
2107 fn local(&mut self, l: &Local) -> Result<(), String> {
2108 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
2109 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
2110 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2111 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), Some(self.map_ty(&t.ty)?)),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2112 _ => return Err("only `let <ident>` bindings are supported".into()),
2113 },
2114 Pat::Wild(_) => ("_".into(), false, None),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago2115 Pat::Tuple(t) => return self.local_tuple(l, t),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2116 _ => return Err("destructuring `let` is not implemented yet".into()),
2117 };
2118
2119 let Some(init) = &l.init else {
2120 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
2121 // not. Rust's own rules make reading it before assignment illegal,
2122 // so the two agree on every program rustc accepts.
2123 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
2124 let t = t.owned();
2125 self.line(&format!("var {}: {}", ident(&name), t.render()));
2126 self.bind(&name, t);
2127 return Ok(());
2128 };
2129 if init.diverge.is_some() {
2130 return Err("`let ... else` is not implemented yet".into());
2131 }
2132
2133 if !expressible(&init.expr) && name != "_" {
2134 // The initialiser is an `if`/`match` whose arms are statements.
2135 // Declare first, then let each arm assign into the binding.
2136 let t = ann
2137 .clone()
2138 .ok_or_else(|| {
2139 format!(
2140 "`let {name} = match/if ...` needs a type annotation: \
2141 its arms are statements, so the binding must be \
2142 declared before they run"
2143 )
2144 })?
2145 .owned();
2146 self.line(&format!("var {}: {}", ident(&name), t.render()));
2147 self.bind(&name, t.clone());
2148 let target = ident(&name);
2149 return self.assign_from(&init.expr, &target, Some(&t));
2150 }
2151
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago2152 // `let it = xs.chunks_exact(k)` binds an iterator, not a value.
2153 if is_iterator_expr(&init.expr) {
2154 let it = self.resolve_iter(&init.expr)?;
2155 self.bind_alias(&name, Alias::Iterator(Box::new(it)));
2156 return Ok(());
2157 }
2158
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2159 let v = self.expr_at(&init.expr, ann.as_ref())?;
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 17h ago2160
2161 // `let s = &buf[..n]` binds a view of a place that is already in
2162 // scope. Nim's borrow checker will not let a `let` borrow out of a
2163 // local, and there is nothing to materialise anyway -- a view is a
2164 // reference. Binding it as an alias substitutes the same expression at
2165 // each use, which re-evaluates nothing because the initialiser is a
2166 // place expression with no side effects.
2167 if v.window.is_none()
2168 && matches!(v.ty, Some(Nim::OpenArray(_)))
2169 && is_pure_place(&init.expr)
2170 {
2171 let t = v.ty.clone().unwrap();
2172 let elem = match &t {
2173 Nim::OpenArray(e) => Some((**e).clone()),
2174 _ => None,
2175 };
2176 self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) });
2177 let _ = elem;
2178 return Ok(());
2179 }
2180
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2181 if let Some(w) = v.window.clone() {
2182 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
2183 // view into the caller's buffer. Copying it into a `seq` would
2184 // still print the right bytes but would stop writes reaching the
2185 // caller, so it is bound as an alias.
2186 if v.guard.is_some() && v.guard_err.is_some() {
2187 return Err(format!(
2188 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
2189 which Nim cannot represent; apply `?` or `unwrap()` to it \
2190 in the same expression"
2191 ));
2192 }
2193 self.bind_alias(&name, w);
2194 return Ok(());
2195 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago2196 // A `let` binding a borrow keeps the view: `let res = encode(..)?`
2197 // names the caller's buffer, and copying it into a `seq` would still
2198 // print the right bytes while silently breaking the aliasing.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2199 let t = match (ann, &v.ty) {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago2200 (Some(a), _) => a.unvar(),
2201 (None, Some(t)) => t.clone().unvar(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2202 (None, None) => {
2203 return Err(format!(
2204 "cannot infer the type of `let {name}`; annotate it — \
2205 guessing here would change integer width, and with it the \
2206 meaning of any arithmetic on `{name}`"
2207 ))
2208 }
2209 };
2210
2211 if name == "_" {
2212 let code = v.code.clone();
2213 self.line(&format!("discard {code}"));
2214 return Ok(());
2215 }
2216 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
2217 // 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 17h ago2218 //
2219 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
2220 // Rust may write through it, and Nim only accepts a `var` where a
2221 // `var` parameter is wanted, so the binding has to be one.
2222 let mutable = mutable || is_mut_borrow(&init.expr);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2223 let kw = if mutable { "var" } else { "let" };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago2224 // Inside a generic proc the binding's type may mention a parameter Nim
2225 // will infer; naming it in an annotation would not resolve.
2226 let line = if self.mentions_type_param(&t) {
2227 format!("{} {} = {}", kw, ident(&name), v.code)
2228 } else {
2229 format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)
2230 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2231 self.line(&line);
2232 self.bind(&name, t);
2233 Ok(())
2234 }
2235
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago2236 /// `let (a, b) = ..` — tuple destructuring.
2237 fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> {
2238 let Some(init) = &l.init else {
2239 return Err("a destructuring `let` needs an initialiser".into());
2240 };
2241 let names: Vec<(String, bool)> = t
2242 .elems
2243 .iter()
2244 .map(|p| match p {
2245 Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())),
2246 Pat::Wild(_) => Ok(("_".to_string(), false)),
2247 _ => Err("only plain identifiers are supported in a destructuring `let`"),
2248 })
2249 .collect::<Result<_, _>>()?;
2250
2251 // `split_at` hands back two *views* of the same slice. Nim has no
2252 // tuple of views, and there is nothing to materialise anyway, so each
2253 // name becomes a window into the original.
2254 if let Expr::MethodCall(m) = &*init.expr {
2255 let mname = m.method.to_string();
2256 if (mname == "split_at" || mname == "split_at_mut")
2257 && m.args.len() == 1
2258 && names.len() == 2
2259 {
2260 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
2261 let at = self.expr(&m.args[0])?;
2262 let cut = self.fresh("Cut");
2263 self.line(&format!("let {}: int = int({})", cut, at.code));
2264 self.bind_alias(
2265 &names[0].0,
2266 Alias::Window {
2267 code: code.clone(),
2268 off: base.clone(),
2269 len: cut.clone(),
2270 elem: elem.clone(),
2271 },
2272 );
2273 self.bind_alias(
2274 &names[1].0,
2275 Alias::Window {
2276 code,
2277 off: format!("({} + {})", base, cut),
2278 len: format!("({} - {})", len, cut),
2279 elem,
2280 },
2281 );
2282 return Ok(());
2283 }
2284 }
2285
2286 let v = self.expr(&init.expr)?;
2287 let tys = match &v.ty {
2288 Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(),
2289 _ => {
2290 return Err(format!(
2291 "cannot destructure this into {} bindings: its type is not a \
2292 tuple of that many elements",
2293 names.len()
2294 ))
2295 }
2296 };
2297 let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" };
2298 let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect();
2299 self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code));
2300 for ((n, _), t) in names.iter().zip(tys) {
2301 self.bind(n, t);
2302 }
2303 Ok(())
2304 }
2305
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2306 /// Expressions that are statements in Rust and statements in Nim too
2307 /// (control flow). Returns `None` when it emitted lines itself.
2308 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
2309 match e {
2310 Expr::If(_) => {
2311 self.if_stmt(e)?;
2312 Ok(None)
2313 }
2314 Expr::While(w) => {
2315 if w.label.is_some() {
2316 return Err("loop labels are not implemented yet".into());
2317 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2318 self.in_loop_cond = true;
2319 let c = self.expr(&w.cond);
2320 self.in_loop_cond = false;
2321 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2322 self.line(&format!("while {}:", c.code));
2323 let saved = self.target.take();
2324 self.nested_block(&w.body)?;
2325 self.target = saved;
2326 Ok(None)
2327 }
2328 Expr::Loop(l) => {
2329 if l.label.is_some() {
2330 return Err("loop labels are not implemented yet".into());
2331 }
2332 self.line("while true:");
2333 let saved = self.target.take();
2334 self.nested_block(&l.body)?;
2335 self.target = saved;
2336 Ok(None)
2337 }
2338 Expr::ForLoop(f) => {
2339 self.for_loop(f)?;
2340 Ok(None)
2341 }
2342 Expr::Block(b) => {
2343 if b.label.is_some() {
2344 return Err("block labels are not implemented yet".into());
2345 }
2346 self.line("block:");
2347 self.nested_block(&b.block)?;
2348 Ok(None)
2349 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2350 Expr::Unsafe(u) => {
2351 // Transparent in statement position too, for the same reason.
2352 self.nested_block_flat(&u.block)?;
2353 Ok(None)
2354 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2355 Expr::Match(_) => {
2356 self.match_stmt(e)?;
2357 Ok(None)
2358 }
2359 Expr::Return(r) => {
2360 match &r.expr {
2361 Some(e) => {
2362 let want = self.ret.clone();
2363 let v = self.expr_at(e, want.as_ref())?;
2364 self.line(&format!("return {}", v.code));
2365 }
2366 None => self.line("return"),
2367 }
2368 Ok(None)
2369 }
2370 Expr::Break(b) => {
2371 if b.expr.is_some() || b.label.is_some() {
2372 return Err("`break` with a value or a label is not implemented yet".into());
2373 }
2374 self.line("break");
2375 Ok(None)
2376 }
2377 Expr::Continue(c) => {
2378 if c.label.is_some() {
2379 return Err("labelled `continue` is not implemented yet".into());
2380 }
2381 self.line("continue");
2382 Ok(None)
2383 }
2384 Expr::Assign(a) => {
2385 let lhs = self.expr(&a.left)?;
2386 if !expressible(&a.right) {
2387 let target = lhs.code.clone();
2388 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
2389 }
2390 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
2391 self.line(&format!("{} = {}", lhs.code, rhs.code));
2392 Ok(None)
2393 }
2394 Expr::Binary(b) if is_compound(&b.op) => {
2395 let lhs = self.expr(&b.left)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago2396 // A compound assignment on a user type goes to that type's own
2397 // `impl OpAssign`, not to Nim's built-in operator.
2398 if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) {
2399 // The impl's own parameter type types the right operand,
2400 // so `b_vec *= 4` takes 4 at the width the impl declares.
2401 let want = self.op_param(&lhs.ty, compound_symbol(&b.op));
2402 let rhs = self.expr_at(&b.right, want.as_ref())?;
2403 self.line(&format!("{}({}, {})", f, lhs.code, rhs.code));
2404 return Ok(None);
2405 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2406 // `i += 1` must widen the literal to `i`'s type, not to the
2407 // i32 an unconstrained Rust literal would default to.
2408 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
2409 let op = self.bin_op(&b.op, &lhs, &rhs)?;
2410 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
2411 // both languages, so the expanded form is always correct.
2412 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
2413 Ok(None)
2414 }
2415 Expr::Macro(m) => {
2416 let line = self.macro_call(&m.mac)?;
2417 self.line(&line);
2418 Ok(None)
2419 }
2420 _ => Ok(Some(self.expr(e)?)),
2421 }
2422 }
2423
2424 /// Lower `e` in statement position, assigning each arm's value to
2425 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
2426 /// the trip when their arms are too big for a Nim `if`-expression.
2427 fn assign_from(
2428 &mut self,
2429 e: &Expr,
2430 target: &str,
2431 expect: Option<&Nim>,
2432 ) -> Result<(), String> {
2433 let saved = self.target.replace((target.to_string(), expect.cloned()));
2434 let r = match e {
2435 Expr::If(_) => self.if_stmt(e),
2436 Expr::Match(_) => self.match_stmt(e),
2437 other => {
2438 let v = self.expr_at(other, expect)?;
2439 self.line(&format!("{} = {}", target, v.code));
2440 Ok(())
2441 }
2442 };
2443 self.target = saved;
2444 r
2445 }
2446
2447 /// Emit a block's value into the active assignment target, if there is
2448 /// one, or discard it if there is not.
2449 fn emit_tail(&mut self, v: Option<Val>) {
2450 let Some(v) = v else { return };
2451 match self.target.clone() {
2452 Some((t, _)) => {
2453 let code = v.code.clone();
2454 self.line(&format!("{t} = {code}"));
2455 }
2456 None => {
2457 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2458 let code = v.code.clone();
2459 if needs {
2460 self.line(&format!("discard {code}"));
2461 } else if !code.is_empty() {
2462 self.line(&code);
2463 }
2464 }
2465 }
2466 }
2467
2468 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
2469 let Expr::If(i) = e else { unreachable!() };
2470 if let Expr::Let(_) = &*i.cond {
2471 return Err("`if let` is not implemented yet".into());
2472 }
2473 let c = self.expr(&i.cond)?;
2474 self.line(&format!("if {}:", c.code));
2475 self.nested_block(&i.then_branch)?;
2476 match &i.else_branch {
2477 None => {}
2478 Some((_, els)) => match &**els {
2479 Expr::If(_) => {
2480 // Nim needs `elif`; splice the nested `if` in as one.
2481 let mark = self.out.len();
2482 self.if_stmt(els)?;
2483 let tail = self.out.split_off(mark);
2484 let indent = " ".repeat(self.indent);
2485 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
2486 }
2487 Expr::Block(b) => {
2488 self.line("else:");
2489 self.nested_block(&b.block)?;
2490 }
2491 _ => return Err("unsupported `else` form".into()),
2492 },
2493 }
2494 Ok(())
2495 }
2496
2497 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
2498 if f.label.is_some() {
2499 return Err("loop labels are not implemented yet".into());
2500 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2501 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2502
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2503 // One index loop drives the whole chain. Rust's adaptors are lazy and
2504 // compose; resolving them to an index and binding each name to an
2505 // lvalue reproduces that without materialising anything.
2506 let i = self.fresh("Idx");
2507 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
2508 self.indent += 1;
2509 self.push_scope();
2510 let before = self.out.len();
2511
2512 self.bind_pattern(&f.pat, &it, &i)?;
2513
2514 let saved = self.target.take();
2515 if let Some(v) = self.block_body(&f.body)? {
2516 let code = v.code.clone();
2517 self.line(&format!("discard {code}"));
2518 }
2519 self.target = saved;
2520 if self.out.len() == before {
2521 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2522 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2523 self.pop_scope();
2524 self.indent -= 1;
2525 Ok(())
2526 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2527
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2528 /// Resolve a chain of iterator adaptors into a single `Iter`.
2529 ///
2530 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
2531 /// `filter`, `take_while` and friends are rejected rather than partially
2532 /// honoured: silently dropping an adaptor would change which elements the
2533 /// loop visits.
2534 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
2535 match e {
2536 Expr::Reference(r) => self.resolve_iter(&r.expr),
2537 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2538 Expr::Range(r) => {
2539 let lo = match &r.start {
2540 Some(e) => self.expr(e)?,
2541 None => return Err("a `for` over `..n` needs a start bound".into()),
2542 };
2543 let hi = match &r.end {
2544 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2545 None => {
2546 return Err("a `for` over an unbounded range would not terminate".into())
2547 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2548 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2549 let ty = lo.ty.clone().or(hi.ty.clone());
2550 Ok(Iter::Range {
2551 lo: lo.code,
2552 hi: hi.code,
2553 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
2554 ty,
2555 })
2556 }
2557 Expr::MethodCall(m) => {
2558 let name = m.method.to_string();
2559 match name.as_str() {
2560 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
2561 let mut it = self.resolve_iter(&m.receiver)?;
2562 if name == "iter_mut" {
2563 if let Iter::Elems { mutable, .. } = &mut it {
2564 *mutable = true;
2565 }
2566 }
2567 Ok(it)
2568 }
2569 "enumerate" if m.args.is_empty() => {
2570 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
2571 }
2572 "zip" if m.args.len() == 1 => {
2573 let a = self.resolve_iter(&m.receiver)?;
2574 let b = self.resolve_iter(&m.args[0])?;
2575 Ok(Iter::Zip(Box::new(a), Box::new(b)))
2576 }
2577 "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2578 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2579 let k = self.expr(&m.args[0])?;
2580 Ok(Iter::Chunks {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2581 code,
2582 base,
2583 len,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2584 k: k.code,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2585 elem,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2586 mutable: name.ends_with("_mut"),
2587 })
2588 }
2589 "windows" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2590 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2591 let k = self.expr(&m.args[0])?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2592 Ok(Iter::Windows { code, base, len, k: k.code, elem })
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2593 }
2594 other => Err(format!(
2595 "iterator adaptor `.{other}()` is not implemented; it has \
2596 no index-loop equivalent here, and dropping it would \
2597 change which elements the loop visits"
2598 )),
2599 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2600 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago2601 Expr::Path(p) => {
2602 let n = path_name(&p.path);
2603 if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) {
2604 return Ok((*it).clone());
2605 }
2606 if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) {
2607 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
2608 }
2609 let v = self.expr(e)?;
2610 Ok(Iter::Elems {
2611 len: format!("{}.len", v.code),
2612 elem: elem_of(&v.ty),
2613 code: v.code,
2614 off: "0".into(),
2615 mutable: false,
2616 })
2617 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2618 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2619 // A `for` binding that is itself a window iterates that window,
2620 // not the whole container it points into.
2621 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2622 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2623 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2624 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2625 Ok(Iter::Elems {
2626 len: format!("{}.len", v.code),
2627 elem: elem_of(&v.ty),
2628 code: v.code,
2629 off: "0".into(),
2630 mutable: false,
2631 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2632 }
2633 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2634 }
2635
2636 /// Bind a `for` pattern against a resolved iterator at index `i`.
2637 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
2638 match (p, it) {
2639 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
2640 self.bind_pattern(&t.elems[0], a, i)?;
2641 self.bind_pattern(&t.elems[1], b, i)
2642 }
2643 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
2644 if let Pat::Ident(id) = &t.elems[0] {
2645 let n = id.ident.to_string();
2646 // Rust's `enumerate` counts in `usize`.
2647 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
2648 self.bind(&n, Nim::Prim("uint".into()));
2649 }
2650 self.bind_pattern(&t.elems[1], inner, i)
2651 }
2652 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
2653 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
2654 ),
2655 (Pat::Wild(_), _) => Ok(()),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2656 // `for &byte in xs` — the `&` destructures the reference, which in
2657 // Nim is already the value.
2658 (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
2659 (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2660 (Pat::Ident(id), _) => {
2661 let name = id.ident.to_string();
2662 match it {
2663 Iter::Range { lo, ty, .. } => {
2664 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
2665 // The loop counts from zero; the range's own start is
2666 // added back so the binding has Rust's value and type.
2667 self.line(&format!(
2668 "let {}: {} = {}({}) + {}",
2669 ident(&name),
2670 t.render(),
2671 t.render(),
2672 i,
2673 lo
2674 ));
2675 self.bind(&name, t);
2676 Ok(())
2677 }
2678 Iter::Elems { code, off, elem, mutable, .. } => {
2679 let access = if off == "0" {
2680 format!("{}[{}]", code, i)
2681 } else {
2682 format!("{}[{} + {}]", code, off, i)
2683 };
2684 if *mutable {
2685 // An alias, not a copy: assigning through the
2686 // binding must reach the original element.
2687 self.bind_alias(
2688 &name,
2689 Alias::Value { code: access, ty: elem.clone() },
2690 );
2691 } else {
2692 let t = elem
2693 .clone()
2694 .ok_or("cannot infer the element type of this `for`")?;
2695 self.line(&format!(
2696 "let {}: {} = {}",
2697 ident(&name),
2698 t.render(),
2699 access
2700 ));
2701 self.bind(&name, t);
2702 }
2703 Ok(())
2704 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2705 Iter::Chunks { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2706 self.bind_alias(
2707 &name,
2708 Alias::Window {
2709 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2710 off: format!("({} + {} * int({}))", base, i, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2711 len: format!("int({})", k),
2712 elem: elem.clone(),
2713 },
2714 );
2715 Ok(())
2716 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2717 Iter::Windows { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2718 self.bind_alias(
2719 &name,
2720 Alias::Window {
2721 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago2722 off: format!("({} + {})", base, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago2723 len: format!("int({})", k),
2724 elem: elem.clone(),
2725 },
2726 );
2727 Ok(())
2728 }
2729 // Handled above: a zip or enumerate needs a tuple pattern,
2730 // and binding one name to the pair is not supported.
2731 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
2732 }
2733 }
2734 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2735 }
2736 }
2737
2738 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
2739 let Expr::Match(m) = e else { unreachable!() };
2740 let scrut = self.expr(&m.expr)?;
2741 let t = scrut
2742 .ty
2743 .clone()
2744 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2745 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2746 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2747
2748 // A `match` whose arms neither bind nor guard is a Nim `case`, which
2749 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
2750 // an if/elif chain, because Nim's `case` cannot destructure.
2751 let plain = m.arms.iter().all(|a| {
2752 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
2753 });
2754 if plain {
2755 self.match_case(m, &name, &t)
2756 } else {
2757 self.match_chain(m, &name, &t)
2758 }
2759 }
2760
2761 fn match_case(
2762 &mut self,
2763 m: &syn::ExprMatch,
2764 name: &str,
2765 t: &Nim,
2766 ) -> Result<(), String> {
2767 // A variant object is discriminated by its `kind` field.
2768 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
2769 self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2770
2771 let mut saw_wild = false;
2772 for arm in &m.arms {
2773 match &arm.pat {
2774 Pat::Wild(_) => {
2775 saw_wild = true;
2776 self.line("else:");
2777 }
2778 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2779 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2780 self.line(&format!("of {}:", labels.join(", ")));
2781 }
2782 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2783 self.arm_body(&arm.body)?;
2784 }
2785 if !saw_wild && !self.case_is_total(t, m) {
2786 // Rust checked exhaustiveness already, but Nim cannot always see
2787 // it -- an integer `case` needs every value covered -- so make the
2788 // unreachable arm explicit rather than leave a compile error.
2789 self.line("else:");
2790 self.line(" rsPanic(\"unreachable match arm\")");
2791 }
2792 Ok(())
2793 }
2794
2795 /// Whether a Nim `case` over this type is already total, in which case
2796 /// adding an `else` would be a compile error rather than a safety net.
2797 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
2798 let Nim::Named(n, _) = t else { return false };
2799 let Some(def) = self.enums.get(n) else { return false };
2800 def.variants.len() == m.arms.len()
2801 }
2802
2803 /// The if/elif form, for arms that bind or destructure.
2804 fn match_chain(
2805 &mut self,
2806 m: &syn::ExprMatch,
2807 name: &str,
2808 t: &Nim,
2809 ) -> Result<(), String> {
2810 let mut first = true;
2811 let mut closed = false;
2812 for arm in &m.arms {
2813 let (pat, guard) = match &arm.pat {
2814 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
2815 p => (p, None),
2816 };
2817 if guard.is_some() && binds(pat) {
2818 return Err("a `match` guard on a binding pattern is not \
2819 implemented yet"
2820 .into());
2821 }
2822 let test = self.pat_test(pat, name, t)?;
2823 let test = match (test, guard) {
2824 (Some(t), Some(g)) => {
2825 let g = self.expr(g)?;
2826 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2827 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2828 (None, Some(g)) => Some(self.expr(g)?.code),
2829 (t, None) => t,
2830 };
2831 match test {
2832 Some(test) => {
2833 self.line(&format!(
2834 "{} {}:",
2835 if first { "if" } else { "elif" },
2836 test
2837 ));
2838 first = false;
2839 }
2840 None => {
2841 // An irrefutable pattern: everything left falls here.
2842 if first {
2843 self.line("block:");
2844 } else {
2845 self.line("else:");
2846 }
2847 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2848 }
2849 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2850 self.indent += 1;
2851 self.push_scope();
2852 let before = self.out.len();
2853 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2854 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2855 self.arm_body_at(&arm.body, before)?;
2856 self.pop_scope();
2857 if closed {
2858 break;
2859 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2860 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2861 if !closed {
2862 // Rust proved this unreachable; Nim cannot see that, and leaving
2863 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago2864 self.line("else:");
2865 self.line(" rsPanic(\"unreachable match arm\")");
2866 }
2867 Ok(())
2868 }
2869
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago2870 /// The condition that selects this arm, or `None` if it always matches.
2871 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
2872 Ok(match p {
2873 Pat::Wild(_) => None,
2874 Pat::Ident(i) if i.subpat.is_none() => None,
2875 Pat::Or(o) => {
2876 let mut parts = Vec::new();
2877 for c in &o.cases {
2878 match self.pat_test(c, name, t)? {
2879 Some(x) => parts.push(x),
2880 None => return Ok(None),
2881 }
2882 }
2883 Some(format!("({})", parts.join(" or ")))
2884 }
2885 Pat::Lit(_) | Pat::Range(_) => {
2886 let labels = self.pat_labels(p, Some(t))?;
2887 Some(match p {
2888 Pat::Range(_) => format!("({} in {})", name, labels[0]),
2889 _ => format!("({} == {})", name, labels[0]),
2890 })
2891 }
2892 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
2893 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
2894 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
2895 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
2896 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
2897 _ => return Err("unsupported `match` pattern".into()),
2898 })
2899 }
2900
2901 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
2902 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
2903 let last = path_name(path);
2904 match last.as_str() {
2905 "Ok" => return Ok(format!("{name}.ok")),
2906 "Err" => return Ok(format!("(not {name}.ok)")),
2907 "Some" => return Ok(format!("{name}.has")),
2908 "None" => return Ok(format!("(not {name}.has)")),
2909 _ => {}
2910 }
2911 let Some((def, v)) = self.resolve_variant(path) else {
2912 return Err(format!(
2913 "`{last}` in a pattern is not a known enum variant; if it names \
2914 an enum declared in another module, that is not implemented yet"
2915 ));
2916 };
2917 if let Nim::Named(n, _) = t {
2918 if *n != def.name {
2919 return Err(format!(
2920 "pattern `{}::{}` does not match the scrutinee type `{}`",
2921 def.name, v, n
2922 ));
2923 }
2924 }
2925 Ok(if def.simple {
2926 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
2927 } else {
2928 format!("({}.kind == {})", name, def.kind_ident(&v))
2929 })
2930 }
2931
2932 /// Emit the `let`s that a pattern's bindings introduce.
2933 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
2934 match p {
2935 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
2936 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
2937 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
2938 Pat::Ident(i) if i.subpat.is_none() => {
2939 let b = i.ident.to_string();
2940 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
2941 self.bind(&b, t.clone());
2942 Ok(())
2943 }
2944 Pat::TupleStruct(ts) => {
2945 let fields = self.variant_fields(&ts.path, t)?;
2946 for (i, sub) in ts.elems.iter().enumerate() {
2947 let Some((fname, fty)) = fields.get(i) else {
2948 return Err(format!(
2949 "pattern binds {} field(s) but the variant has {}",
2950 ts.elems.len(),
2951 fields.len()
2952 ));
2953 };
2954 let access = format!("{}.{}", name, ident(fname));
2955 self.pat_bind(sub, &access, fty)?;
2956 }
2957 Ok(())
2958 }
2959 Pat::Struct(st) => {
2960 let fields = self.variant_fields(&st.path, t)?;
2961 for f in &st.fields {
2962 let syn::Member::Named(m) = &f.member else {
2963 return Err("unsupported struct pattern field".into());
2964 };
2965 let m = m.to_string();
2966 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
2967 return Err(format!("unknown field `{m}` in pattern"));
2968 };
2969 let access = format!("{}.{}", name, ident(fname));
2970 self.pat_bind(&f.pat, &access, fty)?;
2971 }
2972 Ok(())
2973 }
2974 _ => Err("unsupported `match` pattern".into()),
2975 }
2976 }
2977
2978 /// The payload fields a variant pattern destructures.
2979 fn variant_fields(
2980 &self,
2981 path: &syn::Path,
2982 t: &Nim,
2983 ) -> Result<Vec<(String, Nim)>, String> {
2984 let last = path_name(path);
2985 // `Ok`/`Err`/`Some` read the prelude's own field names.
2986 if let Nim::Named(n, a) = t {
2987 match (n.as_str(), last.as_str()) {
2988 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
2989 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
2990 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
2991 _ => {}
2992 }
2993 }
2994 let Some((def, v)) = self.resolve_variant(path) else {
2995 return Err(format!("`{last}` is not a known enum variant"));
2996 };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago2997 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2998 // The variant's payload is declared in the enum's own parameters; the
2999 // scrutinee says what they are here.
3000 Ok(fields
3001 .into_iter()
3002 .map(|(n, ft)| (n, self.subst_type_args(&def.name, t, ft)))
3003 .collect())
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3004 }
3005
3006 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
3007 self.indent += 1;
3008 let before = self.out.len();
3009 self.indent -= 1;
3010 self.arm_body_at(body, before)
3011 }
3012
3013 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
3014 match body {
3015 Expr::Block(b) => self.nested_block(&b.block)?,
3016 other => {
3017 self.indent += 1;
3018 // An arm's value is the `match`'s value, so it is typed by
3019 // whatever the `match` is being assigned to -- without which
3020 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
3021 let want = self.target.clone().and_then(|(_, t)| t);
3022 let v = match (want, expressible(other)) {
3023 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
3024 _ => self.expr_stmt(other)?,
3025 };
3026 self.emit_tail(v);
3027 self.indent -= 1;
3028 }
3029 }
3030 if self.out.len() == before {
3031 self.indent += 1;
3032 self.line("discard");
3033 self.indent -= 1;
3034 }
3035 Ok(())
3036 }
3037
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3038 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
3039 match p {
3040 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
3041 Pat::Or(o) => {
3042 let mut out = Vec::new();
3043 for p in &o.cases {
3044 out.extend(self.pat_labels(p, expect)?);
3045 }
3046 Ok(out)
3047 }
3048 Pat::Range(r) => {
3049 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
3050 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
3051 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
3052 let op = match r.limits {
3053 syn::RangeLimits::HalfOpen(_) => "..<",
3054 syn::RangeLimits::Closed(_) => "..",
3055 };
3056 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
3057 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3058 Pat::Path(pp) => {
3059 if let Some((def, v)) = self.resolve_variant(&pp.path) {
3060 return Ok(vec![if def.simple {
3061 format!("{}.{}", ident(&def.name), ident(&v))
3062 } else {
3063 def.kind_ident(&v)
3064 }]);
3065 }
3066 Ok(vec![ident(&path_name(&pp.path))])
3067 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3068 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3069 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3070 .into()),
3071 }
3072 }
3073
3074 // --------------------------------------------------------- expressions
3075
3076 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
3077 self.expr_at(e, None)
3078 }
3079
3080 /// Lower `e`, with the type the surrounding code expects of it.
3081 ///
3082 /// Rust infers an unsuffixed integer literal's type from its context and
3083 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
3084 /// expected type down to the literal is what makes `let x: u8 = 255` and
3085 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
3086 /// widths silently diverge, which is exactly the class of bug this
3087 /// project refuses to ship.
3088 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
3089 match e {
3090 Expr::Lit(l) => self.lit_at(&l.lit, expect),
3091 Expr::Path(p) => {
3092 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3093 if name == "None" {
3094 return Ok(Val::new(self.none_of(expect), expect.cloned()));
3095 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 15h ago3096 // `log::Level` and `log::LevelFilter` come from the facade
3097 // shim, under names no crate can collide with.
3098 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3099 if (q == "Level" || q == "LevelFilter") && !self.enums.contains_key(&q) {
3100 let pre = if q == "Level" { "rsLvl" } else { "rsFlt" };
3101 let t = if q == "Level" { "RsLogLevel" } else { "RsLogFilter" };
3102 if q == "LevelFilter" && name == "Off" {
3103 return Ok(Val::new("rsFltOff", Some(Nim::Prim(t.into()))));
3104 }
3105 if matches!(name.as_str(), "Error" | "Warn" | "Info" | "Debug" | "Trace") {
3106 return Ok(Val::new(
3107 format!("{pre}{name}"),
3108 Some(Nim::Prim(t.into())),
3109 ));
3110 }
3111 }
3112 }
3113
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago3114 // `Perms::READ`: a constant of a `bitflags!` type.
3115 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3116 let q = if q == "Self" {
3117 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3118 } else {
3119 q
3120 };
3121 if let Some(c) = self.flag_consts.get(&(q.clone(), name.clone())) {
3122 return Ok(Val::new(c.clone(), Some(Nim::Named(q, vec![]))));
3123 }
3124 }
3125
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago3126 // `Grid::BORDER`: a `const` declared inside an `impl`.
3127 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3128 let q = if q == "Self" {
3129 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3130 } else {
3131 q
3132 };
3133 if let Some((nim, t)) = self.assoc_consts.get(&(q, name.clone())) {
3134 return Ok(Val::new(nim.clone(), Some(t.clone())));
3135 }
3136 }
3137
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago3138 // `i32::MAX` and friends: an associated const on a primitive.
3139 if matches!(name.as_str(), "MAX" | "MIN") {
3140 if let Some(q) = p.path.segments.iter().rev().nth(1) {
3141 if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) {
3142 if t.is_integer() {
3143 let f = if name == "MAX" { "high" } else { "low" };
3144 return Ok(Val::new(
3145 format!("{}({})", f, t.render()),
3146 Some(t),
3147 ));
3148 }
3149 }
3150 }
3151 }
3152
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3153 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
3154 // declared here. In Nim that is a constructor call.
3155 if p.path.segments.len() > 1 {
3156 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
3157 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
3158 if n == "FmtError" {
3159 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
3160 }
3161 }
3162 }
3163 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
3164 return Ok(Val::new(
3165 format!("{}()", ident(&name)),
3166 Some(Nim::Named(name.clone(), vec![])),
3167 ));
3168 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3169 // A unit enum variant used as a value: `Error::InvalidLength`.
3170 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago3171 let (ty, targs) = self.variant_type(&def, expect)?;
3172 return Ok(if def.simple && targs.is_empty() {
3173 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), Some(ty))
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3174 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago3175 // A unit variant of a generic enum has no argument to
3176 // infer the parameters from, so they are written out.
3177 Val::new(
3178 format!("{}{}()", def.ctor_ident(&v), targs),
3179 Some(ty),
3180 )
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3181 });
3182 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3183 // A `for` binding that stands for an element of the container
3184 // it came from: using it must read (and assigning through it
3185 // must write) that element, not a copy.
3186 if let Some(a) = self.lookup_alias(&name) {
3187 return Ok(match a {
3188 Alias::Value { code, ty } => Val::new(code, ty),
3189 // A window *is* a slice; as a value it is the view it
3190 // denotes, which is what Rust's `&[T]` means too.
3191 Alias::Window { code, off, len, elem } => Val::new(
3192 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
3193 elem.map(|e| Nim::OpenArray(Box::new(e))),
3194 ),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago3195 // An iterator is not a value here: it is consumed by a
3196 // `for`, or asked for its `.remainder()`.
3197 Alias::Iterator(_) => {
3198 return Err(format!(
3199 "`{name}` is an iterator; it can be iterated or asked \
3200 for its `remainder()`, but not used as a value"
3201 ))
3202 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3203 });
3204 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3205 if let Some(t) = self.lookup(&name) {
3206 return Ok(Val::new(ident(&name), Some(t)));
3207 }
3208 // A top-level function used as a value, e.g. passed to a
3209 // parameter of `impl Fn(..)` type.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3210 if let Some(k) = self.resolve_fn(&p.path) {
3211 let sig = &self.fns[&k];
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3212 let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone()));
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3213 return Ok(Val::new(self.fn_name(&k.0, &k.1), Some(t)));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3214 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3215 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3216 }
3217 Expr::Paren(p) => {
3218 let v = self.expr_at(&p.expr, expect)?;
3219 Ok(Val::new(format!("({})", v.code), v.ty))
3220 }
3221 Expr::Group(g) => self.expr_at(&g.expr, expect),
3222 // `&x` is a value in Nim; `&mut x` in an argument position binds to
3223 // a `var` parameter, which is also just `x` at the call site.
3224 Expr::Reference(r) => self.expr_at(&r.expr, expect),
3225 Expr::Unary(u) => self.unary(u, expect),
3226 Expr::Binary(b) => self.binary(b, expect),
3227 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3228 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
3229 let Expr::Range(r) = &*i.index else { unreachable!() };
3230 let base = self.expr(&i.expr)?;
3231 let lo = match &r.start {
3232 Some(e) => format!("int({})", self.expr(e)?.code),
3233 None => "0".into(),
3234 };
3235 // Nim's `toOpenArray` takes an inclusive upper bound.
3236 let hi = match (&r.end, r.limits) {
3237 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
3238 format!("int({}) - 1", self.expr(e)?.code)
3239 }
3240 (Some(e), syn::RangeLimits::Closed(_)) => {
3241 format!("int({})", self.expr(e)?.code)
3242 }
3243 (None, _) => format!("{}.len - 1", base.code),
3244 };
3245 let elem = elem_of(&base.ty)
3246 .ok_or("cannot infer the element type of this slice")?;
3247 Ok(Val::new(
3248 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
3249 Some(Nim::OpenArray(Box::new(elem))),
3250 ))
3251 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3252 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3253 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
3254 let idx = self.expr(&i.index)?;
3255 return Ok(Val::new(
3256 format!("{}[{} + int({})]", code, off, idx.code),
3257 elem,
3258 ));
3259 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3260 let base = self.expr(&i.expr)?;
3261 let idx = self.expr(&i.index)?;
3262 // Rust indexes with usize; Nim wants an `int`, and a `uint`
3263 // index is a type error there rather than a silent conversion.
3264 let idx_code = match &idx.ty {
3265 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
3266 _ => idx.code.clone(),
3267 };
3268 let elem = match base.ty.clone() {
3269 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
3270 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
3271 _ => None,
3272 };
3273 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
3274 }
3275 Expr::Field(f) => {
3276 let base = self.expr(&f.base)?;
3277 let name = match &f.member {
3278 syn::Member::Named(n) => n.to_string(),
3279 syn::Member::Unnamed(i) => format!("f{}", i.index),
3280 };
3281 let t = match &base.ty {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago3282 Some(bt @ Nim::Named(s, _)) => self
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3283 .structs
3284 .get(s)
3285 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago3286 .map(|(_, t)| self.subst_type_args(s, bt, t.clone())),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3287 _ => None,
3288 };
3289 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
3290 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3291 // `unsafe` is a permission marker, not a semantic change: it does
3292 // not alter what the enclosed operations mean. So the block is
3293 // transparent here, and each operation inside still goes through
3294 // the ordinary lowering -- and is still rejected if it has no
3295 // faithful mapping.
3296 Expr::Unsafe(u) => match single_expr(&u.block) {
3297 Some(e) => self.expr_at(e, expect),
3298 None => Err("an `unsafe` block used as a value must be a single \
3299 expression"
3300 .into()),
3301 },
3302 Expr::Closure(c) => self.closure(c, expect),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3303 Expr::Try(t) => self.try_op(t),
3304 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3305 Expr::MethodCall(m) => self.method(m, expect),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3306 Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
3307 // `vec![..]`'s elements take their type from the annotation on
3308 // the binding, exactly as Rust's would.
3309 let want = match expect {
3310 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
3311 _ => None,
3312 };
3313 let saved = std::mem::replace(&mut self.vec_expect, want.clone());
3314 let code = self.macro_call(&m.mac);
3315 self.vec_expect = saved;
3316 let code = code?;
3317 let ty = match want {
3318 Some(e) => Some(Nim::Seq(Box::new(e))),
3319 None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
3320 };
3321 Ok(Val::new(code, ty))
3322 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3323 Expr::Macro(m) => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3324 let is_write = matches!(path_name(&m.mac.path).as_str(), "write" | "writeln");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3325 let code = self.macro_call(&m.mac)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3326 // A formatter write is a statement that appends, not a value.
3327 let ty = if is_write { Some(Nim::Unit) } else { None };
3328 Ok(Val::new(code, ty))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3329 }
3330 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3331 if s.rest.is_some() {
3332 return Err("struct update syntax `..rest` is not implemented yet".into());
3333 }
3334 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
3335 // which is constructed positionally in Nim.
3336 if let Some((def, v)) = self.resolve_variant(&s.path) {
3337 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
3338 let mut args = vec![String::new(); fields.len()];
3339 for f in &s.fields {
3340 let syn::Member::Named(m) = &f.member else {
3341 return Err("unsupported enum variant field".into());
3342 };
3343 let want = format!("{}_{}", v, m);
3344 let i = fields
3345 .iter()
3346 .position(|(n, _)| *n == want)
3347 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
3348 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
3349 }
3350 if let Some(i) = args.iter().position(|a| a.is_empty()) {
3351 return Err(format!(
3352 "`{}::{}` is missing field `{}`",
3353 def.name, v, fields[i].0
3354 ));
3355 }
3356 return Ok(Val::new(
3357 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
3358 Some(Nim::Named(def.name.clone(), vec![])),
3359 ));
3360 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago3361 // `Self { .. }` inside an `impl` names the type being
3362 // implemented, and its fields are that type's fields.
3363 let name = match path_name(&s.path).as_str() {
3364 "Self" => self
3365 .self_ty
3366 .as_ref()
3367 .map(type_name)
3368 .ok_or("`Self` outside an `impl` block")?,
3369 other => other.to_string(),
3370 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3371 let mut parts = Vec::new();
3372 for f in &s.fields {
3373 let fname = match &f.member {
3374 syn::Member::Named(n) => n.to_string(),
3375 syn::Member::Unnamed(i) => format!("f{}", i.index),
3376 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3377 let want = self
3378 .structs
3379 .get(&name)
3380 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
3381 .map(|(_, t)| t.clone());
3382 let v = self.expr_at(&f.expr, want.as_ref())?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3383 parts.push(format!("{}: {}", ident(&fname), v.code));
3384 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago3385 // Nim cannot infer an object's generic parameters from a
3386 // constructor's field values, so they are written out.
3387 let gp = self.type_generics.get(&name).cloned().unwrap_or_default();
3388 let ty = if gp.is_empty() {
3389 Nim::Named(name.clone(), vec![])
3390 } else {
3391 match expect {
3392 Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => {
3393 Nim::Named(name.clone(), a.clone())
3394 }
3395 _ => {
3396 return Err(format!(
3397 "`{name} {{ .. }}` is generic, and Nim cannot infer \
3398 its parameters from the field values; annotate the \
3399 binding or the return type"
3400 ))
3401 }
3402 }
3403 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3404 Ok(Val::new(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago3405 format!("{}({})", ty.render(), parts.join(", ")),
3406 Some(ty),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3407 ))
3408 }
3409 Expr::Array(a) => {
3410 let mut parts = Vec::new();
3411 let mut elem = match expect {
3412 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
3413 Some((**t).clone())
3414 }
3415 _ => None,
3416 };
3417 for e in &a.elems {
3418 let want = elem.clone();
3419 let v = self.expr_at(e, want.as_ref())?;
3420 elem = elem.or(v.ty.clone());
3421 parts.push(v.code);
3422 }
3423 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
3424 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
3425 }
3426 Expr::Repeat(r) => {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago3427 // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size
3428 // array from a `seq`, so the expected type decides which, and
3429 // an array needs its elements written out.
3430 let want_elem = match expect {
3431 Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => {
3432 Some((**e).clone())
3433 }
3434 _ => None,
3435 };
3436 let v = self.expr_at(&r.expr, want_elem.as_ref())?;
3437 if let Some(Nim::Array(n, _)) = expect {
3438 let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect();
3439 let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t)));
3440 return Ok(Val::new(format!("[{}]", elems.join(", ")), t));
3441 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3442 let n = self.expr(&r.len)?;
3443 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
3444 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
3445 }
3446 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
3447 Expr::Tuple(t) => {
3448 let mut parts = Vec::new();
3449 let mut tys = Vec::new();
3450 for e in &t.elems {
3451 let v = self.expr(e)?;
3452 tys.push(v.ty.clone());
3453 parts.push(v.code);
3454 }
3455 let ty = tys
3456 .iter()
3457 .cloned()
3458 .collect::<Option<Vec<_>>>()
3459 .map(Nim::Tuple);
3460 Ok(Val::new(format!("({})", parts.join(", ")), ty))
3461 }
3462 // `if` and `match` are expressions in both languages, but only
3463 // when every arm is itself a single expression.
3464 Expr::If(i) => self.if_expr(i, expect),
3465 Expr::Block(b) if b.block.stmts.len() == 1 => {
3466 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
3467 self.expr_at(e, expect)
3468 } else {
3469 Err("block expression with statements in value position is not implemented yet".into())
3470 }
3471 }
3472 other => Err(format!(
3473 "unsupported expression in value position: {}",
3474 expr_kind(other)
3475 )),
3476 }
3477 }
3478
3479 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
3480 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
3481 return Err(
3482 "an `if` used as a value must have an `else` and single-expression arms".into(),
3483 );
3484 };
3485 let c = self.expr(&i.cond)?;
3486 let t = self.expr_at(then, expect)?;
3487 let want = expect.cloned().or_else(|| t.ty.clone());
3488 let e = match &**els {
3489 Expr::Block(b) => match single_expr(&b.block) {
3490 Some(x) => self.expr_at(x, want.as_ref())?,
3491 None => return Err("an `if` used as a value must have single-expression arms".into()),
3492 },
3493 other => self.expr_at(other, want.as_ref())?,
3494 };
3495 let ty = t.ty.clone().or(e.ty.clone());
3496 Ok(Val::new(
3497 format!("(if {}: {} else: {})", c.code, t.code, e.code),
3498 ty,
3499 ))
3500 }
3501
3502 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
3503 match l {
3504 Lit::Int(i) => {
3505 let suffix = i.suffix();
3506 if let Some(why) = ty::rejected(suffix) {
3507 return Err(format!("integer literal `{}`: {}", i, why));
3508 }
3509 let digits = i.base10_digits().to_string();
3510 // Rust's default for an unconstrained integer literal is i32.
3511 // Nim's is `int` (64-bit). Making the width explicit is what
3512 // keeps overflow behaviour the same on both sides.
3513 let t = if suffix.is_empty() {
3514 match expect {
3515 Some(t) if t.is_integer() => t.clone(),
3516 // Rust's fallback for an otherwise-unconstrained
3517 // integer literal.
3518 _ => Nim::Prim("int32".into()),
3519 }
3520 } else {
3521 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
3522 };
3523 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
3524 }
3525 Lit::Float(f) => {
3526 let t = match f.suffix() {
3527 "" => match expect {
3528 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
3529 _ => Nim::Prim("float64".into()),
3530 },
3531 "f64" => Nim::Prim("float64".into()),
3532 "f32" => Nim::Prim("float32".into()),
3533 s => return Err(format!("unknown float suffix `{s}`")),
3534 };
3535 let d = f.base10_digits();
3536 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
3537 Ok(Val::new(d, Some(t)))
3538 }
3539 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
3540 Lit::Str(s) => Ok(Val::new(
3541 fmt::nim_str(&s.value()),
3542 Some(Nim::Prim("string".into())),
3543 )),
3544 Lit::Char(c) => Ok(Val::new(
3545 format!("Rune({})", c.value() as u32),
3546 Some(Nim::Prim("Rune".into())),
3547 )),
3548 Lit::Byte(b) => Ok(Val::new(
3549 format!("{}'u8", b.value()),
3550 Some(Nim::Prim("uint8".into())),
3551 )),
3552 Lit::ByteStr(b) => {
3553 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
3554 Ok(Val::new(
3555 format!("@[{}]", bytes.join(", ")),
3556 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
3557 ))
3558 }
3559 other => Err(format!("unsupported literal: {other:?}")),
3560 }
3561 }
3562
3563 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
3564 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
3565 // the positive half of the range before the negation runs. Folding the
3566 // sign into the literal keeps `i8::MIN` and friends expressible.
3567 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
3568 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
3569 let v = self.lit_at(&l.lit, expect)?;
3570 return Ok(Val::new(format!("-{}", v.code), v.ty));
3571 }
3572 }
3573 let v = self.expr_at(&u.expr, expect)?;
3574 match u.op {
3575 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
3576 // Rust's `!` is logical on bool and bitwise-complement on integers.
3577 // Nim spells those `not` and `not` as well, so one mapping covers
3578 // both — but only because Nim overloads `not` the same way.
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago3579 UnOp::Not(_) => {
3580 if let Some(f) = self.op_proc(&v.ty, "not") {
3581 return Ok(Val::new(format!("{}({})", f, v.code), v.ty));
3582 }
3583 Ok(Val::new(format!("(not {})", v.code), v.ty))
3584 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3585 UnOp::Deref(_) => Ok(v),
3586 _ => Err("unsupported unary operator".into()),
3587 }
3588 }
3589
3590 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
3591 // A comparison's operands are unrelated to the `bool` it produces, so
3592 // the outer expectation is not passed through to them.
3593 let down = match b.op {
3594 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3595 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
3596 _ => expect,
3597 };
3598 let mut l = self.expr_at(&b.left, down)?;
3599 // Rust unifies the two operand types; propagating whichever side is
3600 // known to the other reproduces that, and disagreement then surfaces
3601 // as a Nim type error rather than as a silent width change.
3602 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
3603 if l.ty.is_none() && r.ty.is_some() {
3604 l = self.expr_at(&b.left, r.ty.as_ref())?;
3605 }
3606 let r = std::mem::replace(&mut r, Val::untyped(""));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago3607 // A binary operator on a user type goes to that type's own impl.
3608 if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) {
3609 let want = self.op_param(&l.ty, binary_symbol(&b.op));
3610 let r = self.expr_at(&b.right, want.as_ref())?;
3611 let ret = self
3612 .methods
3613 .get(&(
3614 type_name(l.ty.as_ref().unwrap()),
3615 op_method(binary_symbol(&b.op)).to_string(),
3616 ))
3617 .map(|s| s.ret.clone());
3618 return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret));
3619 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3620 let op = self.bin_op(&b.op, &l, &r)?;
3621 let ty = match b.op {
3622 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3623 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
3624 // Rust's shift takes its result type from the *left* operand, and
3625 // the right may be a different width entirely.
3626 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
3627 _ => l.ty.clone().or(r.ty.clone()),
3628 };
3629 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
3630 }
3631
3632 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
3633 Ok(match op {
3634 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
3635 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
3636 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
3637 BinOp::Div(_) | BinOp::DivAssign(_) => {
3638 // Nim spells integer division `div`. Both languages truncate
3639 // toward zero, so once the right operator is chosen the
3640 // semantics match, including for negative operands.
3641 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3642 "cannot tell integer from float division here; annotate the operands",
3643 )?;
3644 if t.is_integer() { "div" } else { "/" }
3645 }
3646 BinOp::Rem(_) | BinOp::RemAssign(_) => {
3647 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3648 "cannot tell integer from float remainder here; annotate the operands",
3649 )?;
3650 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
3651 }
3652 BinOp::And(_) => "and",
3653 BinOp::Or(_) => "or",
3654 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
3655 // bools, exactly as Rust's `&`/`|`/`^` are.
3656 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
3657 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
3658 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
3659 // Settled empirically: Nim's `shr` on a signed integer is
3660 // arithmetic, matching Rust. See DESIGN.md.
3661 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
3662 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
3663 BinOp::Eq(_) => "==",
3664 BinOp::Ne(_) => "!=",
3665 BinOp::Lt(_) => "<",
3666 BinOp::Le(_) => "<=",
3667 BinOp::Gt(_) => ">",
3668 BinOp::Ge(_) => ">=",
3669 other => return Err(format!("unsupported binary operator {other:?}")),
3670 })
3671 }
3672
3673 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
3674 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3675 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3676 let from = v.ty.clone().ok_or_else(|| {
3677 format!(
3678 "cannot lower `as {}`: the source type is unknown, and `as` \
3679 truncates, so the source width decides the result",
3680 to.render()
3681 )
3682 })?;
3683
3684 let code = match (&from, &to) {
3685 (f, t) if f.is_integer() && t.is_integer() => {
3686 // Rust's `as` between integers is a pure bit-width truncation
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 17h ago3687 // or sign-extension, never a range check. `cast` says exactly
3688 // that. (Nim's `T(x)` turns out to truncate here as well --
3689 // see DESIGN.md item 5 -- but `cast` is the spelling that
3690 // means it rather than the one that happens to agree.)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3691 format!("cast[{}]({})", t.render(), v.code)
3692 }
3693 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
3694 format!("{}({})", p, v.code)
3695 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 15h ago3696 // The facade's level enums carry their Rust discriminants, so
3697 // `Level::Info as usize` is the ordinal.
3698 (Nim::Prim(p), t)
3699 if t.is_integer() && (p == "RsLogLevel" || p == "RsLogFilter") =>
3700 {
3701 format!("{}(ord({}))", t.render(), v.code)
3702 }
3703 // A C-like enum's `as` yields its discriminant, which is its
3704 // ordinal in Nim.
3705 (Nim::Named(n, _), t) if t.is_integer() && self.enums.get(n).is_some_and(|d| d.simple) => {
3706 format!("{}(ord({}))", t.render(), v.code)
3707 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago3708 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
3709 format!("{}(ord({}))", t.render(), v.code)
3710 }
3711 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
3712 format!("cast[{}](int32({}))", t.render(), v.code)
3713 }
3714 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
3715 format!("Rune(int32({}))", v.code)
3716 }
3717 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
3718 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
3719 // Rust saturates float->int casts; Nim rounds and range-errors.
3720 // Not the same operation, so it is refused rather than mapped.
3721 return Err(format!(
3722 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
3723 no faithful mapping is implemented",
3724 t.render()
3725 ));
3726 }
3727 (f, t) => {
3728 return Err(format!(
3729 "unsupported cast from `{}` to `{}`",
3730 f.render(),
3731 t.render()
3732 ))
3733 }
3734 };
3735 Ok(Val::new(code, Some(to)))
3736 }
3737
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3738 /// Rust's `?`: return early on the error branch, otherwise yield the value.
3739 ///
3740 /// The early return is statements, not an expression, so they are emitted
3741 /// ahead of the line being built. Every caller lowers its sub-expressions
3742 /// before emitting its own line, which is what makes that ordering hold.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3743 /// The container, start offset, length and element type an expression
3744 /// denotes as a slice. A window alias contributes its own offset, so
3745 /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
3746 /// into the original buffer rather than through a rebuilt view.
3747 fn slice_parts(
3748 &mut self,
3749 e: &Expr,
3750 ) -> Result<(String, String, String, Option<Nim>), String> {
3751 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
3752 return Ok((code, off, len, elem));
3753 }
3754 let v = self.expr(e)?;
3755 let len = format!("{}.len", v.code);
3756 Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
3757 }
3758
3759 /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
3760 fn map_closure(
3761 &mut self,
3762 what: &str,
3763 recv: &Val,
3764 kind: &str,
3765 targs: &[Nim],
3766 c: &syn::ExprClosure,
3767 ) -> Result<Val, String> {
3768 if c.capture.is_some() {
3769 return Err("a `move` closure captures by value; Nim's closures \
3770 capture by reference, and the two are not the same"
3771 .into());
3772 }
3773 if c.inputs.len() != 1 {
3774 return Err(format!("`.{what}()` takes a one-argument closure"));
3775 }
3776 let pname = match &c.inputs[0] {
3777 Pat::Ident(i) => i.ident.to_string(),
3778 Pat::Wild(_) => "unused0".into(),
3779 _ => return Err("only plain identifier closure parameters are supported".into()),
3780 };
3781
3782 let is_opt = kind == "Option";
3783 let tmp = self.fresh("Map");
3784 let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
3785 self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
3786
3787 let body = match &*c.body {
3788 Expr::Block(b) => single_expr(&b.block)
3789 .ok_or("a closure body with statements is not implemented yet")?,
3790 other => other,
3791 };
3792 self.push_scope();
3793 // The parameter names the payload itself, so a view stays a view.
3794 self.bind_alias(
3795 &pname,
3796 Alias::Value {
3797 code: format!("{}.val", tmp),
3798 ty: Some(targs[0].clone()),
3799 },
3800 );
3801 let v = self.expr(body)?;
3802 self.pop_scope();
3803
3804 let inner = v
3805 .ty
3806 .clone()
3807 .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
3808 // `and_then`'s closure already returns the wrapped type; `map`'s does
3809 // not and has to be re-wrapped.
3810 let (test, some_branch, none_branch, out_ty) = if is_opt {
3811 let out = if what == "map" {
3812 Nim::Named("Option".into(), vec![inner.clone()])
3813 } else {
3814 inner.clone()
3815 };
3816 let body_code = if what == "map" {
3817 format!("rsSome[{}]({})", inner.render(), v.code)
3818 } else {
3819 v.code.clone()
3820 };
3821 (
3822 format!("{}.has", tmp),
3823 body_code,
3824 format!("rsNone[{}]()", elem_arg(&out).render()),
3825 out,
3826 )
3827 } else {
3828 let e = targs[1].clone();
3829 let out = if what == "map" {
3830 Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
3831 } else {
3832 inner.clone()
3833 };
3834 let ok_ty = elem_arg(&out);
3835 let body_code = if what == "map" {
3836 format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
3837 } else {
3838 v.code.clone()
3839 };
3840 (
3841 format!("{}.ok", tmp),
3842 body_code,
3843 format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
3844 out,
3845 )
3846 };
3847 Ok(Val::new(
3848 format!("(if {}: {} else: {})", test, some_branch, none_branch),
3849 Some(out_ty),
3850 ))
3851 }
3852
3853 /// `|x| x + 1` -> a Nim anonymous proc.
3854 ///
3855 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
3856 /// A `move` closure captures by value, which is a different thing, so it
3857 /// is rejected rather than lowered to the same construct.
3858 fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
3859 if c.capture.is_some() {
3860 return Err("a `move` closure captures by value; Nim's closures \
3861 capture by reference, and the two are not the same"
3862 .into());
3863 }
3864 let want: Option<&Vec<Nim>> = match expect {
3865 Some(Nim::Proc(a, _)) => Some(a),
3866 _ => None,
3867 };
3868
3869 self.push_scope();
3870 let mut parts = Vec::new();
3871 let mut ptys = Vec::new();
3872 for (i, p) in c.inputs.iter().enumerate() {
3873 let (name, ann) = match p {
3874 Pat::Ident(id) => (id.ident.to_string(), None),
3875 Pat::Type(t) => match &*t.pat {
3876 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
3877 _ => return Err("only plain identifier closure parameters are supported".into()),
3878 },
3879 Pat::Wild(_) => (format!("unused{i}"), None),
3880 _ => return Err("only plain identifier closure parameters are supported".into()),
3881 };
3882 let t = ann
3883 .or_else(|| want.and_then(|w| w.get(i).cloned()))
3884 .ok_or_else(|| {
3885 format!(
3886 "cannot infer the type of closure parameter `{name}`; \
3887 annotate it"
3888 )
3889 })?;
3890 parts.push(format!("{}: {}", ident(&name), t.render()));
3891 self.bind(&name, t.clone());
3892 ptys.push(t);
3893 }
3894
3895 let ret_ann = match &c.output {
3896 ReturnType::Default => None,
3897 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
3898 };
3899 let body = match &*c.body {
3900 Expr::Block(b) => single_expr(&b.block)
3901 .ok_or("a closure body with statements is not implemented yet")?,
3902 other => other,
3903 };
3904 let v = self.expr_at(body, ret_ann.as_ref())?;
3905 self.pop_scope();
3906
3907 let ret = ret_ann
3908 .or_else(|| v.ty.clone())
3909 .ok_or("cannot infer a closure's return type; annotate it")?;
3910 Ok(Val::new(
3911 format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
3912 Some(Nim::Proc(ptys, Box::new(ret))),
3913 ))
3914 }
3915
3916 /// Lower a block's statements at the current indentation, without opening
3917 /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
3918 /// of its own in the generated code.
3919 fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
3920 self.push_scope();
3921 let tail = self.block_body(b)?;
3922 self.emit_tail(tail);
3923 self.pop_scope();
3924 Ok(())
3925 }
3926
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3927 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
3928 if self.in_loop_cond {
3929 return Err("`?` in a loop condition is not implemented yet: the \
3930 early-return it expands to would be evaluated once, \
3931 before the loop, rather than on each iteration"
3932 .into());
3933 }
3934 let v = self.expr(&t.expr)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago3935 if self.fmt_param.is_some() {
3936 // Writing into a string cannot fail, so `?` on a formatter write
3937 // is a no-op. `?` on anything else can fail, and `format!` panics
3938 // when a formatting impl returns an error -- so that is what the
3939 // error branch does here, with std's own message.
3940 if v.ty.as_ref() == Some(&Nim::Unit) {
3941 return Ok(v);
3942 }
3943 if let Some(Nim::Named(n, a)) = v.ty.clone() {
3944 if n == "Result" && a.len() == 2 {
3945 let tmp = self.fresh("Fmt");
3946 self.line(&format!(
3947 "let {}: {} = {}",
3948 tmp,
3949 Nim::Named(n, a.clone()).render(),
3950 v.code
3951 ));
3952 self.line(&format!("if not {}.ok:", tmp));
3953 self.line(
3954 " rsPanic(\"a formatting trait implementation returned an error\")",
3955 );
3956 return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone())));
3957 }
3958 }
3959 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago3960 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
3961 // An `Option`/`Result` of a view: the check is emitted here and the
3962 // view itself survives as an alias, since it has no value form.
3963 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
3964 let err = v.guard_err.clone().ok_or(
3965 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
3966 )?;
3967 let Nim::Named(n, ra) = &ret else {
3968 return Err(format!("`?` in a function returning `{}`", ret.render()));
3969 };
3970 if n != "Result" || ra.len() != 2 {
3971 return Err(format!("`?` in a function returning `{}`", ret.render()));
3972 }
3973 self.line(&format!("if not {}:", guard));
3974 self.line(&format!(
3975 " return rsErr[{}, {}]({})",
3976 ra[0].render(),
3977 ra[1].render(),
3978 err
3979 ));
3980 let mut out = Val::new(String::new(), None);
3981 out.window = Some(w);
3982 return Ok(out);
3983 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago3984 let vt = v.ty.clone().ok_or(
3985 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
3986 )?;
3987 let ret = self
3988 .ret
3989 .clone()
3990 .ok_or("`?` outside a function with a return type")?;
3991 let tmp = self.fresh("Try");
3992 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
3993
3994 match (&vt, &ret) {
3995 (Nim::Named(a, ai), Nim::Named(b, bi))
3996 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
3997 {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago3998 // Rust inserts a `From::from` on the error here. Where the
3999 // types differ we call the crate's own `impl From`; we never
4000 // assume the conversion is the identity.
4001 let err = if ai[1] == bi[1] {
4002 format!("{}.err", tmp)
4003 } else {
4004 let key = (type_name(&ai[1]), type_name(&bi[1]));
4005 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4006 format!(
4007 "`?` needs `From<{}> for {}` to convert the error, and \
4008 no such `impl` is in scope; assuming the conversion is \
4009 the identity would be a guess",
4010 key.0, key.1
4011 )
4012 })?;
4013 format!("{}({}.err)", f, tmp)
4014 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4015 self.line(&format!("if not {}.ok:", tmp));
4016 self.line(&format!(
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4017 " return rsErr[{}, {}]({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4018 bi[0].render(),
4019 bi[1].render(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4020 err
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4021 ));
4022 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
4023 }
4024 (Nim::Named(a, ai), Nim::Named(b, bi))
4025 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
4026 {
4027 self.line(&format!("if not {}.has:", tmp));
4028 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
4029 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
4030 }
4031 _ => Err(format!(
4032 "`?` on `{}` in a function returning `{}` is not a supported \
4033 combination",
4034 vt.render(),
4035 ret.render()
4036 )),
4037 }
4038 }
4039
4040 fn call(&mut self, c: &syn::ExprCall, expect: Option<&Nim>) -> Result<Val, String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4041 let Expr::Path(p) = &*c.func else {
4042 return Err("only calls to named functions are supported".into());
4043 };
4044 let name = path_name(&p.path);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4045 let target = self.resolve_fn(&p.path);
4046 let ptys: Vec<Nim> = target
4047 .as_ref()
4048 .and_then(|k| self.fns.get(k))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4049 .map(|s| s.params.clone())
4050 .unwrap_or_default();
4051 let mut args = Vec::new();
4052 for (i, a) in c.args.iter().enumerate() {
4053 let want = ptys.get(i).cloned();
4054 args.push(self.expr_at(a, want.as_ref())?);
4055 }
4056 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
4057
4058 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4059 // `Ok`/`Err` must name the *whole* Result type, not just the half
4060 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
4061 match name.as_str() {
4062 "Some" => {
4063 let inner = match expect {
4064 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
4065 _ => {
4066 return Err("`Some(..)` needs a known `Option<T>` type here; \
4067 annotate the binding or the return type"
4068 .into())
4069 }
4070 };
4071 return Ok(Val::new(
4072 format!("rsSome[{}]({})", inner, codes.join(", ")),
4073 expect.cloned(),
4074 ));
4075 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4076 "Ok" if self.fmt_param.is_some()
4077 && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) =>
4078 {
4079 // `Ok(())` ends a `fmt` body: nothing more is written.
4080 return Ok(Val::new(String::new(), Some(Nim::Unit)));
4081 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4082 "Ok" | "Err" => {
4083 let (t, e) = match expect {
4084 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4085 (a[0].render(), a[1].render())
4086 }
4087 _ => {
4088 return Err(format!(
4089 "`{name}(..)` needs a known `Result<T, E>` type here; \
4090 annotate the binding or the return type"
4091 ))
4092 }
4093 };
4094 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
4095 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
4096 return Ok(Val::new(
4097 format!("{}[{}, {}]({})", ctor, t, e, arg),
4098 expect.cloned(),
4099 ));
4100 }
4101 _ => {}
4102 }
4103
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4104 // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's
4105 // object constructor names its fields even when Rust's does not.
4106 if let Some(fields) = self.structs.get(&name).cloned() {
4107 if fields.len() == c.args.len() {
4108 let mut parts = Vec::new();
4109 for (i, a) in c.args.iter().enumerate() {
4110 let v = self.expr_at(a, Some(&fields[i].1))?;
4111 parts.push(format!("{}: {}", ident(&fields[i].0), v.code));
4112 }
4113 return Ok(Val::new(
4114 format!("{}({})", ident(&name), parts.join(", ")),
4115 Some(Nim::Named(name.clone(), vec![])),
4116 ));
4117 }
4118 }
4119
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 15h ago4120 // `log::set_max_level` / `log::max_level`.
4121 if name == "set_max_level" && codes.len() == 1 {
4122 return Ok(Val::new(
4123 format!("rsLogMaxLevel = int({})", codes[0]),
4124 Some(Nim::Unit),
4125 ));
4126 }
4127 if name == "max_level" && codes.is_empty() {
4128 return Ok(Val::new(
4129 "RsLogFilter(rsLogMaxLevel)",
4130 Some(Nim::Prim("RsLogFilter".into())),
4131 ));
4132 }
4133
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago4134 // `Spacing::from(d)`: a `From` impl called through its target type.
4135 // Rust picks the impl by the argument's type, and so do we -- Nim
4136 // cannot overload on return type, so each impl has its own proc name.
4137 if name == "from" && codes.len() == 1 {
4138 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
4139 let q = if q == "Self" {
4140 self.self_ty.as_ref().map(type_name).unwrap_or(q)
4141 } else {
4142 q
4143 };
4144 if let Some(src) = args[0].ty.as_ref().map(type_name) {
4145 if let Some(f) = self.from_impls.get(&(src, q.clone())).cloned() {
4146 return Ok(Val::new(
4147 format!("{}({})", f, codes[0]),
4148 Some(Nim::Named(q, vec![])),
4149 ));
4150 }
4151 }
4152 }
4153 }
4154
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago4155 // `u32::from(b)`: `From` between primitives is lossless by definition
4156 // -- it is the widening direction only -- so a plain Nim conversion is
4157 // exact. (The truncating direction is `as`, which is `cast`.)
4158 if name == "from" && codes.len() == 1 {
4159 if let Some(q) = p.path.segments.iter().rev().nth(1) {
4160 if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) {
4161 return Ok(Val::new(
4162 format!("{}({})", t, codes[0]),
4163 Some(Nim::Prim(t)),
4164 ));
4165 }
4166 }
4167 }
4168
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4169 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
4170 // string view; no copy, no validation, same memory.
4171 if name == "from_utf8_unchecked" && codes.len() == 1 {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4172 // `String::from_utf8_unchecked(v)` takes ownership and yields an
4173 // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields
4174 // a view. Same name, different operations -- the qualifier says
4175 // which, and an unqualified call is ambiguous.
4176 let q = p
4177 .path
4178 .segments
4179 .iter()
4180 .rev()
4181 .nth(1)
4182 .map(|s| s.ident.to_string());
4183 return match q.as_deref() {
4184 Some("String") => Ok(Val::new(
4185 format!("rsStringOf({})", codes[0]),
4186 Some(Nim::Prim("string".into())),
4187 )),
4188 Some("str") => Ok(Val::new(
4189 format!("rsStrView({})", codes[0]),
4190 Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
4191 )),
4192 _ => Err(
4193 "`from_utf8_unchecked` must be written as `str::..` (a \
4194 borrowed view) or `String::..` (an owned string); the two \
4195 are different operations"
4196 .into(),
4197 ),
4198 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4199 }
4200
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4201 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
4202 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago4203 let (ty, _) = self.variant_type(&def, expect)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4204 return Ok(Val::new(
4205 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago4206 Some(ty),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4207 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4208 }
4209
4210 // A bare path that names a primitive type is Rust's tuple-struct-like
4211 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4212 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
4213 // is invoked.
4214 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
4215 return Ok(Val::new(
4216 format!("{}({})", ident(&name), codes.join(", ")),
4217 Some((*ret).clone()),
4218 ));
4219 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago4220 // `Adler32::new()` / `Adler32::default()`: a method called through
4221 // its type rather than through a receiver.
4222 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
4223 // `Self::new()` inside an `impl` names the type being implemented.
4224 let q = if q == "Self" {
4225 self.self_ty.as_ref().map(type_name).unwrap_or(q)
4226 } else {
4227 q
4228 };
4229 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago4230 // Re-lower the arguments with the declared parameter types, so
4231 // a literal takes the width the signature asks for.
4232 let declared = sig.params.clone();
4233 let mut args = args.clone();
4234 let mut codes = codes.clone();
4235 for (i, a) in c.args.iter().enumerate() {
4236 if let Some(want) = declared.get(i) {
4237 let want = want.clone().unvar();
4238 args[i] = self.expr_at(a, Some(&want))?;
4239 codes[i] = args[i].code.clone();
4240 }
4241 }
4242 let sig = &self.methods[&(q.clone(), name.clone())];
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago4243 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
4244 let ret = Self::instantiate(sig, &arg_tys);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago4245 let nim = self
4246 .statics
4247 .get(&(q.clone(), name.clone()))
4248 .cloned()
4249 .unwrap_or_else(|| ident(&name));
4250 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
4251 }
4252 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago4253 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
4254 let ret = target
4255 .as_ref()
4256 .and_then(|k| self.fns.get(k))
4257 .map(|sig| Self::instantiate(sig, &arg_tys));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4258 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4259 return Err(format!(
4260 "call to unknown function `{name}`; only functions defined in \
4261 this file and the supported standard-library subset can be lowered"
4262 ));
4263 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4264 let nim = match &target {
4265 Some((m, n)) => self.fn_name(m, n),
4266 None => ident(&name),
4267 };
4268 Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4269 }
4270
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4271 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4272 let name = m.method.to_string();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago4273 // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield.
4274 if name == "remainder" && m.args.is_empty() {
4275 if let Expr::Path(p) = &*m.receiver {
4276 if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) {
4277 if let Iter::Chunks { code, base, len, k, elem, .. } = &*it {
4278 let kept = format!("(({} div int({})) * int({}))", len, k, k);
4279 let mut v = Val::new(
4280 String::new(),
4281 elem.clone().map(|e| Nim::OpenArray(Box::new(e))),
4282 );
4283 v.window = Some(Alias::Window {
4284 code: code.clone(),
4285 off: format!("({} + {})", base, kept),
4286 len: format!("({} - {})", len, kept),
4287 elem: elem.clone(),
4288 });
4289 return Ok(v);
4290 }
4291 return Err(
4292 "`.remainder()` is only defined for a `chunks_exact` iterator".into(),
4293 );
4294 }
4295 }
4296 return Err("`.remainder()` needs an iterator bound by `let`".into());
4297 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4298 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
4299 match name.as_str() {
4300 "len" => {
4301 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
4302 }
4303 "is_empty" => {
4304 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
4305 }
4306 other => {
4307 return Err(format!(
4308 "`.{other}()` on a slice window from `chunks_exact`/\
4309 `windows` is not implemented; only indexing and \
4310 `len()` are"
4311 ))
4312 }
4313 }
4314 }
4315 let recv = self.expr(&m.receiver)?;
4316 let rt0 = recv.ty.clone();
4317
4318// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
4319 // way to put a view in an object, so instead of materialising an
4320 // Option the view and its validity condition travel together until
4321 // an `ok_or`/`?`/`unwrap` resolves them.
4322 if matches!(name.as_str(), "get" | "get_mut")
4323 && matches!(m.args.first(), Some(Expr::Range(_)))
4324 {
4325 let Some(Expr::Range(r)) = m.args.first() else { unreachable!() };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4326 let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4327 let lo = match &r.start {
4328 Some(e) => format!("int({})", self.expr(e)?.code),
4329 None => "0".into(),
4330 };
4331 let len = match (&r.end, r.limits) {
4332 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
4333 format!("(int({}) - {})", self.expr(e)?.code, lo)
4334 }
4335 (Some(e), syn::RangeLimits::Closed(_)) => {
4336 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
4337 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4338 (None, _) => format!("({} - {})", blen, lo),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4339 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4340 // Hoisted, so the bounds are computed once -- as Rust computes
4341 // them once -- and cannot be re-evaluated later in a scope where
4342 // the names they mention have been shadowed by a loop pattern.
4343 let off_t = self.fresh("Off");
4344 let len_t = self.fresh("Len");
4345 self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
4346 self.line(&format!("let {}: int = {}", len_t, len));
4347 let elem = belem
4348 .or_else(|| elem_of(&rt0))
4349 .ok_or("cannot infer the element type of this slice")?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4350 let mut v = Val::new(
4351 String::new(),
4352 Some(Nim::Named(
4353 "Option".into(),
4354 vec![Nim::OpenArray(Box::new(elem.clone()))],
4355 )),
4356 );
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4357 v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4358 v.window = Some(Alias::Window {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4359 code,
4360 off: off_t,
4361 len: len_t,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4362 elem: Some(elem),
4363 });
4364 return Ok(v);
4365 }
4366
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4367 // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
4368 // parameter type comes from the receiver, so they are handled before
4369 // the arguments are lowered. The closure is expanded inline, with its
4370 // parameter aliased to the payload: that keeps the whole thing an
4371 // expression and avoids handing a view to a generic proc.
4372 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
4373 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
4374 (recv.ty.clone(), &m.args[0])
4375 {
4376 if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
4377 {
4378 return self.map_closure(&name, &recv, &kind, &targs, c);
4379 }
4380 }
4381 }
4382
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4383 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
4384 // own type; `v.push(e)` takes the element type.
4385 let arg_want = match (name.as_str(), &recv.ty) {
4386 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
4387 (_, t) => t.clone(),
4388 };
4389 let mut args = Vec::new();
4390 for a in &m.args {
4391 args.push(self.expr_at(a, arg_want.as_ref())?);
4392 }
4393 let a0 = args.first().map(|a| a.code.clone());
4394 let rt = recv.ty.clone();
4395
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 16h ago4396 // A method the input defines wins over our model of the standard
4397 // library: `is_empty` on a `bitflags!` type is that type's, not the
4398 // sequence one. Rust resolves inherent methods the same way.
4399 if let Some(t) = &rt {
4400 let key = (type_name(t), name.clone());
4401 if self.methods.contains_key(&key) {
4402 let declared = self.methods[&key].params.clone();
4403 let skip = usize::from(declared.len() == m.args.len() + 1);
4404 for (i, a) in m.args.iter().enumerate() {
4405 if let Some(want) = declared.get(i + skip) {
4406 let want = want.clone().unvar();
4407 args[i] = self.expr_at(a, Some(&want))?;
4408 }
4409 }
4410 let mut arg_tys: Vec<Option<Nim>> = vec![rt.clone()];
4411 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
4412 let ret = Self::instantiate(&self.methods[&key], &arg_tys);
4413 let nim = self
4414 .statics
4415 .get(&key)
4416 .cloned()
4417 .unwrap_or_else(|| ident(&name));
4418 let mut all = vec![recv.code.clone()];
4419 all.extend(args.iter().map(|a| a.code.clone()));
4420 return Ok(Val::new(format!("{}({})", nim, all.join(", ")), Some(ret)));
4421 }
4422 }
4423
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4424 let (code, ty) = match name.as_str() {
4425 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
4426 // explicit so that a `usize` binding type-checks on the Nim side.
4427 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
4428 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
4429 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
4430 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
4431 | "into_iter" => (recv.code.clone(), rt.clone()),
4432 "unwrap" | "expect" => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4433 // Expanded inline rather than called as a generic proc: when
4434 // the payload is a view, Nim can only borrow from a path
4435 // expression, which a proc body containing the panic is not.
4436 let (kind, inner) = match &rt {
4437 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
4438 ("Option", a[0].clone())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4439 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4440 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4441 ("Result", a[0].clone())
4442 }
4443 _ => {
4444 return Err(format!(
4445 "`.{name}()` needs a known `Option`/`Result` receiver type"
4446 ))
4447 }
4448 };
4449 if self.in_loop_cond {
4450 return Err(format!(
4451 "`.{name}()` in a loop condition is not implemented yet: the \
4452 check it expands to would run once, before the loop"
4453 ));
4454 }
4455 let tmp = self.fresh("Unwrap");
4456 let rty = rt.clone().unwrap();
4457 self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code));
4458 let (test, msg) = if kind == "Option" {
4459 (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value")
4460 } else {
4461 (format!("{}.ok", tmp), "called `Result::unwrap()` on an `Err` value")
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4462 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4463 let msg = if name == "expect" {
4464 args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg))
4465 } else {
4466 fmt::nim_str(msg)
4467 };
4468 self.line(&format!("if not {}:", test));
4469 self.line(&format!(" rsPanic({})", msg));
4470 // If the payload is a view, hand back an alias rather than a
4471 // value: Nim will not let a `let` borrow out of a local, and a
4472 // view is a reference anyway, so there is nothing to bind.
4473 // `{tmp}.val` is a plain field access, so substituting it at
4474 // each use re-evaluates nothing.
4475 if matches!(inner, Nim::OpenArray(_)) {
4476 let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone()));
4477 v.window = Some(Alias::Value {
4478 code: format!("{}.val", tmp),
4479 ty: Some(inner),
4480 });
4481 return Ok(v);
4482 }
4483 (format!("{}.val", tmp), Some(inner))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4484 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4485 "ok_or" if recv.guard.is_some() => {
4486 let e = args.first().ok_or("`ok_or` takes one argument")?;
4487 let ety = e.ty.clone();
4488 let mut v = recv.clone();
4489 v.guard_err = Some(e.code.clone());
4490 v.ty = match (&recv.ty, ety) {
4491 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
4492 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
4493 }
4494 _ => None,
4495 };
4496 return Ok(v);
4497 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4498 "ok_or" => {
4499 let inner = match &rt {
4500 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
4501 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
4502 };
4503 let e = args.first().ok_or("`ok_or` takes one argument")?;
4504 let ety = e
4505 .ty
4506 .clone()
4507 .ok_or("`ok_or` needs a known error type for its argument")?;
4508 (
4509 format!(
4510 "rsOkOr[{}, {}]({}, {})",
4511 inner.render(),
4512 ety.render(),
4513 recv.code,
4514 e.code
4515 ),
4516 Some(Nim::Named("Result".into(), vec![inner, ety])),
4517 )
4518 }
4519 "unwrap_or" => {
4520 let inner = match &rt {
4521 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
4522 Some(a[0].clone())
4523 }
4524 _ => None,
4525 };
4526 (
4527 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
4528 inner,
4529 )
4530 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4531 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
4532 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
4533 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
4534 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
4535
4536 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
4537 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
4538 // Nim raises OverflowDefect, so the operation is routed through
4539 // the unsigned view of the same width, which is what Rust's
4540 // wrapping_* is defined to compute.
4541 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
4542 let op = match name.as_str() {
4543 "wrapping_add" => "+",
4544 "wrapping_sub" => "-",
4545 _ => "*",
4546 };
4547 let t = rt.clone().ok_or_else(|| {
4548 format!("`{name}` needs a known receiver type to pick the wrapping width")
4549 })?;
4550 if !t.is_integer() {
4551 return Err(format!("`{name}` on a non-integer type"));
4552 }
4553 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
4554 if t.is_unsigned() {
4555 (format!("({} {} {})", recv.code, op, arg), Some(t))
4556 } else {
4557 let u = unsigned_peer(&t)?;
4558 (
4559 format!(
4560 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
4561 t.render(), u, recv.code, op, u, arg
4562 ),
4563 Some(t),
4564 )
4565 }
4566 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4567 // Inside a formatting impl, a write through the `Formatter` *is*
4568 // the value the proc returns, so it lowers to the string written.
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4569 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => {
4570 let a = args.first().ok_or("`write_str` takes one argument")?;
4571 // A `&str` argument is a character view, not a Nim string.
4572 let text = match &a.ty {
4573 Some(Nim::Prim(p)) if p == "string" => a.code.clone(),
4574 _ => format!("rsDisplay({})", a.code),
4575 };
4576 (format!("result.add({})", text), Some(Nim::Unit))
4577 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago4578 "saturating_add" | "saturating_sub" | "saturating_mul" | "checked_add"
4579 | "checked_sub" | "checked_mul" => {
4580 let t = rt
4581 .clone()
4582 .filter(|t| t.is_integer())
4583 .ok_or_else(|| format!("`{name}` needs a known integer receiver"))?;
4584 let arg = args
4585 .first()
4586 .ok_or_else(|| format!("`{name}` takes one argument"))?;
4587 let f = match name.as_str() {
4588 "saturating_add" => "rsSatAdd",
4589 "saturating_sub" => "rsSatSub",
4590 "saturating_mul" => "rsSatMul",
4591 "checked_add" => "rsChkAdd",
4592 "checked_sub" => "rsChkSub",
4593 _ => "rsChkMul",
4594 };
4595 let out = if name.starts_with("checked") {
4596 Nim::Named("Option".into(), vec![t])
4597 } else {
4598 t
4599 };
4600 (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
4601 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4602 "abs" => (format!("abs({})", recv.code), rt.clone()),
4603 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4604 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4605 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
4606 "as_bytes" | "into_bytes" => (
4607 format!("rsBytes({})", recv.code),
4608 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
4609 ),
4610
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4611 "into" => {
4612 // `.into()` resolves through the `impl From` declarations, and
4613 // needs the target type to pick one.
4614 let from = rt
4615 .clone()
4616 .ok_or("`.into()` needs a known receiver type")?;
4617 let to = expect
4618 .ok_or("`.into()` needs a known target type; annotate the binding")?;
4619 let key = (type_name(&from), type_name(to));
4620 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4621 format!(
4622 "no `impl From<{}> for {}` in this file, so `.into()` has \
4623 no conversion to call",
4624 key.0, key.1
4625 )
4626 })?;
4627 (format!("{}({})", f, recv.code), Some(to.clone()))
4628 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4629 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4630 // A method defined in this file via `impl`, found by the
4631 // receiver's type rather than by name alone.
4632 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 16h ago4633 // Re-lower the arguments with the declared parameter types:
4634 // a method's own signature says what width its literals are,
4635 // which the receiver's type does not.
4636 let declared: Option<Vec<Nim>> = key
4637 .as_ref()
4638 .and_then(|k| self.methods.get(k))
4639 .map(|s| s.params.clone());
4640 if let Some(d) = &declared {
4641 // params[0] is the receiver for a method with `self`.
4642 let skip = usize::from(d.len() == m.args.len() + 1);
4643 for (i, a) in m.args.iter().enumerate() {
4644 if let Some(want) = d.get(i + skip) {
4645 let want = want.clone().unvar();
4646 args[i] = self.expr_at(a, Some(&want))?;
4647 }
4648 }
4649 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago4650 let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
4651 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago4652 let sig = key
4653 .as_ref()
4654 .and_then(|k| self.methods.get(k))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 16h ago4655 .map(|s| Self::instantiate(s, &arg_tys));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4656 if let Some(ret) = sig {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago4657 // Use the name the proc was actually emitted under: an
4658 // inherent method is qualified by its module, a trait
4659 // method by its trait.
4660 let nim = key
4661 .and_then(|k| self.statics.get(&k).cloned())
4662 .unwrap_or_else(|| ident(&name));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4663 let mut all = vec![recv.code.clone()];
4664 all.extend(args.iter().map(|a| a.code.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago4665 (format!("{}({})", nim, all.join(", ")), Some(ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4666 } else {
4667 return Err(format!(
4668 "unsupported method `.{name}()`; it is neither defined in \
4669 this file nor part of the standard-library subset that \
4670 has a verified Nim equivalent"
4671 ));
4672 }
4673 }
4674 };
4675 Ok(Val::new(code, ty))
4676 }
4677
4678 // -------------------------------------------------------------- macros
4679
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4680 /// The element type of a `vec![..]`, from its first element.
4681 fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
4682 let body = mac.tokens.to_string();
4683 if body.trim().is_empty() {
4684 return Ok(None);
4685 }
4686 let first: Option<Expr> = if body.contains(';') {
4687 // The whole body must be consumed or the parse fails, so the
4688 // length is parsed too even though only the element is wanted.
4689 mac.parse_body_with(|input: syn::parse::ParseStream| {
4690 let v: Expr = input.parse()?;
4691 input.parse::<syn::Token![;]>()?;
4692 let _len: Expr = input.parse()?;
4693 Ok(v)
4694 })
4695 .ok()
4696 } else {
4697 mac.parse_body_with(
4698 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4699 )
4700 .ok()
4701 .and_then(|p| p.into_iter().next())
4702 };
4703 match first {
4704 Some(e) => Ok(self.expr(&e)?.ty),
4705 None => Ok(None),
4706 }
4707 }
4708
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4709 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
4710 let name = path_name(&mac.path);
4711 match name.as_str() {
4712 "println" | "print" | "eprintln" | "eprint" => {
4713 let s = self.format_args(mac)?;
4714 let nl = name.ends_with("ln");
4715 Ok(match (name.starts_with('e'), nl) {
4716 (false, true) => format!("echo {s}"),
4717 (false, false) => format!("stdout.write({s})"),
4718 (true, true) => format!("stderr.writeLine({s})"),
4719 (true, false) => format!("stderr.write({s})"),
4720 })
4721 }
4722 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4723 "write" | "writeln" => {
4724 // `write!(f, "..", ..)` inside a formatting impl: the first
4725 // argument is the sink, the rest is an ordinary format call.
4726 let args: Vec<Expr> = mac
4727 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
4728 .map_err(|e| format!("write!: {e}"))?
4729 .into_iter()
4730 .collect();
4731 let sink = args.first().ok_or("`write!` needs a sink")?;
4732 if !self.is_fmt_param(sink) {
4733 return Err("`write!` to anything but the `Formatter` of the \
4734 enclosing formatting impl is not implemented"
4735 .into());
4736 }
4737 let s = self.format_pieces(&args[1..])?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4738 let s = if name == "writeln" {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4739 format!("({} & \"\\n\")", s)
4740 } else {
4741 s
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4742 };
4743 Ok(format!("result.add({})", s))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4744 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4745 "panic" => {
4746 let s = self.format_args(mac)?;
4747 Ok(format!("rsPanic({s})"))
4748 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4749 // `debug_assert*` fires in debug builds, which is the profile
4750 // this project models, so it lowers the same as `assert*`.
4751 "assert" | "debug_assert" => {
4752 let args: Vec<Expr> = mac
4753 .parse_body_with(
4754 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4755 )
4756 .map_err(|e| format!("{name}!: {e}"))?
4757 .into_iter()
4758 .collect();
4759 let cond = args.first().ok_or("`assert!` needs a condition")?;
4760 let v = self.expr(cond)?;
4761 let msg = if args.len() > 1 {
4762 self.format_pieces(&args[1..])?
4763 } else {
4764 fmt::nim_str("assertion failed")
4765 };
4766 Ok(format!("(if not ({}): rsPanic({}))", v.code, msg))
4767 }
4768 "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => {
4769 let args: Vec<Expr> = mac
4770 .parse_body_with(
4771 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4772 )
4773 .map_err(|e| format!("{name}!: {e}"))?
4774 .into_iter()
4775 .collect();
4776 if args.len() < 2 {
4777 return Err(format!("`{name}!` takes two operands"));
4778 }
4779 let a = self.expr(&args[0])?;
4780 let b = self.expr_at(&args[1], a.ty.as_ref())?;
4781 let ne = name.ends_with("_ne");
4782 let op = if ne { "!=" } else { "==" };
4783 // Rust's message shows both sides; reproducing it keeps a
4784 // failing assertion as informative as the original.
4785 let label = if ne { "assertion failed: `(left != right)`" } else { "assertion failed: `(left == right)`" };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4786 Ok(format!(
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4787 "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))",
4788 a.code, op, b.code, fmt::nim_str(label), a.code, b.code
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4789 ))
4790 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 15h ago4791 // The `log` facade. See `src/prelude.nim` for why these are
4792 // lowered directly rather than expanded. The enabled check wraps
4793 // the whole thing because Rust does not evaluate a log record's
4794 // arguments when the level is disabled.
4795 "error" | "warn" | "info" | "debug" | "trace" | "log" => {
4796 let args: Vec<Expr> = mac
4797 .parse_body_with(
4798 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4799 )
4800 .map_err(|e| format!("`{name}!`: {e}"))?
4801 .into_iter()
4802 .collect();
4803 let (level, rest) = if name == "log" {
4804 let first = args.first().ok_or("`log!` needs a level")?;
4805 (self.log_level_of(first)?, &args[1..])
4806 } else {
4807 (
4808 match name.as_str() {
4809 "error" => "rsLvlError",
4810 "warn" => "rsLvlWarn",
4811 "info" => "rsLvlInfo",
4812 "debug" => "rsLvlDebug",
4813 _ => "rsLvlTrace",
4814 }
4815 .to_string(),
4816 &args[..],
4817 )
4818 };
4819 let msg = self.format_pieces(rest)?;
4820 let target = fmt::nim_str(&self.cur_mod.clone());
4821 Ok(format!(
4822 "(if rsLogEnabled({lvl}): rsLog({lvl}, {target}, {msg}))",
4823 lvl = level
4824 ))
4825 }
4826 "log_enabled" => {
4827 let e: Expr = mac
4828 .parse_body()
4829 .map_err(|e| format!("`log_enabled!`: {e}"))?;
4830 let l = self.log_level_of(&e)?;
4831 Ok(format!("rsLogEnabled({l})"))
4832 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4833 "vec" => {
4834 let body = mac.tokens.to_string();
4835 if body.trim().is_empty() {
4836 return Ok("@[]".into());
4837 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4838 // `vec![elem; n]` is the repeat form, not a list. The macro
4839 // body has no brackets, so it is parsed directly.
4840 if body.contains(';') {
4841 let (v, n) = mac
4842 .parse_body_with(|input: syn::parse::ParseStream| {
4843 let v: Expr = input.parse()?;
4844 input.parse::<syn::Token![;]>()?;
4845 let n: Expr = input.parse()?;
4846 Ok((v, n))
4847 })
4848 .map_err(|e| format!("vec![elem; n]: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4849 let want = self.vec_expect.clone();
4850 let v = self.expr_at(&v, want.as_ref())?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4851 let n = self.expr(&n)?;
4852 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
4853 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4854 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
4855 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
4856 .map_err(|e| format!("vec!: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4857 let want = self.vec_expect.clone();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4858 let mut parts = Vec::new();
4859 for e in &elems {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago4860 parts.push(self.expr_at(e, want.as_ref())?.code);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4861 }
4862 Ok(format!("@[{}]", parts.join(", ")))
4863 }
4864 other => Err(format!(
4865 "unsupported macro `{other}!`; a macro whose expansion is not \
4866 known cannot be lowered faithfully"
4867 )),
4868 }
4869 }
4870
4871 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
4872 fn format_args(&mut self, mac: &syn::Macro) -> Result<String, String> {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4873 let args: Vec<Expr> = mac
4874 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
4875 .map_err(|e| format!("format arguments: {e}"))?
4876 .into_iter()
4877 .collect();
4878 self.format_pieces(&args)
4879 }
4880
4881 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
4882 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
4883 let Some(Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. })) = args.first() else {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4884 if args.is_empty() {
4885 return Ok("\"\"".into());
4886 }
4887 return Err("the first argument must be a literal format string".into());
4888 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago4889 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4890
4891 let pieces = fmt::parse(&s.value())?;
4892 let mut parts: Vec<String> = Vec::new();
4893 let mut next = 0usize;
4894 let mut used = vec![false; rest.len()];
4895 for p in &pieces {
4896 match p {
4897 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
4898 fmt::Piece::Arg { r#ref, spec } => {
4899 let v = match r#ref {
4900 fmt::Ref::Next => {
4901 let e = rest.get(next).ok_or("too few arguments for format string")?;
4902 used[next] = true;
4903 next += 1;
4904 self.expr(e)?
4905 }
4906 fmt::Ref::Index(i) => {
4907 let e = rest.get(*i).ok_or("format index out of range")?;
4908 used[*i] = true;
4909 self.expr(e)?
4910 }
4911 fmt::Ref::Named(n) => {
4912 let t = self.lookup(n).ok_or_else(|| {
4913 format!("`{{{n}}}` captures `{n}`, which is not in scope")
4914 })?;
4915 Val::new(ident(n), Some(t))
4916 }
4917 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 18h ago4918 let integer = v.ty.as_ref().is_some_and(|t| t.is_integer());
4919 if spec.radix.is_some() && !integer && v.ty.is_none() {
4920 return Err(
4921 "a radix format (`{:x}`, `{:b}`, ...) needs a known \
4922 argument type: on an integer it formats the bit \
4923 pattern, on anything else it calls that type's own \
4924 impl"
4925 .into(),
4926 );
4927 }
4928 parts.push(fmt::render_arg(&v.code, spec, integer));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4929 }
4930 }
4931 }
4932 // Rust rejects an argument that no `{}` consumes; so do we, rather
4933 // than dropping it from the output.
4934 if let Some(i) = used.iter().position(|u| !u) {
4935 return Err(format!(
4936 "argument {} is never used by the format string",
4937 i + 1
4938 ));
4939 }
4940 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
4941 }
4942}
4943
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago4944/// Whether a pattern introduces a binding.
4945fn binds(p: &Pat) -> bool {
4946 match p {
4947 Pat::Ident(_) => true,
4948 Pat::Guard(g) => binds(&g.pat),
4949 Pat::Paren(x) => binds(&x.pat),
4950 Pat::Reference(r) => binds(&r.pat),
4951 Pat::Or(o) => o.cases.iter().any(binds),
4952 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
4953 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
4954 _ => false,
4955 }
4956}
4957
4958/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
4959fn destructures(p: &Pat) -> bool {
4960 matches!(
4961 p,
4962 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
4963 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
4964 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
4965 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
4966}
4967
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago4968/// Whether an expression has a direct Nim expression form.
4969///
4970/// Nim's `if` is an expression only when every arm is a single expression, and
4971/// its `case` is never one here. Anything else has to be lowered as statements
4972/// that assign into a target.
4973fn expressible(e: &Expr) -> bool {
4974 match e {
4975 Expr::If(i) => {
4976 let Some(then) = single_expr(&i.then_branch) else { return false };
4977 if !expressible(then) {
4978 return false;
4979 }
4980 match &i.else_branch {
4981 None => false,
4982 Some((_, els)) => match &**els {
4983 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
4984 other => expressible(other),
4985 },
4986 }
4987 }
4988 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
4989 _ => true,
4990 }
4991}
4992
4993/// The single expression a block consists of, if that is all it is. An `if`
4994/// can only be lowered as a Nim `if`-expression when both arms are this shape.
4995fn single_expr(b: &syn::Block) -> Option<&Expr> {
4996 match (b.stmts.len(), b.stmts.first()) {
4997 (1, Some(Stmt::Expr(e, None))) => Some(e),
4998 _ => None,
4999 }
5000}
5001
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago5002/// Substitute `params[i] -> args[i]` through a type. Enough of the type
5003/// grammar is covered to expand the aliases we accept; anything else is left
5004/// alone and will be reported by `ty::map` if it is unsupported.
5005fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
5006 use syn::Type;
5007 match t {
5008 Type::Path(p) => {
5009 if p.qself.is_none() && p.path.segments.len() == 1 {
5010 let seg = &p.path.segments[0];
5011 if seg.arguments.is_empty() {
5012 let name = seg.ident.to_string();
5013 if let Some(i) = params.iter().position(|x| *x == name) {
5014 return args[i].clone();
5015 }
5016 }
5017 }
5018 let mut p = p.clone();
5019 for seg in &mut p.path.segments {
5020 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
5021 for g in &mut a.args {
5022 if let syn::GenericArgument::Type(t) = g {
5023 *t = substitute(t, params, args);
5024 }
5025 }
5026 }
5027 }
5028 Type::Path(p)
5029 }
5030 Type::Reference(r) => {
5031 let mut r = r.clone();
5032 r.elem = Box::new(substitute(&r.elem, params, args));
5033 Type::Reference(r)
5034 }
5035 Type::Slice(sl) => {
5036 let mut sl = sl.clone();
5037 sl.elem = Box::new(substitute(&sl.elem, params, args));
5038 Type::Slice(sl)
5039 }
5040 Type::Array(a) => {
5041 let mut a = a.clone();
5042 a.elem = Box::new(substitute(&a.elem, params, args));
5043 Type::Array(a)
5044 }
5045 Type::Tuple(tp) => {
5046 let mut tp = tp.clone();
5047 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
5048 Type::Tuple(tp)
5049 }
5050 Type::Paren(p) => substitute(&p.elem, params, args),
5051 Type::Group(g) => substitute(&g.elem, params, args),
5052 other => other.clone(),
5053 }
5054}
5055
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago5056// --------------------------------------------------------------- utilities
5057
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago5058/// Whether a return type is a borrow of one of the arguments, which Nim
5059/// models with a view rather than with an owned copy.
5060fn returns_borrow(t: &syn::Type) -> bool {
5061 match t {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago5062 syn::Type::Reference(r) => match &*r.elem {
5063 syn::Type::Slice(_) => true,
5064 // `&str` is a borrow of someone else's bytes too, and returning it
5065 // means returning a view, not an owned string.
5066 syn::Type::Path(p) => p.path.is_ident("str"),
5067 _ => false,
5068 },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago5069 syn::Type::Paren(p) => returns_borrow(&p.elem),
5070 syn::Type::Group(g) => returns_borrow(&g.elem),
5071 _ => false,
5072 }
5073}
5074
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 18h ago5075/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
5076/// to the crate root, which is where a flattened module's items live unless
5077/// they came from one of the extra input files.
5078fn module_of(prefix: &[String]) -> String {
5079 match prefix.last() {
5080 Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
5081 _ => String::new(),
5082 }
5083}
5084
5085/// The first type argument of an `Option[T]` / `Result[T, E]`.
5086fn elem_arg(t: &Nim) -> Nim {
5087 match t {
5088 Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
5089 other => other.clone(),
5090 }
5091}
5092
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago5093/// The element type of a sequence-like Nim type.
5094fn elem_of(t: &Option<Nim>) -> Option<Nim> {
5095 match t {
5096 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
5097 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
5098 _ => None,
5099 }
5100}
5101
5102/// The short name a Nim type is known by, for keying method tables.
5103fn type_name(t: &Nim) -> String {
5104 match t {
5105 Nim::Named(n, _) => n.clone(),
5106 Nim::Prim(p) => p.clone(),
5107 other => other.render(),
5108 }
5109}
5110
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago5111/// `(trait, operator)` for every operator trait we dispatch.
5112const OPERATOR_TRAITS: &[(&str, &str)] = &[
5113 ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"),
5114 ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"),
5115 ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="),
5116 ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="),
5117 ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="),
5118 ("Neg", "neg"), ("Not", "not"),
5119];
5120
5121/// `(operator, trait method name)`.
5122const OP_METHOD: &[(&str, &str)] = &[
5123 ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"),
5124 ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"),
5125 ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"),
5126 ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"),
5127 ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"),
5128 (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"),
5129];
5130
5131fn op_method(op: &str) -> &'static str {
5132 OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("")
5133}
5134
5135/// The operator symbol a compound assignment applies.
5136fn compound_symbol(op: &BinOp) -> &'static str {
5137 match op {
5138 BinOp::AddAssign(_) => "+=",
5139 BinOp::SubAssign(_) => "-=",
5140 BinOp::MulAssign(_) => "*=",
5141 BinOp::DivAssign(_) => "/=",
5142 BinOp::RemAssign(_) => "%=",
5143 BinOp::BitAndAssign(_) => "&=",
5144 BinOp::BitOrAssign(_) => "|=",
5145 BinOp::BitXorAssign(_) => "^=",
5146 BinOp::ShlAssign(_) => "<<=",
5147 BinOp::ShrAssign(_) => ">>=",
5148 _ => "",
5149 }
5150}
5151
5152fn binary_symbol(op: &BinOp) -> &'static str {
5153 match op {
5154 BinOp::Add(_) => "+",
5155 BinOp::Sub(_) => "-",
5156 BinOp::Mul(_) => "*",
5157 BinOp::Div(_) => "/",
5158 BinOp::Rem(_) => "%",
5159 BinOp::BitAnd(_) => "&",
5160 BinOp::BitOr(_) => "|",
5161 BinOp::BitXor(_) => "^",
5162 BinOp::Shl(_) => "<<",
5163 BinOp::Shr(_) => ">>",
5164 _ => "",
5165 }
5166}
5167
5168/// The operator a trait overloads, if it is one of the operator traits.
5169fn operator_trait(t: &str) -> Option<&'static str> {
5170 Some(match t {
5171 "Add" => "+",
5172 "Sub" => "-",
5173 "Mul" => "*",
5174 "Div" => "/",
5175 "Rem" => "%",
5176 "BitAnd" => "&",
5177 "BitOr" => "|",
5178 "BitXor" => "^",
5179 "Shl" => "<<",
5180 "Shr" => ">>",
5181 "AddAssign" => "+=",
5182 "SubAssign" => "-=",
5183 "MulAssign" => "*=",
5184 "DivAssign" => "/=",
5185 "RemAssign" => "%=",
5186 "BitAndAssign" => "&=",
5187 "BitOrAssign" => "|=",
5188 "BitXorAssign" => "^=",
5189 "ShlAssign" => "<<=",
5190 "ShrAssign" => ">>=",
5191 "Neg" => "neg",
5192 "Not" => "not",
5193 _ => return None,
5194 })
5195}
5196
5197/// The Nim proc name for a trait method, qualified by trait and type so that
5198/// two traits declaring the same method name cannot collide.
5199fn trait_method_name(ty: &str, tr: &str, m: &str) -> String {
5200 format!("rs{}_{}_{}", tr, ty, m)
5201}
5202
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 18h ago5203fn is_fmt_trait(t: &str) -> bool {
5204 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
5205}
5206
5207/// The prelude proc a formatting trait's output is produced by.
5208fn fmt_proc(t: &str) -> &'static str {
5209 match t {
5210 "Display" => "rsDisplay",
5211 "Debug" => "rsDebug",
5212 "LowerHex" => "rsLowerHex",
5213 "UpperHex" => "rsUpperHex",
5214 "Binary" => "rsBinary",
5215 _ => "rsOctal",
5216 }
5217}
5218
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 17h ago5219/// Whether an expression is an iterator-producing chain rather than a value.
5220fn is_iterator_expr(e: &Expr) -> bool {
5221 match e {
5222 Expr::MethodCall(m) => matches!(
5223 m.method.to_string().as_str(),
5224 "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact"
5225 | "chunks_exact_mut" | "windows"
5226 ),
5227 Expr::Paren(p) => is_iterator_expr(&p.expr),
5228 _ => false,
5229 }
5230}
5231
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 17h ago5232/// Whether an expression denotes a place -- a variable, a field, or an index
5233/// or slice of one -- and so may be re-evaluated with no side effect.
5234fn is_pure_place(e: &Expr) -> bool {
5235 match e {
5236 Expr::Path(_) => true,
5237 Expr::Field(f) => is_pure_place(&f.base),
5238 Expr::Index(i) => {
5239 is_pure_place(&i.expr)
5240 && match &*i.index {
5241 Expr::Range(r) => {
5242 r.start.as_deref().map_or(true, is_pure_place)
5243 && r.end.as_deref().map_or(true, is_pure_place)
5244 }
5245 other => is_pure_place(other),
5246 }
5247 }
5248 Expr::Lit(_) => true,
5249 Expr::Reference(r) => is_pure_place(&r.expr),
5250 Expr::Paren(p) => is_pure_place(&p.expr),
5251 Expr::Group(g) => is_pure_place(&g.expr),
5252 // Arithmetic on places is still side-effect free, so a bound like
5253 // `..want - 1` does not stop the binding being an alias.
5254 Expr::Binary(b) if !is_compound(&b.op) => {
5255 is_pure_place(&b.left) && is_pure_place(&b.right)
5256 }
5257 Expr::Unary(u) => is_pure_place(&u.expr),
5258 Expr::Cast(c) => is_pure_place(&c.expr),
5259 _ => false,
5260 }
5261}
5262
5263/// Whether an expression is a `&mut` borrow, directly or through parens.
5264fn is_mut_borrow(e: &Expr) -> bool {
5265 match e {
5266 Expr::Reference(r) => r.mutability.is_some(),
5267 Expr::Paren(p) => is_mut_borrow(&p.expr),
5268 Expr::Group(g) => is_mut_borrow(&g.expr),
5269 _ => false,
5270 }
5271}
5272
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago5273fn takes_self(sig: &syn::Signature) -> bool {
5274 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
5275}
5276
5277fn path_name(p: &syn::Path) -> String {
5278 p.segments
5279 .last()
5280 .map(|s| s.ident.to_string())
5281 .unwrap_or_default()
5282}
5283
5284fn is_compound(op: &BinOp) -> bool {
5285 matches!(
5286 op,
5287 BinOp::AddAssign(_)
5288 | BinOp::SubAssign(_)
5289 | BinOp::MulAssign(_)
5290 | BinOp::DivAssign(_)
5291 | BinOp::RemAssign(_)
5292 | BinOp::BitAndAssign(_)
5293 | BinOp::BitOrAssign(_)
5294 | BinOp::BitXorAssign(_)
5295 | BinOp::ShlAssign(_)
5296 | BinOp::ShrAssign(_)
5297 )
5298}
5299
5300/// The Nim literal suffix for an integer type (`5'i32`).
5301fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
5302 let Nim::Prim(p) = t else {
5303 return Err("not a primitive integer".into());
5304 };
5305 Ok(match p.as_str() {
5306 "int8" => "i8",
5307 "int16" => "i16",
5308 "int32" => "i32",
5309 "int64" => "i64",
5310 "int" => "i",
5311 "uint8" => "u8",
5312 "uint16" => "u16",
5313 "uint32" => "u32",
5314 "uint64" => "u64",
5315 "uint" => "u",
5316 other => return Err(format!("no Nim literal suffix for `{other}`")),
5317 })
5318}
5319
5320/// The unsigned integer type of the same width, used to spell `wrapping_*`.
5321fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
5322 let Nim::Prim(p) = t else {
5323 return Err("not a primitive integer".into());
5324 };
5325 Ok(match p.as_str() {
5326 "int8" => "uint8",
5327 "int16" => "uint16",
5328 "int32" => "uint32",
5329 "int64" => "uint64",
5330 "int" => "uint",
5331 other => return Err(format!("`{other}` has no unsigned peer")),
5332 })
5333}
5334
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 19h ago5335fn quote_meta(m: &syn::Meta) -> String {
5336 match m {
5337 syn::Meta::Path(p) => path_name(p),
5338 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
5339 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
5340 }
5341}
5342
5343fn item_attrs(i: &Item) -> &[syn::Attribute] {
5344 match i {
5345 Item::Fn(f) => &f.attrs,
5346 Item::Struct(s) => &s.attrs,
5347 Item::Enum(e) => &e.attrs,
5348 Item::Impl(x) => &x.attrs,
5349 Item::Const(c) => &c.attrs,
5350 Item::Type(t) => &t.attrs,
5351 Item::Mod(m) => &m.attrs,
5352 Item::Use(u) => &u.attrs,
5353 Item::ExternCrate(e) => &e.attrs,
5354 Item::Static(s) => &s.attrs,
5355 _ => &[],
5356 }
5357}
5358
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 19h ago5359fn item_kind(i: &Item) -> &'static str {
5360 match i {
5361 Item::Trait(_) => "`trait`",
5362 Item::Static(_) => "`static`",
5363 Item::Macro(_) => "macro definition",
5364 Item::Union(_) => "`union`",
5365 Item::ForeignMod(_) => "`extern` block",
5366 _ => "item",
5367 }
5368}
5369
5370fn expr_kind(e: &Expr) -> &'static str {
5371 match e {
5372 Expr::Async(_) => "`async` block",
5373 Expr::Await(_) => "`.await`",
5374 Expr::Try(_) => "`?`",
5375 Expr::Range(_) => "range",
5376 Expr::Match(_) => "`match` (only statement position is implemented)",
5377 Expr::Let(_) => "`let` expression",
5378 Expr::Unsafe(_) => "`unsafe` block",
5379 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
5380 _ => "expression",
5381 }
5382}