nandi/rustnimpublic Fork 0
8354895601e0a0fa3e1962b9ef728d00050ffb97
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 · 5005 lines · 209.8 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h 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 8h 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 9h 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 8h 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 8h 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 8h 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 8h 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 8h 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 8h 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 8h 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 9h ago103 }
104}
105
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h 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 7h 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 8h ago117}
118
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h 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 8h 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 9h 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 8h 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 9h ago144 }
145 fn untyped(code: impl Into<String>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago146 Val::new(code, None)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h 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 6h 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 9h ago156}
157
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago158/// One variant of a Rust enum.
159#[derive(Clone)]
160struct Variant {
161 name: String,
162 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
163 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
164 /// Nim requires the branches of a variant object to have distinct fields.
165 fields: Vec<(String, Nim)>,
166}
167
168#[derive(Clone)]
169struct EnumDef {
170 name: String,
171 /// True when every variant is a unit variant, which Nim represents as a
172 /// plain `enum` rather than an object variant.
173 simple: bool,
174 variants: Vec<Variant>,
175}
176
177impl EnumDef {
178 fn kind_ident(&self, v: &str) -> String {
179 format!("k{}{}", self.name, v)
180 }
181 fn ctor_ident(&self, v: &str) -> String {
182 format!("{}{}", self.name, v)
183 }
184 fn get(&self, v: &str) -> Option<&Variant> {
185 self.variants.iter().find(|x| x.name == v)
186 }
187}
188
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago189pub struct Lowerer {
190 out: String,
191 indent: usize,
192 scopes: Vec<HashMap<String, Nim>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago193 /// Names introduced by a `for` pattern that stand for an lvalue or a
194 /// window into a container, rather than for a variable of their own.
195 alias_scopes: Vec<HashMap<String, Alias>>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago196 /// `(module, name) -> signature`. Rust keeps `lower::decode` and
197 /// `mixed::decode` apart by module; flattening into one Nim module would
198 /// merge them, so the module is part of the key and of the emitted name.
199 fns: HashMap<(String, String), Sig>,
200 /// Module being lowered: the file stem, or empty for the crate root.
201 cur_mod: String,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago202 /// The type of the `impl` block being lowered, which `Self` names.
203 self_ty: Option<Nim>,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago204 /// Type parameters of the enclosing `impl`, which its methods share.
205 impl_generics: Vec<String>,
206 /// Type parameters of the proc being lowered, impl's included.
207 fn_generics: Vec<String>,
208 /// Type parameters declared by each generic struct or enum.
209 type_generics: HashMap<String, Vec<String>>,
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago210 /// `(type, name) -> type` for `type Item = ..;` inside an `impl`. Rust
211 /// writes those as `Self::Item`, which has to resolve before any
212 /// signature mentioning it is mapped.
213 assoc: HashMap<(String, String), Nim>,
214 /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`.
215 assoc_consts: HashMap<(String, String), (String, Nim)>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago216 /// `use` brings a name into scope from another module. Flattening loses
217 /// the module structure, so the mapping is recorded and consulted when a
218 /// bare call is resolved.
219 use_map: HashMap<String, String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago220 /// struct name -> (field, type)
221 structs: HashMap<String, Vec<(String, Nim)>>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago222 enums: HashMap<String, EnumDef>,
223 /// variant name -> enums declaring it. A variant named by more than one
224 /// enum must be written qualified, or it is rejected as ambiguous.
225 variant_owner: HashMap<String, Vec<String>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago226 /// `(receiver type, method) -> signature`. Keyed by type because two
227 /// types may define the same method name, and Nim tells them apart by
228 /// overload resolution on the first parameter.
229 methods: HashMap<(String, String), Sig>,
230 /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
231 /// on a user type can be checked rather than assumed.
232 fmt_impls: HashMap<(String, String), ()>,
233 /// `(from, to)` conversions declared by `impl From<A> for B`.
234 from_impls: HashMap<(String, String), String>,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago235 /// Operator traits implemented for a type, so `a += b` on a user type can
236 /// be dispatched to the impl rather than to Nim's built-in operator.
237 op_impls: HashMap<(String, String), ()>,
238 /// `(type, method) -> nim name`, for calls written as `Type::method(..)`.
239 statics: HashMap<(String, String), String>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago240 /// Forward declarations, emitted between the type definitions and the
241 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
242 /// proc is declared up front rather than the input being reordered --
243 /// which would not work for mutual recursion anyway.
244 forwards: Vec<String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago245 /// Element type a `vec![..]` should build, from the binding's annotation.
246 vec_expect: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago247 /// While lowering a formatting impl: the `Formatter` parameter's name.
248 /// Writes through it produce the proc's string result.
249 fmt_param: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago250 /// `type X<T> = ...`, expanded before any type is mapped.
251 aliases: HashMap<String, (Vec<String>, syn::Type)>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago252 /// Module names supplied as separate input files. A `mod x;` naming one
253 /// of these is satisfied by that file having been passed in.
254 pub modules: Vec<String>,
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 7h ago255 /// How many items were actually translated. If this is zero the input
256 /// produced nothing but the prelude, and reporting success for that is
257 /// the precise failure this project exists to avoid -- see `findings/`.
258 emitted: usize,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago259 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
260 /// evaluated against these exactly as rustc would, so an item that is
261 /// dropped here is genuinely not part of the program being compiled.
262 pub features: Vec<String>,
263 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago264 /// Return type of the proc being lowered, so `return e` and a trailing
265 /// expression can type their literals the way Rust's inference would.
266 ret: Option<Nim>,
267 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
268 /// statement must assign their value to.
269 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago270 /// Set while lowering a `while` condition, which Nim re-evaluates each
271 /// iteration and so cannot have statements hoisted out of it.
272 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago273 tmp: usize,
274}
275
276impl Lowerer {
277 pub fn new() -> Self {
278 Lowerer {
279 out: String::new(),
280 indent: 0,
281 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago282 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago283 fns: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago284 cur_mod: String::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago285 self_ty: None,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago286 impl_generics: Vec::new(),
287 fn_generics: Vec::new(),
288 type_generics: HashMap::new(),
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago289 assoc: HashMap::new(),
290 assoc_consts: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago291 use_map: HashMap::new(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago292 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago293 enums: HashMap::new(),
294 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago295 methods: HashMap::new(),
296 fmt_impls: HashMap::new(),
297 from_impls: HashMap::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago298 op_impls: HashMap::new(),
299 statics: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago300 fmt_param: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago301 vec_expect: None,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago302 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago303 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago304 modules: Vec::new(),
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 7h ago305 emitted: 0,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago306 features: Vec::new(),
307 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago308 ret: None,
309 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago310 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago311 tmp: 0,
312 }
313 }
314
315 // ------------------------------------------------------------ emission
316
317 fn line(&mut self, s: &str) {
318 for _ in 0..self.indent {
319 self.out.push_str(" ");
320 }
321 self.out.push_str(s);
322 self.out.push('\n');
323 }
324
325 fn blank(&mut self) {
326 self.out.push('\n');
327 }
328
329 fn fresh(&mut self, hint: &str) -> String {
330 self.tmp += 1;
331 format!("rsTmp{}{}", hint, self.tmp)
332 }
333
334 // --------------------------------------------------------------- scope
335
336 fn push_scope(&mut self) {
337 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago338 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago339 }
340 fn pop_scope(&mut self) {
341 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago342 self.alias_scopes.pop();
343 }
344 fn bind_alias(&mut self, name: &str, a: Alias) {
345 self.alias_scopes
346 .last_mut()
347 .unwrap()
348 .insert(name.to_string(), a);
349 }
350 fn lookup_alias(&self, name: &str) -> Option<Alias> {
351 self.alias_scopes
352 .iter()
353 .rev()
354 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago355 }
356 fn bind(&mut self, name: &str, t: Nim) {
357 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
358 }
359 fn lookup(&self, name: &str) -> Option<Nim> {
360 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
361 }
362
363 // ---------------------------------------------------------------- file
364
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago365 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 9h ago366 self.out.push_str(include_str!("prelude.nim"));
367 self.blank();
368
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago369 // Pass 0: type aliases. A signature in one file may use an alias
370 // declared in another, and inputs are given in whatever order suits
371 // 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 8h ago372 for (m, f) in files {
373 self.cur_mod = m.clone();
374 for item in &f.items {
375 self.collect_aliases(item)?;
376 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago377 }
378
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago379 // Pass 1: signatures and struct shapes, so that a call can be typed
380 // regardless of declaration order (Rust has no forward declarations).
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago381 for (m, f) in files {
382 self.cur_mod = m.clone();
383 for item in &f.items {
384 self.collect(item)?;
385 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago386 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago387 // Pass 2: type definitions, which every signature may mention.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago388 for (m, f) in files {
389 self.cur_mod = m.clone();
390 for item in &f.items {
391 self.item_types(item)?;
392 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago393 }
394
395 // Pass 3: forward declarations. Rust imposes no declaration order and
396 // Nim does, so everything is declared before any body is emitted;
397 // reordering the input would not handle mutual recursion anyway.
398 if !self.forwards.is_empty() {
399 for f in self.forwards.clone() {
400 self.line(&f);
401 }
402 self.blank();
403 }
404
405 // Pass 4: bodies.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago406 for (m, f) in files {
407 self.cur_mod = m.clone();
408 for item in &f.items {
409 self.item(item)?;
410 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago411 }
412
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 7h ago413 // An input that translates to nothing is a failure, however plausible
414 // the output file looks. The prelude alone is not a translation.
415 if self.emitted == 0 {
416 return Err(format!(
417 "nothing was translated: the input has no items this lowering \
418 emits{}. Writing a file containing only the prelude would \
419 report success for work that was not done",
420 if self.dropped_by_cfg > 0 {
421 format!(
422 " ({} item(s) were dropped by `#[cfg]`; enable them with \
423 `--cfg feature=<name>`)",
424 self.dropped_by_cfg
425 )
426 } else {
427 String::new()
428 }
429 ));
430 }
431
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago432 if self.fns.contains_key(&(String::new(), "main".to_string())) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago433 self.blank();
434 self.line("when isMainModule:");
435 self.indent += 1;
436 self.line("try:");
437 self.line(" main()");
438 // Rust's panic exits 101 with a message on stderr. Nim's Defects
439 // exit 1. Mapping them here is what keeps the differential runner's
440 // exit-status comparison meaningful for panicking programs.
441 self.line("except RustPanic as e:");
442 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
443 self.line(" quit(101)");
444 self.line("except Defect as e:");
445 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
446 self.line(" quit(101)");
447 self.indent -= 1;
448 }
449 Ok(std::mem::take(&mut self.out))
450 }
451
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago452 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
453 if !self.cfg_keeps(item_attrs(item))? {
454 return Ok(());
455 }
456 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago457 Item::Use(u) => self.collect_use(&u.tree, &[]),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago458 Item::Type(t) => {
459 let params: Vec<String> = t
460 .generics
461 .params
462 .iter()
463 .filter_map(|g| match g {
464 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
465 _ => None,
466 })
467 .collect();
468 self.aliases
469 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
470 }
471 Item::Mod(m) if m.content.is_some() => {
472 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
473 for i in &items {
474 self.collect_aliases(i)?;
475 }
476 }
477 _ => {}
478 }
479 Ok(())
480 }
481
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago482 /// Record what a `use` brings into scope, as `name -> module`.
483 fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
484 use syn::UseTree;
485 match t {
486 UseTree::Path(p) => {
487 let mut pre = prefix.to_vec();
488 pre.push(p.ident.to_string());
489 self.collect_use(&p.tree, &pre);
490 }
491 UseTree::Group(g) => {
492 for t in &g.items {
493 self.collect_use(t, prefix);
494 }
495 }
496 UseTree::Name(n) => {
497 let m = module_of(prefix);
498 self.use_map.insert(n.ident.to_string(), m);
499 }
500 UseTree::Rename(r) => {
501 let m = module_of(prefix);
502 self.use_map.insert(r.rename.to_string(), m);
503 }
504 // A glob brings in an unknown set of names; resolution falls back
505 // to the current module and the root, as it would without it.
506 UseTree::Glob(_) => {}
507 }
508 }
509
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago510 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago511 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
512 // silently would change what the program does; picking a feature set
513 // on the user's behalf would be a guess. So it is reported, except on
514 // items that carry no runtime meaning here anyway.
515 if !self.cfg_keeps(item_attrs(item))? {
516 self.dropped_by_cfg += 1;
517 return Ok(());
518 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago519 match item {
520 Item::Fn(f) => {
521 let (params, ret) = self.signature(&f.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago522 let gen_names = Self::generics_of(&f.sig.generics);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago523 let name = f.sig.ident.to_string();
524 let nim = self.fn_name(&self.cur_mod, &name);
525 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
526 self.fns
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago527 .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 9h ago528 }
529 Item::Struct(s) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago530 if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
531 return Err(format!(
532 "`struct {}` has a const generic parameter, which Nim has \
533 no equivalent for",
534 s.ident
535 ));
536 }
537 let g = Self::generics_of(&s.generics);
538 // The parameters must be in scope while the field types are
539 // mapped, so that `T` resolves to itself rather than to an
540 // unknown named type.
541 self.type_generics.insert(s.ident.to_string(), g);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago542 let mut fields = Vec::new();
543 for (i, f) in s.fields.iter().enumerate() {
544 let name = match &f.ident {
545 Some(id) => id.to_string(),
546 None => format!("f{i}"), // tuple struct
547 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago548 // A field of `&[T]` / `&str` type is a borrow, and Nim's
549 // view types allow it as an object field, so it stays a
550 // view rather than being copied into a `seq`.
551 let t = self.map_ty(&f.ty)?;
552 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
553 fields.push((name, t));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago554 }
555 self.structs.insert(s.ident.to_string(), fields);
556 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago557 Item::Mod(m) if m.content.is_some() => {
558 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
559 for i in &items {
560 self.collect(i)?;
561 }
562 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago563 Item::Type(t) => {
564 let params: Vec<String> = t
565 .generics
566 .params
567 .iter()
568 .filter_map(|g| match g {
569 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
570 _ => None,
571 })
572 .collect();
573 self.aliases
574 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
575 }
576 Item::Enum(e) => {
577 let name = e.ident.to_string();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago578 if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
579 return Err(format!(
580 "`enum {name}` has a const generic parameter, which Nim \
581 has no equivalent for"
582 ));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago583 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago584 self.type_generics
585 .insert(name.clone(), Self::generics_of(&e.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago586 let mut variants = Vec::new();
587 for v in &e.variants {
588 let vname = v.ident.to_string();
589 if v.discriminant.is_some() {
590 return Err(format!(
591 "`{name}::{vname}` has an explicit discriminant; Rust's \
592 `as` on such an enum has a value this lowering does not \
593 yet preserve"
594 ));
595 }
596 let mut fields = Vec::new();
597 for (i, f) in v.fields.iter().enumerate() {
598 // Nim requires the branches of a variant object to have
599 // distinct field names, so each is prefixed.
600 let fname = match &f.ident {
601 Some(id) => format!("{vname}_{id}"),
602 None => format!("{vname}_f{i}"),
603 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago604 let t = self.map_ty(&f.ty)?;
605 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
606 fields.push((fname, t));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago607 }
608 variants.push(Variant { name: vname, fields });
609 }
610 let simple = variants.iter().all(|v| v.fields.is_empty());
611 for v in &variants {
612 self.variant_owner
613 .entry(v.name.clone())
614 .or_default()
615 .push(name.clone());
616 }
617 self.enums.insert(
618 name.clone(),
619 EnumDef { name, simple, variants },
620 );
621 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago622 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago623 let outer_g =
624 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 8h ago625 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago626 let outer_self = self.self_ty.replace(self_ty.clone());
627 let r = self.collect_impl(im, &self_ty);
628 self.self_ty = outer_self;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago629 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago630 return r;
631 }
632 _ => {}
633 }
634 Ok(())
635 }
636
637 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
638 {
639 let self_ty = self_ty.clone();
640 let tyname = type_name(&self_ty);
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago641 // Associated types first: a signature in the same block may name
642 // one, and it has to resolve by the time that signature is mapped.
643 for it in &im.items {
644 if let syn::ImplItem::Type(t) = it {
645 let v = self.map_ty(&t.ty)?;
646 self.assoc.insert((tyname.clone(), t.ident.to_string()), v);
647 }
648 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago649 if let Some((path, _)) = &im.trait_ {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago650 let tr = path_name(path);
651 if im.items.is_empty() {
652 // A marker trait with no items. We do not model trait
653 // resolution at all, so it generates nothing; any use
654 // that actually needed the trait (a `dyn`, a bound) is
655 // rejected where it appears.
656 return Ok(());
657 }
658 if is_fmt_trait(&tr) {
659 self.forwards.push(format!(
660 "proc {}*(self: {}): string",
661 fmt_proc(&tr),
662 self_ty.render()
663 ));
664 self.fmt_impls.insert((tyname, tr), ());
665 return Ok(());
666 }
667 if tr == "From" {
668 let syn::ImplItem::Fn(m) = &im.items[0] else {
669 return Err("`impl From` must contain `fn from`".into());
670 };
671 let (params, _) = self.signature(&m.sig)?;
672 let src = params
673 .first()
674 .ok_or("`fn from` takes one argument")?
675 .clone();
676 let name = format!("rsFrom{}{}", tyname, type_name(&src));
677 self.forwards.push(self.head_of(&name, &m.sig, None)?);
678 self.from_impls
679 .insert((type_name(&src), tyname), name);
680 return Ok(());
681 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago682 // Any other trait: its methods are emitted as procs on
683 // the type, named after the trait so two traits declaring
684 // the same method name do not collide. The *trait* is not
685 // modelled -- no dynamic dispatch, no bounds -- and a use
686 // that needs it is rejected where it appears.
687 if let Some(op) = operator_trait(&tr) {
688 self.op_impls.insert((tyname.clone(), op.to_string()), ());
689 }
690 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago691 // Already recorded above; a const is emitted with the
692 // bodies.
693 if matches!(it, syn::ImplItem::Type(_) | syn::ImplItem::Const(_)) {
694 continue;
695 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago696 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago697 return Err(format!(
698 "unsupported item in `impl {tr}`: only `fn`, \
699 `type` and `const` are implemented"
700 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago701 };
702 let mname = m.sig.ident.to_string();
703 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago704 let mut gen_names = self.impl_generics.clone();
705 gen_names.extend(Self::generics_of(&m.sig.generics));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago706 let recv = if takes_self(&m.sig) {
707 params.insert(0, self_ty.clone());
708 Some(self_ty.clone())
709 } else {
710 None
711 };
712 let nim = trait_method_name(&tyname, &tr, &mname);
713 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
714 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago715 .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 7h ago716 self.statics.insert((tyname.clone(), mname), nim);
717 }
718 return Ok(());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago719 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago720 for it in &im.items {
721 if let syn::ImplItem::Fn(m) = it {
722 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago723 let mut gen_names = self.impl_generics.clone();
724 gen_names.extend(Self::generics_of(&m.sig.generics));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago725 if takes_self(&m.sig) {
726 params.insert(0, self_ty.clone());
727 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago728 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 7h ago729 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
730 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 8h ago731 self.forwards.push(head);
732 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago733 .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 7h ago734 self.statics
735 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago736 }
737 }
738 }
739 Ok(())
740 }
741
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago742 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
743 ///
744 /// This is evaluation, not approximation: rustc does the same thing, and
745 /// an item whose predicate is false is not part of the compiled program.
746 /// A predicate that cannot be evaluated is reported rather than assumed.
747 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
748 for a in attrs {
749 if a.path().is_ident("cfg") {
750 let pred: syn::Meta = a
751 .parse_args()
752 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
753 if !self.cfg_eval(&pred)? {
754 return Ok(false);
755 }
756 }
757 }
758 Ok(true)
759 }
760
761 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
762 match m {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago763 // Bare flags whose value is determined by the profile this project
764 // models: a normal (non-`--test`) debug build, not a docs build.
765 // Anything platform-specific stays rejected, since we would be
766 // picking a target on the user's behalf.
767 syn::Meta::Path(p) if p.is_ident("test") => Ok(false),
768 syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true),
769 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 6h ago770 syn::Meta::Path(p) if p.is_ident("doctest") || p.is_ident("miri") => Ok(false),
771 // Host facts. The generated Nim is compiled for this machine, so
772 // these are known rather than chosen. See DESIGN.md item 10: it
773 // does make the output host-shaped.
774 syn::Meta::Path(p) if p.is_ident("unix") => Ok(cfg!(unix)),
775 syn::Meta::Path(p) if p.is_ident("windows") => Ok(cfg!(windows)),
776 syn::Meta::NameValue(nv)
777 if nv.path.is_ident("target_os")
778 || nv.path.is_ident("target_arch")
779 || nv.path.is_ident("target_family")
780 || nv.path.is_ident("target_vendor") =>
781 {
782 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
783 return Err("this `cfg` key expects a string".into());
784 };
785 let key = nv.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
786 Ok(s.value()
787 == match key.as_str() {
788 "target_os" => std::env::consts::OS,
789 "target_arch" => std::env::consts::ARCH,
790 "target_family" => std::env::consts::FAMILY,
791 _ => "unknown",
792 })
793 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago794 // The generated Nim is compiled for the same machine, so the
795 // target's word size and endianness are known rather than
796 // guessed. This does mean the output is host-shaped: a crate that
797 // branches on pointer width has had that branch decided here.
798 syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => {
799 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
800 return Err("`target_pointer_width = ..` expects a string".into());
801 };
802 Ok(s.value() == (usize::BITS).to_string())
803 }
804 syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => {
805 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
806 return Err("`target_endian = ..` expects a string".into());
807 };
808 Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" })
809 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago810 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
811 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
812 return Err("`feature = ..` expects a string".into());
813 };
814 Ok(self.features.iter().any(|f| *f == s.value()))
815 }
816 syn::Meta::List(l) if l.path.is_ident("not") => {
817 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
818 Ok(!self.cfg_eval(&inner)?)
819 }
820 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
821 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
822 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
823 .map_err(|e| e.to_string())?;
824 let all = l.path.is_ident("all");
825 let mut acc = all;
826 for i in &items {
827 let v = self.cfg_eval(i)?;
828 acc = if all { acc && v } else { acc || v };
829 }
830 Ok(acc)
831 }
832 other => Err(format!(
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 6h ago833 "`#[cfg({})]` is not a predicate rustnim can evaluate. \
834 Features (`--cfg feature=..`), host facts (`unix`, `windows`, \
835 `target_os`, `target_arch`, `target_family`, \
836 `target_pointer_width`, `target_endian`), `doc`/`doctest`/\
837 `miri`, and `not`/`all`/`any` over those are. A custom or \
838 build-script `cfg` has no value we could know",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago839 quote_meta(other)
840 )),
841 }
842 }
843
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago844 /// 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 8h ago845 /// lowering goes through here rather than calling `ty::map` directly, so
846 /// an alias cannot be missed in one position and honoured in another.
847 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 6h ago848 // `Self::Item` names an associated type of the enclosing `impl`.
849 if let syn::Type::Path(p) = t {
850 let segs: Vec<String> =
851 p.path.segments.iter().map(|s| s.ident.to_string()).collect();
852 if segs.len() == 2 {
853 let owner = if segs[0] == "Self" {
854 self.self_ty.as_ref().map(type_name)
855 } else {
856 Some(segs[0].clone())
857 };
858 if let Some(o) = owner {
859 if let Some(a) = self.assoc.get(&(o, segs[1].clone())) {
860 return Ok(a.clone());
861 }
862 }
863 }
864 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago865 let n = ty::map(&self.expand(t, 0)?)?;
866 Ok(self.subst_self(n))
867 }
868
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago869 /// Substitute a generic type's parameters with the arguments the use site
870 /// supplies: a field of `Holder<T>` read through a `Holder<i32>` is `i32`.
871 fn subst_type_args(&self, name: &str, used_as: &Nim, field: Nim) -> Nim {
872 let Some(params) = self.type_generics.get(name) else { return field };
873 if params.is_empty() {
874 return field;
875 }
876 let Nim::Named(n, args) = used_as else { return field };
877 if n != name || args.len() != params.len() {
878 return field;
879 }
880 let map: HashMap<String, Nim> =
881 params.iter().cloned().zip(args.iter().cloned()).collect();
882 Self::subst(&field, &map)
883 }
884
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago885 /// `Self` inside an `impl` block names the type being implemented.
886 fn subst_self(&self, t: Nim) -> Nim {
887 let Some(me) = &self.self_ty else { return t };
888 match t {
889 Nim::Named(n, _) if n == "Self" => me.clone(),
890 Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))),
891 Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))),
892 Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))),
893 Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))),
894 Nim::Named(n, a) => {
895 Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect())
896 }
897 other => other,
898 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago899 }
900
901 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
902 if depth > 16 {
903 return Err("type alias expansion did not terminate; is it cyclic?".into());
904 }
905 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
906 // Only an unqualified name can be one of this file's aliases.
907 // `fmt::Result` and `core::result::Result` are different types that
908 // merely end in the same segment.
909 if p.path.segments.len() != 1 {
910 return Ok(t.clone());
911 }
912 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
913 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
914 return Ok(t.clone());
915 };
916 let args: Vec<syn::Type> = match &seg.arguments {
917 syn::PathArguments::AngleBracketed(a) => a
918 .args
919 .iter()
920 .filter_map(|g| match g {
921 GenericArgument::Type(t) => Some(t.clone()),
922 _ => None,
923 })
924 .collect(),
925 _ => vec![],
926 };
927 if args.len() != params.len() {
928 // Flattening several files into one module can bring a crate's own
929 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
930 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
931 // module; here they are told apart by arity, and a use that fits
932 // neither is left for `ty::map` to report.
933 return Ok(t.clone());
934 }
935 self.expand(&substitute(target, params, &args), depth + 1)
936 }
937
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago938 /// The type parameters a generic item declares.
939 ///
940 /// Trait bounds and `where` clauses are dropped. Nim instantiates a
941 /// generic structurally: an operation the bound would have permitted
942 /// either exists for the instantiated type or is a compile error at the
943 /// instantiation site. So dropping a bound cannot make an accepted
944 /// program mean something different — it only makes rustnim accept some
945 /// programs rustc would have rejected, which does not matter when the
946 /// input is known-good Rust.
947 fn generics_of(g: &syn::Generics) -> Vec<String> {
948 g.params
949 .iter()
950 .filter_map(|p| match p {
951 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
952 _ => None,
953 })
954 .collect()
955 }
956
957 /// Bind a signature's type parameters by matching its declared parameter
958 /// types against the actual argument types, then substitute into `ret`.
959 ///
960 /// This is the small amount of inference a call site needs: Nim will
961 /// resolve the instantiation itself, but the *binding* still has to be
962 /// annotated with a concrete type, and `T` is not one.
963 fn instantiate(sig: &Sig, args: &[Option<Nim>]) -> Nim {
964 if sig.generics.is_empty() {
965 return sig.ret.clone();
966 }
967 let mut bound: HashMap<String, Nim> = HashMap::new();
968 for (decl, actual) in sig.params.iter().zip(args) {
969 if let Some(a) = actual {
970 Self::unify(decl, a, &sig.generics, &mut bound);
971 }
972 }
973 Self::subst(&sig.ret, &bound)
974 }
975
976 fn unify(decl: &Nim, actual: &Nim, params: &[String], out: &mut HashMap<String, Nim>) {
977 match (decl, actual) {
978 (Nim::Named(n, da), _) if params.iter().any(|p| p == n) && da.is_empty() => {
979 out.entry(n.clone()).or_insert_with(|| actual.clone());
980 }
981 (Nim::Named(_, da), Nim::Named(_, aa)) if da.len() == aa.len() => {
982 for (d, a) in da.iter().zip(aa) {
983 Self::unify(d, a, params, out);
984 }
985 }
986 (Nim::Seq(d), Nim::Seq(a))
987 | (Nim::OpenArray(d), Nim::OpenArray(a))
988 | (Nim::Seq(d), Nim::OpenArray(a))
989 | (Nim::OpenArray(d), Nim::Seq(a))
990 | (Nim::Var(d), Nim::Var(a))
991 | (Nim::Array(_, d), Nim::Array(_, a)) => Self::unify(d, a, params, out),
992 (Nim::Var(d), a) => Self::unify(d, a, params, out),
993 (d, Nim::Var(a)) => Self::unify(d, a, params, out),
994 (Nim::Tuple(d), Nim::Tuple(a)) if d.len() == a.len() => {
995 for (d, a) in d.iter().zip(a) {
996 Self::unify(d, a, params, out);
997 }
998 }
999 _ => {}
1000 }
1001 }
1002
1003 fn subst(t: &Nim, m: &HashMap<String, Nim>) -> Nim {
1004 match t {
1005 Nim::Named(n, a) if a.is_empty() => m.get(n).cloned().unwrap_or_else(|| t.clone()),
1006 Nim::Named(n, a) => {
1007 Nim::Named(n.clone(), a.iter().map(|x| Self::subst(x, m)).collect())
1008 }
1009 Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))),
1010 Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))),
1011 Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))),
1012 Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))),
1013 Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()),
1014 other => other.clone(),
1015 }
1016 }
1017
1018 /// Whether a type mentions a type parameter that is in scope here. Such a
1019 /// type cannot be used as a Nim annotation at an instantiation site: Nim
1020 /// infers it, and writing `T` would name something that is not bound.
1021 fn mentions_type_param(&self, t: &Nim) -> bool {
1022 match t {
1023 Nim::Named(n, a) => {
1024 self.fn_generics.iter().any(|g| g == n)
1025 || a.iter().any(|x| self.mentions_type_param(x))
1026 }
1027 Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) => {
1028 self.mentions_type_param(e)
1029 }
1030 Nim::Tuple(ts) => ts.iter().any(|x| self.mentions_type_param(x)),
1031 Nim::Proc(a, r) => {
1032 a.iter().any(|x| self.mentions_type_param(x)) || self.mentions_type_param(r)
1033 }
1034 _ => false,
1035 }
1036 }
1037
1038 /// `[T, U]`, or empty.
1039 fn gen_list(params: &[String]) -> String {
1040 if params.is_empty() {
1041 String::new()
1042 } else {
1043 format!("[{}]", params.join(", "))
1044 }
1045 }
1046
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago1047 /// The Nim name for a function, qualified by its module.
1048 fn fn_name(&self, module: &str, name: &str) -> String {
1049 if module.is_empty() {
1050 ident(name)
1051 } else {
1052 format!("{}_{}", module, ident(name))
1053 }
1054 }
1055
1056 /// Resolve a call path to the module and name it refers to: an explicit
1057 /// `mixed::decode`, then the current module, then the crate root.
1058 fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
1059 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1060 let last = segs.last()?.clone();
1061 if segs.len() >= 2 {
1062 let q = &segs[segs.len() - 2];
1063 if self.fns.contains_key(&(q.clone(), last.clone())) {
1064 return Some((q.clone(), last));
1065 }
1066 }
1067 let imported = self.use_map.get(&last).cloned();
1068 for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
1069 .into_iter()
1070 .flatten()
1071 {
1072 if self.fns.contains_key(&(m.clone(), last.clone())) {
1073 return Some((m, last));
1074 }
1075 }
1076 None
1077 }
1078
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1079 /// The Nim `proc` head for a Rust signature, used both for the forward
1080 /// declaration and for the definition, so the two cannot drift apart.
1081 fn head_of(
1082 &self,
1083 name: &str,
1084 sig: &syn::Signature,
1085 recv: Option<&Nim>,
1086 ) -> Result<String, String> {
1087 let (ptys, ret) = self.signature(sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1088 // A method inside `impl<T> Foo<T>` is generic in the impl's
1089 // parameters as well as its own.
1090 let mut params = self.impl_generics.clone();
1091 for g in Self::generics_of(&sig.generics) {
1092 if !params.contains(&g) {
1093 params.push(g);
1094 }
1095 }
1096 let gens = Self::gen_list(&params);
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1097 let mut parts = Vec::new();
1098 if let Some(self_ty) = recv {
1099 let mutable = matches!(
1100 sig.inputs.first(),
1101 Some(FnArg::Receiver(r))
1102 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1103 );
1104 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1105 parts.push(format!("self: {}", t.render()));
1106 }
1107 let typed: Vec<&syn::PatType> = sig
1108 .inputs
1109 .iter()
1110 .filter_map(|a| match a {
1111 FnArg::Typed(t) => Some(t),
1112 _ => None,
1113 })
1114 .collect();
1115 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
1116 let pname = match &*p.pat {
1117 Pat::Ident(id) => id.ident.to_string(),
1118 Pat::Wild(_) => format!("unused{}", parts.len()),
1119 _ => return Err("only plain identifier parameters are supported".into()),
1120 };
1121 let _ = i;
1122 parts.push(format!("{}: {}", ident(&pname), t.render()));
1123 }
1124 Ok(if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1125 format!("proc {}*{}({})", ident(name), gens, parts.join(", "))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1126 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1127 format!(
1128 "proc {}*{}({}): {}",
1129 ident(name),
1130 gens,
1131 parts.join(", "),
1132 ret.render()
1133 )
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1134 })
1135 }
1136
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1137 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 8h ago1138 // `unsafe fn` marks a contract for callers; it does not change what
1139 // the body means, so it lowers like any other proc.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1140 if sig.asyncness.is_some() {
1141 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
1142 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1143 // 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 6h ago1144 // they disappear. Type parameters become Nim generic parameters.
1145 // Const parameters have no Nim equivalent and are still rejected.
1146 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 9h ago1147 return Err(format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1148 "`fn {}` has a const generic parameter, which Nim has no \
1149 equivalent for",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1150 sig.ident
1151 ));
1152 }
1153 let mut params = Vec::new();
1154 for a in &sig.inputs {
1155 if let FnArg::Typed(t) = a {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1156 params.push(self.map_ty(&t.ty)?);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1157 }
1158 }
1159 let ret = match &sig.output {
1160 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1161 // A returned `&[T]` is a borrow of the caller's buffer, so it
1162 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
1163 // a `seq`, which `owned()` would do to both.
1164 ReturnType::Type(_, t) => {
1165 let n = self.map_ty(t)?;
1166 if returns_borrow(t) { n } else { n.owned() }
1167 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1168 };
1169 Ok((params, ret))
1170 }
1171
1172 // --------------------------------------------------------------- items
1173
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1174 /// Emit the type definitions only: they must precede every signature.
1175 fn item_types(&mut self, item: &Item) -> Result<(), String> {
1176 if !self.cfg_keeps(item_attrs(item))? {
1177 return Ok(());
1178 }
1179 match item {
1180 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
1181 Item::Mod(m) if m.content.is_some() => {
1182 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1183 for i in &items {
1184 self.item_types(i)?;
1185 }
1186 Ok(())
1187 }
1188 _ => Ok(()),
1189 }
1190 }
1191
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1192 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1193 if !self.cfg_keeps(item_attrs(item))? {
1194 return Ok(());
1195 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1196 // Types were emitted in their own pass.
1197 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
1198 return Ok(());
1199 }
1200 self.item_inner(item)
1201 }
1202
1203 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 7h ago1204 if !matches!(item, Item::Use(_) | Item::ExternCrate(_) | Item::Mod(_) | Item::Type(_)) {
1205 self.emitted += 1;
1206 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1207 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago1208 Item::Fn(f) => {
1209 let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
1210 self.func_named(&nim, &f.sig, &f.block, None)
1211 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1212 Item::Struct(s) => {
1213 let name = s.ident.to_string();
1214 let fields = self.structs[&name].clone();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1215 let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1216 self.line(&format!("type {}*{} = object", ident(&name), g));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1217 self.indent += 1;
1218 if fields.is_empty() {
1219 self.line("discard");
1220 }
1221 for (fname, fty) in &fields {
1222 self.line(&format!("{}*: {}", ident(fname), fty.render()));
1223 }
1224 self.indent -= 1;
1225 self.blank();
1226 Ok(())
1227 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1228 Item::Type(_) => Ok(()), // expanded at every use site
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago1229 Item::Trait(t) => {
1230 // We do not model trait resolution, so a declaration generates
1231 // nothing and a use that needed it is rejected where it
1232 // appears. A *default body*, though, is code: dropping it
1233 // would silently remove a method the impls inherit.
1234 for it in &t.items {
1235 if let syn::TraitItem::Fn(f) = it {
1236 if f.default.is_some() {
1237 return Err(format!(
1238 "`trait {}` gives `{}` a default body; trait \
1239 resolution is not modelled, so that body has no \
1240 impl to be emitted into and dropping it would \
1241 remove code",
1242 t.ident, f.sig.ident
1243 ));
1244 }
1245 }
1246 }
1247 Ok(())
1248 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1249 Item::Enum(e) => {
1250 let def = self.enums[&e.ident.to_string()].clone();
1251 self.emit_enum(&def);
1252 Ok(())
1253 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1254 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1255 let t = self.map_ty(&c.ty)?.owned();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1256 // The annotation types the initialiser, exactly as it does for
1257 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
1258 let v = self.expr_at(&c.expr, Some(&t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1259 self.bind(&c.ident.to_string(), t.clone());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1260 // Only a top-level const is exported; `*` on a local is not
1261 // Nim syntax.
1262 let star = if self.indent == 0 { "*" } else { "" };
1263 let line = format!(
1264 "const {}{}: {} = {}",
1265 ident(&c.ident.to_string()),
1266 star,
1267 t.render(),
1268 v.code
1269 );
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1270 self.line(&line);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1271 if self.indent == 0 {
1272 self.blank();
1273 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1274 Ok(())
1275 }
1276 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1277 let outer_g =
1278 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 8h ago1279 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1280 let outer = self.self_ty.replace(self_ty.clone());
1281 let r = self.impl_body(im, &self_ty);
1282 self.self_ty = outer;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1283 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1284 r
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1285 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1286 // `use` and `extern crate` are resolution directives with no Nim
1287 // analogue once everything is one module.
1288 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
1289 Item::Mod(m) if m.content.is_some() => {
1290 // An inline `mod` is flattened; Nim has no nested modules in a
1291 // single file.
1292 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1293 for i in &items {
1294 self.item(i)?;
1295 }
1296 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1297 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1298 Item::Mod(m) => {
1299 // Satisfied if that file was passed in too; everything is one
1300 // Nim module, so the declaration itself emits nothing.
1301 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
1302 return Ok(());
1303 }
1304 Err(format!(
1305 "`mod {};` refers to another file that was not passed to \
1306 rustnim; add it to the input list",
1307 m.ident
1308 ))
1309 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1310 other => Err(format!("unsupported item: {}", item_kind(other))),
1311 }
1312 }
1313
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1314 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
1315 fn none_of(&self, expect: Option<&Nim>) -> String {
1316 match expect {
1317 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
1318 format!("rsNone[{}]()", a[0].render())
1319 }
1320 _ => "rsNone()".to_string(),
1321 }
1322 }
1323
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1324 fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
1325 if let Some((path, _)) = &im.trait_ {
1326 let tr = path_name(path);
1327 if im.items.is_empty() {
1328 return Ok(());
1329 }
1330 if is_fmt_trait(&tr) {
1331 let syn::ImplItem::Fn(m) = &im.items[0] else {
1332 return Err(format!("unsupported item in `impl {tr}`"));
1333 };
1334 return self.fmt_impl(&tr, self_ty, &m.sig, &m.block);
1335 }
1336 if tr == "From" {
1337 let syn::ImplItem::Fn(m) = &im.items[0] else {
1338 return Err("`impl From` must contain `fn from`".into());
1339 };
1340 let name = {
1341 let (params, _) = self.signature(&m.sig)?;
1342 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
1343 self.from_impls[&(type_name(&src), type_name(self_ty))].clone()
1344 };
1345 return self.func_named(&name, &m.sig, &m.block, None);
1346 }
1347 let tyname = type_name(self_ty);
1348 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago1349 if let syn::ImplItem::Const(c) = it {
1350 self.assoc_const(&tyname, c)?;
1351 continue;
1352 }
1353 if matches!(it, syn::ImplItem::Type(_)) {
1354 continue; // a type binding emits nothing
1355 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1356 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago1357 return Err(format!(
1358 "unsupported item in `impl {tr}`: only `fn`, `type` and \
1359 `const` are implemented"
1360 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1361 };
1362 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1363 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
1364 self.func_named(&nim, &m.sig, &m.block, recv)?;
1365 }
1366 return Ok(());
1367 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago1368 let tyname = type_name(self_ty);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1369 for it in &im.items {
1370 match it {
1371 syn::ImplItem::Fn(m) => {
1372 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1373 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
1374 self.func_named(&nim, &m.sig, &m.block, recv)?;
1375 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago1376 syn::ImplItem::Type(_) => {}
1377 syn::ImplItem::Const(c) => self.assoc_const(&tyname, c)?,
1378 _ => {
1379 return Err("only `fn`, `type` and `const` items are supported \
1380 inside `impl`"
1381 .into())
1382 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1383 }
1384 }
1385 Ok(())
1386 }
1387
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago1388 /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant
1389 /// namespace, so it becomes a module-level const named for both.
1390 fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {
1391 let t = self.map_ty(&c.ty)?.owned();
1392 let v = self.expr_at(&c.expr, Some(&t))?;
1393 let name = format!("{}_{}", tyname, c.ident);
1394 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
1395 self.blank();
1396 self.assoc_consts
1397 .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));
1398 Ok(())
1399 }
1400
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1401 /// The type an operator impl declares for its right-hand operand.
1402 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
1403 let n = type_name(t.as_ref()?);
1404 let sig = self.methods.get(&(n, op_method(op).to_string()))?;
1405 sig.params.get(1).cloned().map(|t| t.unvar())
1406 }
1407
1408 /// The proc implementing `op` for a user type, if there is one.
1409 fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> {
1410 let n = type_name(t.as_ref()?);
1411 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
1412 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
1413 Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1))
1414 } else {
1415 None
1416 }
1417 }
1418
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1419 fn emit_enum(&mut self, def: &EnumDef) {
1420 let name = ident(&def.name);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1421 let g = Self::gen_list(
1422 self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
1423 );
1424 if def.simple && !g.is_empty() {
1425 // A Nim `enum` cannot take parameters; an all-unit generic enum
1426 // has no payload to be generic in anyway, so this would be a
1427 // parameter that never appears.
1428 // Fall through to the object-variant form instead.
1429 }
1430 if def.simple && g.is_empty() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1431 // Every variant is a unit variant, so a plain Nim enum is an exact
1432 // fit: it compares, orders and `case`-checks like Rust's.
1433 self.line(&format!("type {name}* = enum"));
1434 self.indent += 1;
1435 for v in &def.variants {
1436 self.line(&format!("{}", ident(&v.name)));
1437 }
1438 self.indent -= 1;
1439 self.blank();
1440 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1441 self.indent += 1;
1442 self.line("case x");
1443 for v in &def.variants {
1444 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
1445 }
1446 self.indent -= 1;
1447 self.blank();
1448 return;
1449 }
1450
1451 // A data-carrying enum is a Nim object variant: one discriminant enum
1452 // plus a branch per variant. This is the same shape the prelude uses
1453 // for `Option` and `Result`.
1454 self.line("type");
1455 self.indent += 1;
1456 self.line(&format!("{}Kind* = enum", name));
1457 self.indent += 1;
1458 for v in &def.variants {
1459 self.line(&def.kind_ident(&v.name));
1460 }
1461 self.indent -= 1;
1462 self.blank();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1463 self.line(&format!("{}*{} = object", name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1464 self.indent += 1;
1465 self.line(&format!("case kind*: {}Kind", name));
1466 for v in &def.variants {
1467 if v.fields.is_empty() {
1468 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
1469 } else {
1470 self.line(&format!("of {}:", def.kind_ident(&v.name)));
1471 self.indent += 1;
1472 for (f, t) in &v.fields {
1473 self.line(&format!("{}*: {}", ident(f), t.render()));
1474 }
1475 self.indent -= 1;
1476 }
1477 }
1478 self.indent -= 2;
1479 self.blank();
1480
1481 for v in &def.variants {
1482 let args: Vec<String> = v
1483 .fields
1484 .iter()
1485 .enumerate()
1486 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
1487 .collect();
1488 let inits: Vec<String> = v
1489 .fields
1490 .iter()
1491 .enumerate()
1492 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
1493 .collect();
1494 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1495 all.extend(inits);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1496 let ret = format!("{}{}", name, g);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1497 self.line(&format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1498 "proc {}*{}({}): {} = {}({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1499 def.ctor_ident(&v.name),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1500 g,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1501 args.join(", "),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1502 ret,
1503 ret,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1504 all.join(", ")
1505 ));
1506 }
1507 self.blank();
1508
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1509 self.line(&format!("proc rsDebug*{}(x: {}{}): string =", g, name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1510 self.indent += 1;
1511 self.line("case x.kind");
1512 for v in &def.variants {
1513 if v.fields.is_empty() {
1514 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
1515 } else {
1516 let parts: Vec<String> = v
1517 .fields
1518 .iter()
1519 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
1520 .collect();
1521 self.line(&format!(
1522 "of {}: \"{}(\" & {} & \")\"",
1523 def.kind_ident(&v.name),
1524 v.name,
1525 parts.join(" & \", \" & ")
1526 ));
1527 }
1528 }
1529 self.indent -= 1;
1530 self.blank();
1531 }
1532
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1533 /// The concrete type an enum variant constructs, and the `[T]` list to
1534 /// spell at the constructor when the enum is generic.
1535 fn variant_type(
1536 &self,
1537 def: &EnumDef,
1538 expect: Option<&Nim>,
1539 ) -> Result<(Nim, String), String> {
1540 let params = self.type_generics.get(&def.name).cloned().unwrap_or_default();
1541 if params.is_empty() {
1542 return Ok((Nim::Named(def.name.clone(), vec![]), String::new()));
1543 }
1544 match expect {
1545 Some(Nim::Named(n, a)) if *n == def.name && a.len() == params.len() => Ok((
1546 Nim::Named(def.name.clone(), a.clone()),
1547 format!("[{}]", a.iter().map(|t| t.render()).collect::<Vec<_>>().join(", ")),
1548 )),
1549 _ => Err(format!(
1550 "`{}` is a variant of a generic enum, and its type parameters \
1551 cannot be inferred here; annotate the binding or the return type",
1552 def.name
1553 )),
1554 }
1555 }
1556
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1557 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1558 /// to the enum that declares it.
1559 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
1560 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1561 let last = segs.last()?.clone();
1562 if segs.len() >= 2 {
1563 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
1564 if def.get(&last).is_some() {
1565 return Some((def.clone(), last));
1566 }
1567 }
1568 }
1569 // Unqualified: only unambiguous if exactly one enum declares it.
1570 match self.variant_owner.get(&last) {
1571 Some(owners) if owners.len() == 1 => {
1572 let def = self.enums.get(&owners[0])?;
1573 Some((def.clone(), last))
1574 }
1575 _ => None,
1576 }
1577 }
1578
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1579 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
1580 ///
1581 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
1582 /// observable result of `{}` is exactly the bytes written. So the method
1583 /// becomes `proc rsDisplay(self: T): string` and every write through the
1584 /// formatter produces that string. A `fmt` body that does anything else
1585 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
1586 /// because those affect the output and this model does not carry them.
1587 /// The window an expression names, if it names one.
1588 fn window_of(&self, e: &Expr) -> Option<Alias> {
1589 match e {
1590 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
1591 Some(a @ Alias::Window { .. }) => Some(a),
1592 _ => None,
1593 },
1594 Expr::Reference(r) => self.window_of(&r.expr),
1595 Expr::Paren(p) => self.window_of(&p.expr),
1596 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
1597 _ => None,
1598 }
1599 }
1600
1601 /// Whether an expression is the `Formatter` parameter of the formatting
1602 /// impl currently being lowered.
1603 fn is_fmt_param(&self, e: &Expr) -> bool {
1604 let Some(f) = &self.fmt_param else { return false };
1605 match e {
1606 Expr::Path(p) => path_name(&p.path) == *f,
1607 Expr::Reference(r) => self.is_fmt_param(&r.expr),
1608 Expr::Paren(p) => self.is_fmt_param(&p.expr),
1609 _ => false,
1610 }
1611 }
1612
1613 fn fmt_impl(
1614 &mut self,
1615 tr: &str,
1616 self_ty: &Nim,
1617 sig: &syn::Signature,
1618 body: &syn::Block,
1619 ) -> Result<(), String> {
1620 let proc_name = fmt_proc(tr);
1621 // The formatter is the parameter after `self`.
1622 let f = sig
1623 .inputs
1624 .iter()
1625 .filter_map(|a| match a {
1626 FnArg::Typed(t) => match &*t.pat {
1627 Pat::Ident(i) => Some(i.ident.to_string()),
1628 _ => None,
1629 },
1630 _ => None,
1631 })
1632 .next()
1633 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1634
1635 self.push_scope();
1636 self.bind("self", self_ty.clone());
1637 let saved = self.fmt_param.replace(f);
1638 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 7h ago1639 // No assignment target: a formatter write *appends*, because a `fmt`
1640 // body may write repeatedly -- `UpperHex` writes once per byte in a
1641 // loop -- and assigning would keep only the last one.
1642 let outer_target = self.target.take();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1643
1644 self.line(&format!(
1645 "proc {}*(self: {}): string =",
1646 proc_name,
1647 self_ty.render()
1648 ));
1649 self.indent += 1;
1650 let before = self.out.len();
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago1651 let tail = self.block_body(body)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1652 self.emit_tail(tail);
1653 if self.out.len() == before {
1654 self.line("discard");
1655 }
1656 self.indent -= 1;
1657
1658 self.target = outer_target;
1659 self.ret = outer_ret;
1660 self.fmt_param = saved;
1661 self.pop_scope();
1662 self.blank();
1663 Ok(())
1664 }
1665
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1666 fn func(
1667 &mut self,
1668 sig: &syn::Signature,
1669 body: &syn::Block,
1670 recv: Option<Nim>,
1671 ) -> Result<(), String> {
1672 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1673 self.func_named(&name.clone(), sig, body, recv)
1674 }
1675
1676 fn func_named(
1677 &mut self,
1678 name: &str,
1679 sig: &syn::Signature,
1680 body: &syn::Block,
1681 recv: Option<Nim>,
1682 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1683 let (ptys, ret) = self.signature(sig)?;
1684
1685 self.push_scope();
1686 let mut rendered: Vec<String> = Vec::new();
1687
1688 if let Some(self_ty) = recv {
1689 // `&mut self` and `mut self` both mean the body may mutate the
1690 // receiver; only the former is observable by the caller, and a Nim
1691 // `var` parameter is the faithful spelling of that.
1692 let mutable = matches!(
1693 sig.inputs.first(),
1694 Some(FnArg::Receiver(r))
1695 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1696 );
1697 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1698 rendered.push(format!("self: {}", t.render()));
1699 self.bind("self", self_ty);
1700 }
1701
1702 let typed: Vec<&syn::PatType> = sig
1703 .inputs
1704 .iter()
1705 .filter_map(|a| match a {
1706 FnArg::Typed(t) => Some(t),
1707 _ => None,
1708 })
1709 .collect();
1710 for (p, t) in typed.iter().zip(ptys.iter()) {
1711 let pname = match &*p.pat {
1712 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1713 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
1714 // still needs a name for it.
1715 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1716 _ => return Err("only plain identifier parameters are supported".into()),
1717 };
1718 rendered.push(format!("{}: {}", ident(&pname), t.render()));
1719 // Inside the body a `var T` parameter is used exactly like a `T`.
1720 self.bind(&pname, t.clone().owned());
1721 }
1722
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1723 let mut gparams = self.impl_generics.clone();
1724 for g in Self::generics_of(&sig.generics) {
1725 if !gparams.contains(&g) {
1726 gparams.push(g);
1727 }
1728 }
1729 let gens = Self::gen_list(&gparams);
1730 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 9h ago1731 let head = if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1732 format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1733 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1734 format!(
1735 "proc {}*{}({}): {} =",
1736 ident(name),
1737 gens,
1738 rendered.join(", "),
1739 ret.render()
1740 )
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1741 };
1742 self.line(&head);
1743 self.indent += 1;
1744 let outer_ret = self.ret.replace(ret.clone());
1745
1746 // A Rust fn's trailing expression is its return value. Naming Nim's
1747 // implicit `result` as the target makes that true whether the tail is
1748 // a plain expression or an `if`/`match` with statement arms.
1749 let outer_target = if ret == Nim::Unit {
1750 self.target.take()
1751 } else {
1752 self.target.replace(("result".to_string(), Some(ret.clone())))
1753 };
1754 let before = self.out.len();
1755 let tail = self.block_body_at(body, Some(&ret))?;
1756 self.target = outer_target;
1757 match tail {
1758 Some(v) if ret != Nim::Unit => {
1759 let code = v.code.clone();
1760 self.line(&format!("result = {code}"));
1761 }
1762 Some(v) => {
1763 // A trailing expression in a `()`-returning fn is evaluated for
1764 // its effect; Nim requires an explicit discard.
1765 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1766 if needs_discard && !v.code.is_empty() {
1767 let code = v.code.clone();
1768 self.line(&format!("discard {code}"));
1769 }
1770 }
1771 None => {}
1772 }
1773 if self.out.len() == before {
1774 self.line("discard");
1775 }
1776
1777 self.indent -= 1;
1778 self.ret = outer_ret;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1779 self.fn_generics = outer_fg;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1780 self.pop_scope();
1781 self.blank();
1782 Ok(())
1783 }
1784
1785 // ---------------------------------------------------------- statements
1786
1787 /// Lower a block's statements. Returns the block's trailing expression,
1788 /// if it has one, *without* emitting it — the caller decides whether that
1789 /// value is a return value, a binding, or discarded.
1790 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
1791 self.block_body_at(b, None)
1792 }
1793
1794 fn block_body_at(
1795 &mut self,
1796 b: &syn::Block,
1797 expect: Option<&Nim>,
1798 ) -> Result<Option<Val>, String> {
1799 // An assignment target belongs to *this* block's trailing expression
1800 // only. A non-final `if` is a statement and must not assign anything.
1801 let target = self.target.take();
1802 let n = b.stmts.len();
1803 let mut tail = None;
1804 for (i, st) in b.stmts.iter().enumerate() {
1805 let last = i + 1 == n;
1806 match st {
1807 Stmt::Expr(e, None) if last && expressible(e) => {
1808 tail = Some(self.expr_at(e, expect)?)
1809 }
1810 Stmt::Expr(e, None) if last => {
1811 // A trailing `if`/`match` with statement arms, or a loop.
1812 // Lower it as statements; if this block's value is wanted,
1813 // each arm assigns it.
1814 match &target {
1815 Some((t, ty)) => {
1816 let (t, ty) = (t.clone(), ty.clone());
1817 self.assign_from(e, &t, ty.as_ref())?;
1818 }
1819 None => self.stmt(st)?,
1820 }
1821 }
1822 _ => self.stmt(st)?,
1823 }
1824 }
1825 self.target = target;
1826 Ok(tail)
1827 }
1828
1829 /// Lower a block in statement position (loop bodies, `if` arms).
1830 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
1831 self.push_scope();
1832 self.indent += 1;
1833 let before = self.out.len();
1834 let want = self.target.clone().and_then(|(_, t)| t);
1835 let tail = self.block_body_at(b, want.as_ref())?;
1836 self.emit_tail(tail);
1837 if self.out.len() == before {
1838 self.line("discard");
1839 }
1840 self.indent -= 1;
1841 self.pop_scope();
1842 Ok(())
1843 }
1844
1845 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
1846 match s {
1847 Stmt::Local(l) => self.local(l),
1848 Stmt::Expr(e, _) => {
1849 let v = self.expr_stmt(e)?;
1850 if let Some(v) = v {
1851 // A bare expression with a value must be discarded in Nim.
1852 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1853 let code = v.code.clone();
1854 if needs {
1855 self.line(&format!("discard {code}"));
1856 } else if !code.is_empty() {
1857 self.line(&code);
1858 }
1859 }
1860 Ok(())
1861 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1862 // A `const` declared inside a function body is local to it, and
1863 // must be emitted here rather than skipped as an already-emitted
1864 // top-level type.
1865 Stmt::Item(i) => self.item_inner(i),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1866 Stmt::Macro(m) => {
1867 let line = self.macro_call(&m.mac)?;
1868 self.line(&line);
1869 Ok(())
1870 }
1871 }
1872 }
1873
1874 fn local(&mut self, l: &Local) -> Result<(), String> {
1875 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
1876 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
1877 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1878 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 9h ago1879 _ => return Err("only `let <ident>` bindings are supported".into()),
1880 },
1881 Pat::Wild(_) => ("_".into(), false, None),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1882 Pat::Tuple(t) => return self.local_tuple(l, t),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1883 _ => return Err("destructuring `let` is not implemented yet".into()),
1884 };
1885
1886 let Some(init) = &l.init else {
1887 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
1888 // not. Rust's own rules make reading it before assignment illegal,
1889 // so the two agree on every program rustc accepts.
1890 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
1891 let t = t.owned();
1892 self.line(&format!("var {}: {}", ident(&name), t.render()));
1893 self.bind(&name, t);
1894 return Ok(());
1895 };
1896 if init.diverge.is_some() {
1897 return Err("`let ... else` is not implemented yet".into());
1898 }
1899
1900 if !expressible(&init.expr) && name != "_" {
1901 // The initialiser is an `if`/`match` whose arms are statements.
1902 // Declare first, then let each arm assign into the binding.
1903 let t = ann
1904 .clone()
1905 .ok_or_else(|| {
1906 format!(
1907 "`let {name} = match/if ...` needs a type annotation: \
1908 its arms are statements, so the binding must be \
1909 declared before they run"
1910 )
1911 })?
1912 .owned();
1913 self.line(&format!("var {}: {}", ident(&name), t.render()));
1914 self.bind(&name, t.clone());
1915 let target = ident(&name);
1916 return self.assign_from(&init.expr, &target, Some(&t));
1917 }
1918
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago1919 // `let it = xs.chunks_exact(k)` binds an iterator, not a value.
1920 if is_iterator_expr(&init.expr) {
1921 let it = self.resolve_iter(&init.expr)?;
1922 self.bind_alias(&name, Alias::Iterator(Box::new(it)));
1923 return Ok(());
1924 }
1925
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1926 let v = self.expr_at(&init.expr, ann.as_ref())?;
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 7h ago1927
1928 // `let s = &buf[..n]` binds a view of a place that is already in
1929 // scope. Nim's borrow checker will not let a `let` borrow out of a
1930 // local, and there is nothing to materialise anyway -- a view is a
1931 // reference. Binding it as an alias substitutes the same expression at
1932 // each use, which re-evaluates nothing because the initialiser is a
1933 // place expression with no side effects.
1934 if v.window.is_none()
1935 && matches!(v.ty, Some(Nim::OpenArray(_)))
1936 && is_pure_place(&init.expr)
1937 {
1938 let t = v.ty.clone().unwrap();
1939 let elem = match &t {
1940 Nim::OpenArray(e) => Some((**e).clone()),
1941 _ => None,
1942 };
1943 self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) });
1944 let _ = elem;
1945 return Ok(());
1946 }
1947
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1948 if let Some(w) = v.window.clone() {
1949 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
1950 // view into the caller's buffer. Copying it into a `seq` would
1951 // still print the right bytes but would stop writes reaching the
1952 // caller, so it is bound as an alias.
1953 if v.guard.is_some() && v.guard_err.is_some() {
1954 return Err(format!(
1955 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
1956 which Nim cannot represent; apply `?` or `unwrap()` to it \
1957 in the same expression"
1958 ));
1959 }
1960 self.bind_alias(&name, w);
1961 return Ok(());
1962 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago1963 // A `let` binding a borrow keeps the view: `let res = encode(..)?`
1964 // names the caller's buffer, and copying it into a `seq` would still
1965 // print the right bytes while silently breaking the aliasing.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1966 let t = match (ann, &v.ty) {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago1967 (Some(a), _) => a.unvar(),
1968 (None, Some(t)) => t.clone().unvar(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1969 (None, None) => {
1970 return Err(format!(
1971 "cannot infer the type of `let {name}`; annotate it — \
1972 guessing here would change integer width, and with it the \
1973 meaning of any arithmetic on `{name}`"
1974 ))
1975 }
1976 };
1977
1978 if name == "_" {
1979 let code = v.code.clone();
1980 self.line(&format!("discard {code}"));
1981 return Ok(());
1982 }
1983 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
1984 // 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 7h ago1985 //
1986 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
1987 // Rust may write through it, and Nim only accepts a `var` where a
1988 // `var` parameter is wanted, so the binding has to be one.
1989 let mutable = mutable || is_mut_borrow(&init.expr);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1990 let kw = if mutable { "var" } else { "let" };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago1991 // Inside a generic proc the binding's type may mention a parameter Nim
1992 // will infer; naming it in an annotation would not resolve.
1993 let line = if self.mentions_type_param(&t) {
1994 format!("{} {} = {}", kw, ident(&name), v.code)
1995 } else {
1996 format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)
1997 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago1998 self.line(&line);
1999 self.bind(&name, t);
2000 Ok(())
2001 }
2002
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago2003 /// `let (a, b) = ..` — tuple destructuring.
2004 fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> {
2005 let Some(init) = &l.init else {
2006 return Err("a destructuring `let` needs an initialiser".into());
2007 };
2008 let names: Vec<(String, bool)> = t
2009 .elems
2010 .iter()
2011 .map(|p| match p {
2012 Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())),
2013 Pat::Wild(_) => Ok(("_".to_string(), false)),
2014 _ => Err("only plain identifiers are supported in a destructuring `let`"),
2015 })
2016 .collect::<Result<_, _>>()?;
2017
2018 // `split_at` hands back two *views* of the same slice. Nim has no
2019 // tuple of views, and there is nothing to materialise anyway, so each
2020 // name becomes a window into the original.
2021 if let Expr::MethodCall(m) = &*init.expr {
2022 let mname = m.method.to_string();
2023 if (mname == "split_at" || mname == "split_at_mut")
2024 && m.args.len() == 1
2025 && names.len() == 2
2026 {
2027 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
2028 let at = self.expr(&m.args[0])?;
2029 let cut = self.fresh("Cut");
2030 self.line(&format!("let {}: int = int({})", cut, at.code));
2031 self.bind_alias(
2032 &names[0].0,
2033 Alias::Window {
2034 code: code.clone(),
2035 off: base.clone(),
2036 len: cut.clone(),
2037 elem: elem.clone(),
2038 },
2039 );
2040 self.bind_alias(
2041 &names[1].0,
2042 Alias::Window {
2043 code,
2044 off: format!("({} + {})", base, cut),
2045 len: format!("({} - {})", len, cut),
2046 elem,
2047 },
2048 );
2049 return Ok(());
2050 }
2051 }
2052
2053 let v = self.expr(&init.expr)?;
2054 let tys = match &v.ty {
2055 Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(),
2056 _ => {
2057 return Err(format!(
2058 "cannot destructure this into {} bindings: its type is not a \
2059 tuple of that many elements",
2060 names.len()
2061 ))
2062 }
2063 };
2064 let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" };
2065 let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect();
2066 self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code));
2067 for ((n, _), t) in names.iter().zip(tys) {
2068 self.bind(n, t);
2069 }
2070 Ok(())
2071 }
2072
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2073 /// Expressions that are statements in Rust and statements in Nim too
2074 /// (control flow). Returns `None` when it emitted lines itself.
2075 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
2076 match e {
2077 Expr::If(_) => {
2078 self.if_stmt(e)?;
2079 Ok(None)
2080 }
2081 Expr::While(w) => {
2082 if w.label.is_some() {
2083 return Err("loop labels are not implemented yet".into());
2084 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2085 self.in_loop_cond = true;
2086 let c = self.expr(&w.cond);
2087 self.in_loop_cond = false;
2088 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2089 self.line(&format!("while {}:", c.code));
2090 let saved = self.target.take();
2091 self.nested_block(&w.body)?;
2092 self.target = saved;
2093 Ok(None)
2094 }
2095 Expr::Loop(l) => {
2096 if l.label.is_some() {
2097 return Err("loop labels are not implemented yet".into());
2098 }
2099 self.line("while true:");
2100 let saved = self.target.take();
2101 self.nested_block(&l.body)?;
2102 self.target = saved;
2103 Ok(None)
2104 }
2105 Expr::ForLoop(f) => {
2106 self.for_loop(f)?;
2107 Ok(None)
2108 }
2109 Expr::Block(b) => {
2110 if b.label.is_some() {
2111 return Err("block labels are not implemented yet".into());
2112 }
2113 self.line("block:");
2114 self.nested_block(&b.block)?;
2115 Ok(None)
2116 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2117 Expr::Unsafe(u) => {
2118 // Transparent in statement position too, for the same reason.
2119 self.nested_block_flat(&u.block)?;
2120 Ok(None)
2121 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2122 Expr::Match(_) => {
2123 self.match_stmt(e)?;
2124 Ok(None)
2125 }
2126 Expr::Return(r) => {
2127 match &r.expr {
2128 Some(e) => {
2129 let want = self.ret.clone();
2130 let v = self.expr_at(e, want.as_ref())?;
2131 self.line(&format!("return {}", v.code));
2132 }
2133 None => self.line("return"),
2134 }
2135 Ok(None)
2136 }
2137 Expr::Break(b) => {
2138 if b.expr.is_some() || b.label.is_some() {
2139 return Err("`break` with a value or a label is not implemented yet".into());
2140 }
2141 self.line("break");
2142 Ok(None)
2143 }
2144 Expr::Continue(c) => {
2145 if c.label.is_some() {
2146 return Err("labelled `continue` is not implemented yet".into());
2147 }
2148 self.line("continue");
2149 Ok(None)
2150 }
2151 Expr::Assign(a) => {
2152 let lhs = self.expr(&a.left)?;
2153 if !expressible(&a.right) {
2154 let target = lhs.code.clone();
2155 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
2156 }
2157 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
2158 self.line(&format!("{} = {}", lhs.code, rhs.code));
2159 Ok(None)
2160 }
2161 Expr::Binary(b) if is_compound(&b.op) => {
2162 let lhs = self.expr(&b.left)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago2163 // A compound assignment on a user type goes to that type's own
2164 // `impl OpAssign`, not to Nim's built-in operator.
2165 if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) {
2166 // The impl's own parameter type types the right operand,
2167 // so `b_vec *= 4` takes 4 at the width the impl declares.
2168 let want = self.op_param(&lhs.ty, compound_symbol(&b.op));
2169 let rhs = self.expr_at(&b.right, want.as_ref())?;
2170 self.line(&format!("{}({}, {})", f, lhs.code, rhs.code));
2171 return Ok(None);
2172 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2173 // `i += 1` must widen the literal to `i`'s type, not to the
2174 // i32 an unconstrained Rust literal would default to.
2175 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
2176 let op = self.bin_op(&b.op, &lhs, &rhs)?;
2177 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
2178 // both languages, so the expanded form is always correct.
2179 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
2180 Ok(None)
2181 }
2182 Expr::Macro(m) => {
2183 let line = self.macro_call(&m.mac)?;
2184 self.line(&line);
2185 Ok(None)
2186 }
2187 _ => Ok(Some(self.expr(e)?)),
2188 }
2189 }
2190
2191 /// Lower `e` in statement position, assigning each arm's value to
2192 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
2193 /// the trip when their arms are too big for a Nim `if`-expression.
2194 fn assign_from(
2195 &mut self,
2196 e: &Expr,
2197 target: &str,
2198 expect: Option<&Nim>,
2199 ) -> Result<(), String> {
2200 let saved = self.target.replace((target.to_string(), expect.cloned()));
2201 let r = match e {
2202 Expr::If(_) => self.if_stmt(e),
2203 Expr::Match(_) => self.match_stmt(e),
2204 other => {
2205 let v = self.expr_at(other, expect)?;
2206 self.line(&format!("{} = {}", target, v.code));
2207 Ok(())
2208 }
2209 };
2210 self.target = saved;
2211 r
2212 }
2213
2214 /// Emit a block's value into the active assignment target, if there is
2215 /// one, or discard it if there is not.
2216 fn emit_tail(&mut self, v: Option<Val>) {
2217 let Some(v) = v else { return };
2218 match self.target.clone() {
2219 Some((t, _)) => {
2220 let code = v.code.clone();
2221 self.line(&format!("{t} = {code}"));
2222 }
2223 None => {
2224 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2225 let code = v.code.clone();
2226 if needs {
2227 self.line(&format!("discard {code}"));
2228 } else if !code.is_empty() {
2229 self.line(&code);
2230 }
2231 }
2232 }
2233 }
2234
2235 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
2236 let Expr::If(i) = e else { unreachable!() };
2237 if let Expr::Let(_) = &*i.cond {
2238 return Err("`if let` is not implemented yet".into());
2239 }
2240 let c = self.expr(&i.cond)?;
2241 self.line(&format!("if {}:", c.code));
2242 self.nested_block(&i.then_branch)?;
2243 match &i.else_branch {
2244 None => {}
2245 Some((_, els)) => match &**els {
2246 Expr::If(_) => {
2247 // Nim needs `elif`; splice the nested `if` in as one.
2248 let mark = self.out.len();
2249 self.if_stmt(els)?;
2250 let tail = self.out.split_off(mark);
2251 let indent = " ".repeat(self.indent);
2252 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
2253 }
2254 Expr::Block(b) => {
2255 self.line("else:");
2256 self.nested_block(&b.block)?;
2257 }
2258 _ => return Err("unsupported `else` form".into()),
2259 },
2260 }
2261 Ok(())
2262 }
2263
2264 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
2265 if f.label.is_some() {
2266 return Err("loop labels are not implemented yet".into());
2267 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2268 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2269
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2270 // One index loop drives the whole chain. Rust's adaptors are lazy and
2271 // compose; resolving them to an index and binding each name to an
2272 // lvalue reproduces that without materialising anything.
2273 let i = self.fresh("Idx");
2274 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
2275 self.indent += 1;
2276 self.push_scope();
2277 let before = self.out.len();
2278
2279 self.bind_pattern(&f.pat, &it, &i)?;
2280
2281 let saved = self.target.take();
2282 if let Some(v) = self.block_body(&f.body)? {
2283 let code = v.code.clone();
2284 self.line(&format!("discard {code}"));
2285 }
2286 self.target = saved;
2287 if self.out.len() == before {
2288 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2289 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2290 self.pop_scope();
2291 self.indent -= 1;
2292 Ok(())
2293 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2294
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2295 /// Resolve a chain of iterator adaptors into a single `Iter`.
2296 ///
2297 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
2298 /// `filter`, `take_while` and friends are rejected rather than partially
2299 /// honoured: silently dropping an adaptor would change which elements the
2300 /// loop visits.
2301 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
2302 match e {
2303 Expr::Reference(r) => self.resolve_iter(&r.expr),
2304 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2305 Expr::Range(r) => {
2306 let lo = match &r.start {
2307 Some(e) => self.expr(e)?,
2308 None => return Err("a `for` over `..n` needs a start bound".into()),
2309 };
2310 let hi = match &r.end {
2311 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2312 None => {
2313 return Err("a `for` over an unbounded range would not terminate".into())
2314 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2315 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2316 let ty = lo.ty.clone().or(hi.ty.clone());
2317 Ok(Iter::Range {
2318 lo: lo.code,
2319 hi: hi.code,
2320 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
2321 ty,
2322 })
2323 }
2324 Expr::MethodCall(m) => {
2325 let name = m.method.to_string();
2326 match name.as_str() {
2327 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
2328 let mut it = self.resolve_iter(&m.receiver)?;
2329 if name == "iter_mut" {
2330 if let Iter::Elems { mutable, .. } = &mut it {
2331 *mutable = true;
2332 }
2333 }
2334 Ok(it)
2335 }
2336 "enumerate" if m.args.is_empty() => {
2337 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
2338 }
2339 "zip" if m.args.len() == 1 => {
2340 let a = self.resolve_iter(&m.receiver)?;
2341 let b = self.resolve_iter(&m.args[0])?;
2342 Ok(Iter::Zip(Box::new(a), Box::new(b)))
2343 }
2344 "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 8h ago2345 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2346 let k = self.expr(&m.args[0])?;
2347 Ok(Iter::Chunks {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2348 code,
2349 base,
2350 len,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2351 k: k.code,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2352 elem,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2353 mutable: name.ends_with("_mut"),
2354 })
2355 }
2356 "windows" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2357 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2358 let k = self.expr(&m.args[0])?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2359 Ok(Iter::Windows { code, base, len, k: k.code, elem })
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2360 }
2361 other => Err(format!(
2362 "iterator adaptor `.{other}()` is not implemented; it has \
2363 no index-loop equivalent here, and dropping it would \
2364 change which elements the loop visits"
2365 )),
2366 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2367 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago2368 Expr::Path(p) => {
2369 let n = path_name(&p.path);
2370 if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) {
2371 return Ok((*it).clone());
2372 }
2373 if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) {
2374 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
2375 }
2376 let v = self.expr(e)?;
2377 Ok(Iter::Elems {
2378 len: format!("{}.len", v.code),
2379 elem: elem_of(&v.ty),
2380 code: v.code,
2381 off: "0".into(),
2382 mutable: false,
2383 })
2384 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2385 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2386 // A `for` binding that is itself a window iterates that window,
2387 // not the whole container it points into.
2388 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 8h ago2389 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2390 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2391 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2392 Ok(Iter::Elems {
2393 len: format!("{}.len", v.code),
2394 elem: elem_of(&v.ty),
2395 code: v.code,
2396 off: "0".into(),
2397 mutable: false,
2398 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2399 }
2400 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2401 }
2402
2403 /// Bind a `for` pattern against a resolved iterator at index `i`.
2404 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
2405 match (p, it) {
2406 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
2407 self.bind_pattern(&t.elems[0], a, i)?;
2408 self.bind_pattern(&t.elems[1], b, i)
2409 }
2410 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
2411 if let Pat::Ident(id) = &t.elems[0] {
2412 let n = id.ident.to_string();
2413 // Rust's `enumerate` counts in `usize`.
2414 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
2415 self.bind(&n, Nim::Prim("uint".into()));
2416 }
2417 self.bind_pattern(&t.elems[1], inner, i)
2418 }
2419 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
2420 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
2421 ),
2422 (Pat::Wild(_), _) => Ok(()),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2423 // `for &byte in xs` — the `&` destructures the reference, which in
2424 // Nim is already the value.
2425 (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
2426 (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2427 (Pat::Ident(id), _) => {
2428 let name = id.ident.to_string();
2429 match it {
2430 Iter::Range { lo, ty, .. } => {
2431 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
2432 // The loop counts from zero; the range's own start is
2433 // added back so the binding has Rust's value and type.
2434 self.line(&format!(
2435 "let {}: {} = {}({}) + {}",
2436 ident(&name),
2437 t.render(),
2438 t.render(),
2439 i,
2440 lo
2441 ));
2442 self.bind(&name, t);
2443 Ok(())
2444 }
2445 Iter::Elems { code, off, elem, mutable, .. } => {
2446 let access = if off == "0" {
2447 format!("{}[{}]", code, i)
2448 } else {
2449 format!("{}[{} + {}]", code, off, i)
2450 };
2451 if *mutable {
2452 // An alias, not a copy: assigning through the
2453 // binding must reach the original element.
2454 self.bind_alias(
2455 &name,
2456 Alias::Value { code: access, ty: elem.clone() },
2457 );
2458 } else {
2459 let t = elem
2460 .clone()
2461 .ok_or("cannot infer the element type of this `for`")?;
2462 self.line(&format!(
2463 "let {}: {} = {}",
2464 ident(&name),
2465 t.render(),
2466 access
2467 ));
2468 self.bind(&name, t);
2469 }
2470 Ok(())
2471 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2472 Iter::Chunks { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2473 self.bind_alias(
2474 &name,
2475 Alias::Window {
2476 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2477 off: format!("({} + {} * int({}))", base, i, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2478 len: format!("int({})", k),
2479 elem: elem.clone(),
2480 },
2481 );
2482 Ok(())
2483 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2484 Iter::Windows { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2485 self.bind_alias(
2486 &name,
2487 Alias::Window {
2488 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2489 off: format!("({} + {})", base, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2490 len: format!("int({})", k),
2491 elem: elem.clone(),
2492 },
2493 );
2494 Ok(())
2495 }
2496 // Handled above: a zip or enumerate needs a tuple pattern,
2497 // and binding one name to the pair is not supported.
2498 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
2499 }
2500 }
2501 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2502 }
2503 }
2504
2505 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
2506 let Expr::Match(m) = e else { unreachable!() };
2507 let scrut = self.expr(&m.expr)?;
2508 let t = scrut
2509 .ty
2510 .clone()
2511 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2512 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2513 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2514
2515 // A `match` whose arms neither bind nor guard is a Nim `case`, which
2516 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
2517 // an if/elif chain, because Nim's `case` cannot destructure.
2518 let plain = m.arms.iter().all(|a| {
2519 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
2520 });
2521 if plain {
2522 self.match_case(m, &name, &t)
2523 } else {
2524 self.match_chain(m, &name, &t)
2525 }
2526 }
2527
2528 fn match_case(
2529 &mut self,
2530 m: &syn::ExprMatch,
2531 name: &str,
2532 t: &Nim,
2533 ) -> Result<(), String> {
2534 // A variant object is discriminated by its `kind` field.
2535 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
2536 self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2537
2538 let mut saw_wild = false;
2539 for arm in &m.arms {
2540 match &arm.pat {
2541 Pat::Wild(_) => {
2542 saw_wild = true;
2543 self.line("else:");
2544 }
2545 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2546 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2547 self.line(&format!("of {}:", labels.join(", ")));
2548 }
2549 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2550 self.arm_body(&arm.body)?;
2551 }
2552 if !saw_wild && !self.case_is_total(t, m) {
2553 // Rust checked exhaustiveness already, but Nim cannot always see
2554 // it -- an integer `case` needs every value covered -- so make the
2555 // unreachable arm explicit rather than leave a compile error.
2556 self.line("else:");
2557 self.line(" rsPanic(\"unreachable match arm\")");
2558 }
2559 Ok(())
2560 }
2561
2562 /// Whether a Nim `case` over this type is already total, in which case
2563 /// adding an `else` would be a compile error rather than a safety net.
2564 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
2565 let Nim::Named(n, _) = t else { return false };
2566 let Some(def) = self.enums.get(n) else { return false };
2567 def.variants.len() == m.arms.len()
2568 }
2569
2570 /// The if/elif form, for arms that bind or destructure.
2571 fn match_chain(
2572 &mut self,
2573 m: &syn::ExprMatch,
2574 name: &str,
2575 t: &Nim,
2576 ) -> Result<(), String> {
2577 let mut first = true;
2578 let mut closed = false;
2579 for arm in &m.arms {
2580 let (pat, guard) = match &arm.pat {
2581 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
2582 p => (p, None),
2583 };
2584 if guard.is_some() && binds(pat) {
2585 return Err("a `match` guard on a binding pattern is not \
2586 implemented yet"
2587 .into());
2588 }
2589 let test = self.pat_test(pat, name, t)?;
2590 let test = match (test, guard) {
2591 (Some(t), Some(g)) => {
2592 let g = self.expr(g)?;
2593 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2594 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2595 (None, Some(g)) => Some(self.expr(g)?.code),
2596 (t, None) => t,
2597 };
2598 match test {
2599 Some(test) => {
2600 self.line(&format!(
2601 "{} {}:",
2602 if first { "if" } else { "elif" },
2603 test
2604 ));
2605 first = false;
2606 }
2607 None => {
2608 // An irrefutable pattern: everything left falls here.
2609 if first {
2610 self.line("block:");
2611 } else {
2612 self.line("else:");
2613 }
2614 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2615 }
2616 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2617 self.indent += 1;
2618 self.push_scope();
2619 let before = self.out.len();
2620 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2621 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2622 self.arm_body_at(&arm.body, before)?;
2623 self.pop_scope();
2624 if closed {
2625 break;
2626 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2627 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2628 if !closed {
2629 // Rust proved this unreachable; Nim cannot see that, and leaving
2630 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2631 self.line("else:");
2632 self.line(" rsPanic(\"unreachable match arm\")");
2633 }
2634 Ok(())
2635 }
2636
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2637 /// The condition that selects this arm, or `None` if it always matches.
2638 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
2639 Ok(match p {
2640 Pat::Wild(_) => None,
2641 Pat::Ident(i) if i.subpat.is_none() => None,
2642 Pat::Or(o) => {
2643 let mut parts = Vec::new();
2644 for c in &o.cases {
2645 match self.pat_test(c, name, t)? {
2646 Some(x) => parts.push(x),
2647 None => return Ok(None),
2648 }
2649 }
2650 Some(format!("({})", parts.join(" or ")))
2651 }
2652 Pat::Lit(_) | Pat::Range(_) => {
2653 let labels = self.pat_labels(p, Some(t))?;
2654 Some(match p {
2655 Pat::Range(_) => format!("({} in {})", name, labels[0]),
2656 _ => format!("({} == {})", name, labels[0]),
2657 })
2658 }
2659 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
2660 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
2661 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
2662 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
2663 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
2664 _ => return Err("unsupported `match` pattern".into()),
2665 })
2666 }
2667
2668 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
2669 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
2670 let last = path_name(path);
2671 match last.as_str() {
2672 "Ok" => return Ok(format!("{name}.ok")),
2673 "Err" => return Ok(format!("(not {name}.ok)")),
2674 "Some" => return Ok(format!("{name}.has")),
2675 "None" => return Ok(format!("(not {name}.has)")),
2676 _ => {}
2677 }
2678 let Some((def, v)) = self.resolve_variant(path) else {
2679 return Err(format!(
2680 "`{last}` in a pattern is not a known enum variant; if it names \
2681 an enum declared in another module, that is not implemented yet"
2682 ));
2683 };
2684 if let Nim::Named(n, _) = t {
2685 if *n != def.name {
2686 return Err(format!(
2687 "pattern `{}::{}` does not match the scrutinee type `{}`",
2688 def.name, v, n
2689 ));
2690 }
2691 }
2692 Ok(if def.simple {
2693 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
2694 } else {
2695 format!("({}.kind == {})", name, def.kind_ident(&v))
2696 })
2697 }
2698
2699 /// Emit the `let`s that a pattern's bindings introduce.
2700 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
2701 match p {
2702 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
2703 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
2704 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
2705 Pat::Ident(i) if i.subpat.is_none() => {
2706 let b = i.ident.to_string();
2707 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
2708 self.bind(&b, t.clone());
2709 Ok(())
2710 }
2711 Pat::TupleStruct(ts) => {
2712 let fields = self.variant_fields(&ts.path, t)?;
2713 for (i, sub) in ts.elems.iter().enumerate() {
2714 let Some((fname, fty)) = fields.get(i) else {
2715 return Err(format!(
2716 "pattern binds {} field(s) but the variant has {}",
2717 ts.elems.len(),
2718 fields.len()
2719 ));
2720 };
2721 let access = format!("{}.{}", name, ident(fname));
2722 self.pat_bind(sub, &access, fty)?;
2723 }
2724 Ok(())
2725 }
2726 Pat::Struct(st) => {
2727 let fields = self.variant_fields(&st.path, t)?;
2728 for f in &st.fields {
2729 let syn::Member::Named(m) = &f.member else {
2730 return Err("unsupported struct pattern field".into());
2731 };
2732 let m = m.to_string();
2733 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
2734 return Err(format!("unknown field `{m}` in pattern"));
2735 };
2736 let access = format!("{}.{}", name, ident(fname));
2737 self.pat_bind(&f.pat, &access, fty)?;
2738 }
2739 Ok(())
2740 }
2741 _ => Err("unsupported `match` pattern".into()),
2742 }
2743 }
2744
2745 /// The payload fields a variant pattern destructures.
2746 fn variant_fields(
2747 &self,
2748 path: &syn::Path,
2749 t: &Nim,
2750 ) -> Result<Vec<(String, Nim)>, String> {
2751 let last = path_name(path);
2752 // `Ok`/`Err`/`Some` read the prelude's own field names.
2753 if let Nim::Named(n, a) = t {
2754 match (n.as_str(), last.as_str()) {
2755 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
2756 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
2757 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
2758 _ => {}
2759 }
2760 }
2761 let Some((def, v)) = self.resolve_variant(path) else {
2762 return Err(format!("`{last}` is not a known enum variant"));
2763 };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago2764 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2765 // The variant's payload is declared in the enum's own parameters; the
2766 // scrutinee says what they are here.
2767 Ok(fields
2768 .into_iter()
2769 .map(|(n, ft)| (n, self.subst_type_args(&def.name, t, ft)))
2770 .collect())
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2771 }
2772
2773 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
2774 self.indent += 1;
2775 let before = self.out.len();
2776 self.indent -= 1;
2777 self.arm_body_at(body, before)
2778 }
2779
2780 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
2781 match body {
2782 Expr::Block(b) => self.nested_block(&b.block)?,
2783 other => {
2784 self.indent += 1;
2785 // An arm's value is the `match`'s value, so it is typed by
2786 // whatever the `match` is being assigned to -- without which
2787 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
2788 let want = self.target.clone().and_then(|(_, t)| t);
2789 let v = match (want, expressible(other)) {
2790 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
2791 _ => self.expr_stmt(other)?,
2792 };
2793 self.emit_tail(v);
2794 self.indent -= 1;
2795 }
2796 }
2797 if self.out.len() == before {
2798 self.indent += 1;
2799 self.line("discard");
2800 self.indent -= 1;
2801 }
2802 Ok(())
2803 }
2804
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2805 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
2806 match p {
2807 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
2808 Pat::Or(o) => {
2809 let mut out = Vec::new();
2810 for p in &o.cases {
2811 out.extend(self.pat_labels(p, expect)?);
2812 }
2813 Ok(out)
2814 }
2815 Pat::Range(r) => {
2816 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
2817 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
2818 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
2819 let op = match r.limits {
2820 syn::RangeLimits::HalfOpen(_) => "..<",
2821 syn::RangeLimits::Closed(_) => "..",
2822 };
2823 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
2824 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2825 Pat::Path(pp) => {
2826 if let Some((def, v)) = self.resolve_variant(&pp.path) {
2827 return Ok(vec![if def.simple {
2828 format!("{}.{}", ident(&def.name), ident(&v))
2829 } else {
2830 def.kind_ident(&v)
2831 }]);
2832 }
2833 Ok(vec![ident(&path_name(&pp.path))])
2834 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2835 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2836 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2837 .into()),
2838 }
2839 }
2840
2841 // --------------------------------------------------------- expressions
2842
2843 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
2844 self.expr_at(e, None)
2845 }
2846
2847 /// Lower `e`, with the type the surrounding code expects of it.
2848 ///
2849 /// Rust infers an unsuffixed integer literal's type from its context and
2850 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
2851 /// expected type down to the literal is what makes `let x: u8 = 255` and
2852 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
2853 /// widths silently diverge, which is exactly the class of bug this
2854 /// project refuses to ship.
2855 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
2856 match e {
2857 Expr::Lit(l) => self.lit_at(&l.lit, expect),
2858 Expr::Path(p) => {
2859 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2860 if name == "None" {
2861 return Ok(Val::new(self.none_of(expect), expect.cloned()));
2862 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 6h ago2863 // `Grid::BORDER`: a `const` declared inside an `impl`.
2864 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
2865 let q = if q == "Self" {
2866 self.self_ty.as_ref().map(type_name).unwrap_or(q)
2867 } else {
2868 q
2869 };
2870 if let Some((nim, t)) = self.assoc_consts.get(&(q, name.clone())) {
2871 return Ok(Val::new(nim.clone(), Some(t.clone())));
2872 }
2873 }
2874
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago2875 // `i32::MAX` and friends: an associated const on a primitive.
2876 if matches!(name.as_str(), "MAX" | "MIN") {
2877 if let Some(q) = p.path.segments.iter().rev().nth(1) {
2878 if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) {
2879 if t.is_integer() {
2880 let f = if name == "MAX" { "high" } else { "low" };
2881 return Ok(Val::new(
2882 format!("{}({})", f, t.render()),
2883 Some(t),
2884 ));
2885 }
2886 }
2887 }
2888 }
2889
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2890 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
2891 // declared here. In Nim that is a constructor call.
2892 if p.path.segments.len() > 1 {
2893 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
2894 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
2895 if n == "FmtError" {
2896 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
2897 }
2898 }
2899 }
2900 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
2901 return Ok(Val::new(
2902 format!("{}()", ident(&name)),
2903 Some(Nim::Named(name.clone(), vec![])),
2904 ));
2905 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2906 // A unit enum variant used as a value: `Error::InvalidLength`.
2907 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago2908 let (ty, targs) = self.variant_type(&def, expect)?;
2909 return Ok(if def.simple && targs.is_empty() {
2910 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), Some(ty))
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2911 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago2912 // A unit variant of a generic enum has no argument to
2913 // infer the parameters from, so they are written out.
2914 Val::new(
2915 format!("{}{}()", def.ctor_ident(&v), targs),
2916 Some(ty),
2917 )
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2918 });
2919 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2920 // A `for` binding that stands for an element of the container
2921 // it came from: using it must read (and assigning through it
2922 // must write) that element, not a copy.
2923 if let Some(a) = self.lookup_alias(&name) {
2924 return Ok(match a {
2925 Alias::Value { code, ty } => Val::new(code, ty),
2926 // A window *is* a slice; as a value it is the view it
2927 // denotes, which is what Rust's `&[T]` means too.
2928 Alias::Window { code, off, len, elem } => Val::new(
2929 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
2930 elem.map(|e| Nim::OpenArray(Box::new(e))),
2931 ),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago2932 // An iterator is not a value here: it is consumed by a
2933 // `for`, or asked for its `.remainder()`.
2934 Alias::Iterator(_) => {
2935 return Err(format!(
2936 "`{name}` is an iterator; it can be iterated or asked \
2937 for its `remainder()`, but not used as a value"
2938 ))
2939 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2940 });
2941 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2942 if let Some(t) = self.lookup(&name) {
2943 return Ok(Val::new(ident(&name), Some(t)));
2944 }
2945 // A top-level function used as a value, e.g. passed to a
2946 // parameter of `impl Fn(..)` type.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago2947 if let Some(k) = self.resolve_fn(&p.path) {
2948 let sig = &self.fns[&k];
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2949 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 8h ago2950 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 9h ago2951 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2952 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2953 }
2954 Expr::Paren(p) => {
2955 let v = self.expr_at(&p.expr, expect)?;
2956 Ok(Val::new(format!("({})", v.code), v.ty))
2957 }
2958 Expr::Group(g) => self.expr_at(&g.expr, expect),
2959 // `&x` is a value in Nim; `&mut x` in an argument position binds to
2960 // a `var` parameter, which is also just `x` at the call site.
2961 Expr::Reference(r) => self.expr_at(&r.expr, expect),
2962 Expr::Unary(u) => self.unary(u, expect),
2963 Expr::Binary(b) => self.binary(b, expect),
2964 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2965 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
2966 let Expr::Range(r) = &*i.index else { unreachable!() };
2967 let base = self.expr(&i.expr)?;
2968 let lo = match &r.start {
2969 Some(e) => format!("int({})", self.expr(e)?.code),
2970 None => "0".into(),
2971 };
2972 // Nim's `toOpenArray` takes an inclusive upper bound.
2973 let hi = match (&r.end, r.limits) {
2974 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2975 format!("int({}) - 1", self.expr(e)?.code)
2976 }
2977 (Some(e), syn::RangeLimits::Closed(_)) => {
2978 format!("int({})", self.expr(e)?.code)
2979 }
2980 (None, _) => format!("{}.len - 1", base.code),
2981 };
2982 let elem = elem_of(&base.ty)
2983 .ok_or("cannot infer the element type of this slice")?;
2984 Ok(Val::new(
2985 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
2986 Some(Nim::OpenArray(Box::new(elem))),
2987 ))
2988 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2989 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2990 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
2991 let idx = self.expr(&i.index)?;
2992 return Ok(Val::new(
2993 format!("{}[{} + int({})]", code, off, idx.code),
2994 elem,
2995 ));
2996 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago2997 let base = self.expr(&i.expr)?;
2998 let idx = self.expr(&i.index)?;
2999 // Rust indexes with usize; Nim wants an `int`, and a `uint`
3000 // index is a type error there rather than a silent conversion.
3001 let idx_code = match &idx.ty {
3002 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
3003 _ => idx.code.clone(),
3004 };
3005 let elem = match base.ty.clone() {
3006 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
3007 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
3008 _ => None,
3009 };
3010 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
3011 }
3012 Expr::Field(f) => {
3013 let base = self.expr(&f.base)?;
3014 let name = match &f.member {
3015 syn::Member::Named(n) => n.to_string(),
3016 syn::Member::Unnamed(i) => format!("f{}", i.index),
3017 };
3018 let t = match &base.ty {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3019 Some(bt @ Nim::Named(s, _)) => self
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3020 .structs
3021 .get(s)
3022 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3023 .map(|(_, t)| self.subst_type_args(s, bt, t.clone())),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3024 _ => None,
3025 };
3026 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
3027 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago3028 // `unsafe` is a permission marker, not a semantic change: it does
3029 // not alter what the enclosed operations mean. So the block is
3030 // transparent here, and each operation inside still goes through
3031 // the ordinary lowering -- and is still rejected if it has no
3032 // faithful mapping.
3033 Expr::Unsafe(u) => match single_expr(&u.block) {
3034 Some(e) => self.expr_at(e, expect),
3035 None => Err("an `unsafe` block used as a value must be a single \
3036 expression"
3037 .into()),
3038 },
3039 Expr::Closure(c) => self.closure(c, expect),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3040 Expr::Try(t) => self.try_op(t),
3041 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago3042 Expr::MethodCall(m) => self.method(m, expect),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago3043 Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
3044 // `vec![..]`'s elements take their type from the annotation on
3045 // the binding, exactly as Rust's would.
3046 let want = match expect {
3047 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
3048 _ => None,
3049 };
3050 let saved = std::mem::replace(&mut self.vec_expect, want.clone());
3051 let code = self.macro_call(&m.mac);
3052 self.vec_expect = saved;
3053 let code = code?;
3054 let ty = match want {
3055 Some(e) => Some(Nim::Seq(Box::new(e))),
3056 None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
3057 };
3058 Ok(Val::new(code, ty))
3059 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3060 Expr::Macro(m) => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago3061 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 9h ago3062 let code = self.macro_call(&m.mac)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago3063 // A formatter write is a statement that appends, not a value.
3064 let ty = if is_write { Some(Nim::Unit) } else { None };
3065 Ok(Val::new(code, ty))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3066 }
3067 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3068 if s.rest.is_some() {
3069 return Err("struct update syntax `..rest` is not implemented yet".into());
3070 }
3071 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
3072 // which is constructed positionally in Nim.
3073 if let Some((def, v)) = self.resolve_variant(&s.path) {
3074 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
3075 let mut args = vec![String::new(); fields.len()];
3076 for f in &s.fields {
3077 let syn::Member::Named(m) = &f.member else {
3078 return Err("unsupported enum variant field".into());
3079 };
3080 let want = format!("{}_{}", v, m);
3081 let i = fields
3082 .iter()
3083 .position(|(n, _)| *n == want)
3084 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
3085 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
3086 }
3087 if let Some(i) = args.iter().position(|a| a.is_empty()) {
3088 return Err(format!(
3089 "`{}::{}` is missing field `{}`",
3090 def.name, v, fields[i].0
3091 ));
3092 }
3093 return Ok(Val::new(
3094 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
3095 Some(Nim::Named(def.name.clone(), vec![])),
3096 ));
3097 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3098 // `Self { .. }` inside an `impl` names the type being
3099 // implemented, and its fields are that type's fields.
3100 let name = match path_name(&s.path).as_str() {
3101 "Self" => self
3102 .self_ty
3103 .as_ref()
3104 .map(type_name)
3105 .ok_or("`Self` outside an `impl` block")?,
3106 other => other.to_string(),
3107 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3108 let mut parts = Vec::new();
3109 for f in &s.fields {
3110 let fname = match &f.member {
3111 syn::Member::Named(n) => n.to_string(),
3112 syn::Member::Unnamed(i) => format!("f{}", i.index),
3113 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3114 let want = self
3115 .structs
3116 .get(&name)
3117 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
3118 .map(|(_, t)| t.clone());
3119 let v = self.expr_at(&f.expr, want.as_ref())?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3120 parts.push(format!("{}: {}", ident(&fname), v.code));
3121 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3122 // Nim cannot infer an object's generic parameters from a
3123 // constructor's field values, so they are written out.
3124 let gp = self.type_generics.get(&name).cloned().unwrap_or_default();
3125 let ty = if gp.is_empty() {
3126 Nim::Named(name.clone(), vec![])
3127 } else {
3128 match expect {
3129 Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => {
3130 Nim::Named(name.clone(), a.clone())
3131 }
3132 _ => {
3133 return Err(format!(
3134 "`{name} {{ .. }}` is generic, and Nim cannot infer \
3135 its parameters from the field values; annotate the \
3136 binding or the return type"
3137 ))
3138 }
3139 }
3140 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3141 Ok(Val::new(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3142 format!("{}({})", ty.render(), parts.join(", ")),
3143 Some(ty),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3144 ))
3145 }
3146 Expr::Array(a) => {
3147 let mut parts = Vec::new();
3148 let mut elem = match expect {
3149 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
3150 Some((**t).clone())
3151 }
3152 _ => None,
3153 };
3154 for e in &a.elems {
3155 let want = elem.clone();
3156 let v = self.expr_at(e, want.as_ref())?;
3157 elem = elem.or(v.ty.clone());
3158 parts.push(v.code);
3159 }
3160 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
3161 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
3162 }
3163 Expr::Repeat(r) => {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago3164 // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size
3165 // array from a `seq`, so the expected type decides which, and
3166 // an array needs its elements written out.
3167 let want_elem = match expect {
3168 Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => {
3169 Some((**e).clone())
3170 }
3171 _ => None,
3172 };
3173 let v = self.expr_at(&r.expr, want_elem.as_ref())?;
3174 if let Some(Nim::Array(n, _)) = expect {
3175 let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect();
3176 let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t)));
3177 return Ok(Val::new(format!("[{}]", elems.join(", ")), t));
3178 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3179 let n = self.expr(&r.len)?;
3180 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
3181 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
3182 }
3183 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
3184 Expr::Tuple(t) => {
3185 let mut parts = Vec::new();
3186 let mut tys = Vec::new();
3187 for e in &t.elems {
3188 let v = self.expr(e)?;
3189 tys.push(v.ty.clone());
3190 parts.push(v.code);
3191 }
3192 let ty = tys
3193 .iter()
3194 .cloned()
3195 .collect::<Option<Vec<_>>>()
3196 .map(Nim::Tuple);
3197 Ok(Val::new(format!("({})", parts.join(", ")), ty))
3198 }
3199 // `if` and `match` are expressions in both languages, but only
3200 // when every arm is itself a single expression.
3201 Expr::If(i) => self.if_expr(i, expect),
3202 Expr::Block(b) if b.block.stmts.len() == 1 => {
3203 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
3204 self.expr_at(e, expect)
3205 } else {
3206 Err("block expression with statements in value position is not implemented yet".into())
3207 }
3208 }
3209 other => Err(format!(
3210 "unsupported expression in value position: {}",
3211 expr_kind(other)
3212 )),
3213 }
3214 }
3215
3216 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
3217 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
3218 return Err(
3219 "an `if` used as a value must have an `else` and single-expression arms".into(),
3220 );
3221 };
3222 let c = self.expr(&i.cond)?;
3223 let t = self.expr_at(then, expect)?;
3224 let want = expect.cloned().or_else(|| t.ty.clone());
3225 let e = match &**els {
3226 Expr::Block(b) => match single_expr(&b.block) {
3227 Some(x) => self.expr_at(x, want.as_ref())?,
3228 None => return Err("an `if` used as a value must have single-expression arms".into()),
3229 },
3230 other => self.expr_at(other, want.as_ref())?,
3231 };
3232 let ty = t.ty.clone().or(e.ty.clone());
3233 Ok(Val::new(
3234 format!("(if {}: {} else: {})", c.code, t.code, e.code),
3235 ty,
3236 ))
3237 }
3238
3239 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
3240 match l {
3241 Lit::Int(i) => {
3242 let suffix = i.suffix();
3243 if let Some(why) = ty::rejected(suffix) {
3244 return Err(format!("integer literal `{}`: {}", i, why));
3245 }
3246 let digits = i.base10_digits().to_string();
3247 // Rust's default for an unconstrained integer literal is i32.
3248 // Nim's is `int` (64-bit). Making the width explicit is what
3249 // keeps overflow behaviour the same on both sides.
3250 let t = if suffix.is_empty() {
3251 match expect {
3252 Some(t) if t.is_integer() => t.clone(),
3253 // Rust's fallback for an otherwise-unconstrained
3254 // integer literal.
3255 _ => Nim::Prim("int32".into()),
3256 }
3257 } else {
3258 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
3259 };
3260 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
3261 }
3262 Lit::Float(f) => {
3263 let t = match f.suffix() {
3264 "" => match expect {
3265 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
3266 _ => Nim::Prim("float64".into()),
3267 },
3268 "f64" => Nim::Prim("float64".into()),
3269 "f32" => Nim::Prim("float32".into()),
3270 s => return Err(format!("unknown float suffix `{s}`")),
3271 };
3272 let d = f.base10_digits();
3273 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
3274 Ok(Val::new(d, Some(t)))
3275 }
3276 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
3277 Lit::Str(s) => Ok(Val::new(
3278 fmt::nim_str(&s.value()),
3279 Some(Nim::Prim("string".into())),
3280 )),
3281 Lit::Char(c) => Ok(Val::new(
3282 format!("Rune({})", c.value() as u32),
3283 Some(Nim::Prim("Rune".into())),
3284 )),
3285 Lit::Byte(b) => Ok(Val::new(
3286 format!("{}'u8", b.value()),
3287 Some(Nim::Prim("uint8".into())),
3288 )),
3289 Lit::ByteStr(b) => {
3290 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
3291 Ok(Val::new(
3292 format!("@[{}]", bytes.join(", ")),
3293 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
3294 ))
3295 }
3296 other => Err(format!("unsupported literal: {other:?}")),
3297 }
3298 }
3299
3300 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
3301 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
3302 // the positive half of the range before the negation runs. Folding the
3303 // sign into the literal keeps `i8::MIN` and friends expressible.
3304 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
3305 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
3306 let v = self.lit_at(&l.lit, expect)?;
3307 return Ok(Val::new(format!("-{}", v.code), v.ty));
3308 }
3309 }
3310 let v = self.expr_at(&u.expr, expect)?;
3311 match u.op {
3312 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
3313 // Rust's `!` is logical on bool and bitwise-complement on integers.
3314 // Nim spells those `not` and `not` as well, so one mapping covers
3315 // both — but only because Nim overloads `not` the same way.
3316 UnOp::Not(_) => Ok(Val::new(format!("(not {})", v.code), v.ty)),
3317 UnOp::Deref(_) => Ok(v),
3318 _ => Err("unsupported unary operator".into()),
3319 }
3320 }
3321
3322 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
3323 // A comparison's operands are unrelated to the `bool` it produces, so
3324 // the outer expectation is not passed through to them.
3325 let down = match b.op {
3326 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3327 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
3328 _ => expect,
3329 };
3330 let mut l = self.expr_at(&b.left, down)?;
3331 // Rust unifies the two operand types; propagating whichever side is
3332 // known to the other reproduces that, and disagreement then surfaces
3333 // as a Nim type error rather than as a silent width change.
3334 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
3335 if l.ty.is_none() && r.ty.is_some() {
3336 l = self.expr_at(&b.left, r.ty.as_ref())?;
3337 }
3338 let r = std::mem::replace(&mut r, Val::untyped(""));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago3339 // A binary operator on a user type goes to that type's own impl.
3340 if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) {
3341 let want = self.op_param(&l.ty, binary_symbol(&b.op));
3342 let r = self.expr_at(&b.right, want.as_ref())?;
3343 let ret = self
3344 .methods
3345 .get(&(
3346 type_name(l.ty.as_ref().unwrap()),
3347 op_method(binary_symbol(&b.op)).to_string(),
3348 ))
3349 .map(|s| s.ret.clone());
3350 return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret));
3351 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3352 let op = self.bin_op(&b.op, &l, &r)?;
3353 let ty = match b.op {
3354 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3355 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
3356 // Rust's shift takes its result type from the *left* operand, and
3357 // the right may be a different width entirely.
3358 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
3359 _ => l.ty.clone().or(r.ty.clone()),
3360 };
3361 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
3362 }
3363
3364 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
3365 Ok(match op {
3366 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
3367 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
3368 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
3369 BinOp::Div(_) | BinOp::DivAssign(_) => {
3370 // Nim spells integer division `div`. Both languages truncate
3371 // toward zero, so once the right operator is chosen the
3372 // semantics match, including for negative operands.
3373 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3374 "cannot tell integer from float division here; annotate the operands",
3375 )?;
3376 if t.is_integer() { "div" } else { "/" }
3377 }
3378 BinOp::Rem(_) | BinOp::RemAssign(_) => {
3379 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3380 "cannot tell integer from float remainder here; annotate the operands",
3381 )?;
3382 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
3383 }
3384 BinOp::And(_) => "and",
3385 BinOp::Or(_) => "or",
3386 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
3387 // bools, exactly as Rust's `&`/`|`/`^` are.
3388 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
3389 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
3390 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
3391 // Settled empirically: Nim's `shr` on a signed integer is
3392 // arithmetic, matching Rust. See DESIGN.md.
3393 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
3394 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
3395 BinOp::Eq(_) => "==",
3396 BinOp::Ne(_) => "!=",
3397 BinOp::Lt(_) => "<",
3398 BinOp::Le(_) => "<=",
3399 BinOp::Gt(_) => ">",
3400 BinOp::Ge(_) => ">=",
3401 other => return Err(format!("unsupported binary operator {other:?}")),
3402 })
3403 }
3404
3405 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
3406 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3407 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3408 let from = v.ty.clone().ok_or_else(|| {
3409 format!(
3410 "cannot lower `as {}`: the source type is unknown, and `as` \
3411 truncates, so the source width decides the result",
3412 to.render()
3413 )
3414 })?;
3415
3416 let code = match (&from, &to) {
3417 (f, t) if f.is_integer() && t.is_integer() => {
3418 // Rust's `as` between integers is a pure bit-width truncation
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 7h ago3419 // or sign-extension, never a range check. `cast` says exactly
3420 // that. (Nim's `T(x)` turns out to truncate here as well --
3421 // see DESIGN.md item 5 -- but `cast` is the spelling that
3422 // means it rather than the one that happens to agree.)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3423 format!("cast[{}]({})", t.render(), v.code)
3424 }
3425 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
3426 format!("{}({})", p, v.code)
3427 }
3428 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
3429 format!("{}(ord({}))", t.render(), v.code)
3430 }
3431 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
3432 format!("cast[{}](int32({}))", t.render(), v.code)
3433 }
3434 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
3435 format!("Rune(int32({}))", v.code)
3436 }
3437 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
3438 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
3439 // Rust saturates float->int casts; Nim rounds and range-errors.
3440 // Not the same operation, so it is refused rather than mapped.
3441 return Err(format!(
3442 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
3443 no faithful mapping is implemented",
3444 t.render()
3445 ));
3446 }
3447 (f, t) => {
3448 return Err(format!(
3449 "unsupported cast from `{}` to `{}`",
3450 f.render(),
3451 t.render()
3452 ))
3453 }
3454 };
3455 Ok(Val::new(code, Some(to)))
3456 }
3457
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3458 /// Rust's `?`: return early on the error branch, otherwise yield the value.
3459 ///
3460 /// The early return is statements, not an expression, so they are emitted
3461 /// ahead of the line being built. Every caller lowers its sub-expressions
3462 /// 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 8h ago3463 /// The container, start offset, length and element type an expression
3464 /// denotes as a slice. A window alias contributes its own offset, so
3465 /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
3466 /// into the original buffer rather than through a rebuilt view.
3467 fn slice_parts(
3468 &mut self,
3469 e: &Expr,
3470 ) -> Result<(String, String, String, Option<Nim>), String> {
3471 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
3472 return Ok((code, off, len, elem));
3473 }
3474 let v = self.expr(e)?;
3475 let len = format!("{}.len", v.code);
3476 Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
3477 }
3478
3479 /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
3480 fn map_closure(
3481 &mut self,
3482 what: &str,
3483 recv: &Val,
3484 kind: &str,
3485 targs: &[Nim],
3486 c: &syn::ExprClosure,
3487 ) -> Result<Val, String> {
3488 if c.capture.is_some() {
3489 return Err("a `move` closure captures by value; Nim's closures \
3490 capture by reference, and the two are not the same"
3491 .into());
3492 }
3493 if c.inputs.len() != 1 {
3494 return Err(format!("`.{what}()` takes a one-argument closure"));
3495 }
3496 let pname = match &c.inputs[0] {
3497 Pat::Ident(i) => i.ident.to_string(),
3498 Pat::Wild(_) => "unused0".into(),
3499 _ => return Err("only plain identifier closure parameters are supported".into()),
3500 };
3501
3502 let is_opt = kind == "Option";
3503 let tmp = self.fresh("Map");
3504 let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
3505 self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
3506
3507 let body = match &*c.body {
3508 Expr::Block(b) => single_expr(&b.block)
3509 .ok_or("a closure body with statements is not implemented yet")?,
3510 other => other,
3511 };
3512 self.push_scope();
3513 // The parameter names the payload itself, so a view stays a view.
3514 self.bind_alias(
3515 &pname,
3516 Alias::Value {
3517 code: format!("{}.val", tmp),
3518 ty: Some(targs[0].clone()),
3519 },
3520 );
3521 let v = self.expr(body)?;
3522 self.pop_scope();
3523
3524 let inner = v
3525 .ty
3526 .clone()
3527 .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
3528 // `and_then`'s closure already returns the wrapped type; `map`'s does
3529 // not and has to be re-wrapped.
3530 let (test, some_branch, none_branch, out_ty) = if is_opt {
3531 let out = if what == "map" {
3532 Nim::Named("Option".into(), vec![inner.clone()])
3533 } else {
3534 inner.clone()
3535 };
3536 let body_code = if what == "map" {
3537 format!("rsSome[{}]({})", inner.render(), v.code)
3538 } else {
3539 v.code.clone()
3540 };
3541 (
3542 format!("{}.has", tmp),
3543 body_code,
3544 format!("rsNone[{}]()", elem_arg(&out).render()),
3545 out,
3546 )
3547 } else {
3548 let e = targs[1].clone();
3549 let out = if what == "map" {
3550 Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
3551 } else {
3552 inner.clone()
3553 };
3554 let ok_ty = elem_arg(&out);
3555 let body_code = if what == "map" {
3556 format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
3557 } else {
3558 v.code.clone()
3559 };
3560 (
3561 format!("{}.ok", tmp),
3562 body_code,
3563 format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
3564 out,
3565 )
3566 };
3567 Ok(Val::new(
3568 format!("(if {}: {} else: {})", test, some_branch, none_branch),
3569 Some(out_ty),
3570 ))
3571 }
3572
3573 /// `|x| x + 1` -> a Nim anonymous proc.
3574 ///
3575 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
3576 /// A `move` closure captures by value, which is a different thing, so it
3577 /// is rejected rather than lowered to the same construct.
3578 fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
3579 if c.capture.is_some() {
3580 return Err("a `move` closure captures by value; Nim's closures \
3581 capture by reference, and the two are not the same"
3582 .into());
3583 }
3584 let want: Option<&Vec<Nim>> = match expect {
3585 Some(Nim::Proc(a, _)) => Some(a),
3586 _ => None,
3587 };
3588
3589 self.push_scope();
3590 let mut parts = Vec::new();
3591 let mut ptys = Vec::new();
3592 for (i, p) in c.inputs.iter().enumerate() {
3593 let (name, ann) = match p {
3594 Pat::Ident(id) => (id.ident.to_string(), None),
3595 Pat::Type(t) => match &*t.pat {
3596 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
3597 _ => return Err("only plain identifier closure parameters are supported".into()),
3598 },
3599 Pat::Wild(_) => (format!("unused{i}"), None),
3600 _ => return Err("only plain identifier closure parameters are supported".into()),
3601 };
3602 let t = ann
3603 .or_else(|| want.and_then(|w| w.get(i).cloned()))
3604 .ok_or_else(|| {
3605 format!(
3606 "cannot infer the type of closure parameter `{name}`; \
3607 annotate it"
3608 )
3609 })?;
3610 parts.push(format!("{}: {}", ident(&name), t.render()));
3611 self.bind(&name, t.clone());
3612 ptys.push(t);
3613 }
3614
3615 let ret_ann = match &c.output {
3616 ReturnType::Default => None,
3617 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
3618 };
3619 let body = match &*c.body {
3620 Expr::Block(b) => single_expr(&b.block)
3621 .ok_or("a closure body with statements is not implemented yet")?,
3622 other => other,
3623 };
3624 let v = self.expr_at(body, ret_ann.as_ref())?;
3625 self.pop_scope();
3626
3627 let ret = ret_ann
3628 .or_else(|| v.ty.clone())
3629 .ok_or("cannot infer a closure's return type; annotate it")?;
3630 Ok(Val::new(
3631 format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
3632 Some(Nim::Proc(ptys, Box::new(ret))),
3633 ))
3634 }
3635
3636 /// Lower a block's statements at the current indentation, without opening
3637 /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
3638 /// of its own in the generated code.
3639 fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
3640 self.push_scope();
3641 let tail = self.block_body(b)?;
3642 self.emit_tail(tail);
3643 self.pop_scope();
3644 Ok(())
3645 }
3646
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3647 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
3648 if self.in_loop_cond {
3649 return Err("`?` in a loop condition is not implemented yet: the \
3650 early-return it expands to would be evaluated once, \
3651 before the loop, rather than on each iteration"
3652 .into());
3653 }
3654 let v = self.expr(&t.expr)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago3655 if self.fmt_param.is_some() {
3656 // Writing into a string cannot fail, so `?` on a formatter write
3657 // is a no-op. `?` on anything else can fail, and `format!` panics
3658 // when a formatting impl returns an error -- so that is what the
3659 // error branch does here, with std's own message.
3660 if v.ty.as_ref() == Some(&Nim::Unit) {
3661 return Ok(v);
3662 }
3663 if let Some(Nim::Named(n, a)) = v.ty.clone() {
3664 if n == "Result" && a.len() == 2 {
3665 let tmp = self.fresh("Fmt");
3666 self.line(&format!(
3667 "let {}: {} = {}",
3668 tmp,
3669 Nim::Named(n, a.clone()).render(),
3670 v.code
3671 ));
3672 self.line(&format!("if not {}.ok:", tmp));
3673 self.line(
3674 " rsPanic(\"a formatting trait implementation returned an error\")",
3675 );
3676 return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone())));
3677 }
3678 }
3679 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago3680 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
3681 // An `Option`/`Result` of a view: the check is emitted here and the
3682 // view itself survives as an alias, since it has no value form.
3683 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
3684 let err = v.guard_err.clone().ok_or(
3685 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
3686 )?;
3687 let Nim::Named(n, ra) = &ret else {
3688 return Err(format!("`?` in a function returning `{}`", ret.render()));
3689 };
3690 if n != "Result" || ra.len() != 2 {
3691 return Err(format!("`?` in a function returning `{}`", ret.render()));
3692 }
3693 self.line(&format!("if not {}:", guard));
3694 self.line(&format!(
3695 " return rsErr[{}, {}]({})",
3696 ra[0].render(),
3697 ra[1].render(),
3698 err
3699 ));
3700 let mut out = Val::new(String::new(), None);
3701 out.window = Some(w);
3702 return Ok(out);
3703 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3704 let vt = v.ty.clone().ok_or(
3705 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
3706 )?;
3707 let ret = self
3708 .ret
3709 .clone()
3710 .ok_or("`?` outside a function with a return type")?;
3711 let tmp = self.fresh("Try");
3712 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
3713
3714 match (&vt, &ret) {
3715 (Nim::Named(a, ai), Nim::Named(b, bi))
3716 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
3717 {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago3718 // Rust inserts a `From::from` on the error here. Where the
3719 // types differ we call the crate's own `impl From`; we never
3720 // assume the conversion is the identity.
3721 let err = if ai[1] == bi[1] {
3722 format!("{}.err", tmp)
3723 } else {
3724 let key = (type_name(&ai[1]), type_name(&bi[1]));
3725 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
3726 format!(
3727 "`?` needs `From<{}> for {}` to convert the error, and \
3728 no such `impl` is in scope; assuming the conversion is \
3729 the identity would be a guess",
3730 key.0, key.1
3731 )
3732 })?;
3733 format!("{}({}.err)", f, tmp)
3734 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3735 self.line(&format!("if not {}.ok:", tmp));
3736 self.line(&format!(
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago3737 " return rsErr[{}, {}]({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3738 bi[0].render(),
3739 bi[1].render(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago3740 err
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3741 ));
3742 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3743 }
3744 (Nim::Named(a, ai), Nim::Named(b, bi))
3745 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
3746 {
3747 self.line(&format!("if not {}.has:", tmp));
3748 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
3749 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
3750 }
3751 _ => Err(format!(
3752 "`?` on `{}` in a function returning `{}` is not a supported \
3753 combination",
3754 vt.render(),
3755 ret.render()
3756 )),
3757 }
3758 }
3759
3760 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 9h ago3761 let Expr::Path(p) = &*c.func else {
3762 return Err("only calls to named functions are supported".into());
3763 };
3764 let name = path_name(&p.path);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago3765 let target = self.resolve_fn(&p.path);
3766 let ptys: Vec<Nim> = target
3767 .as_ref()
3768 .and_then(|k| self.fns.get(k))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3769 .map(|s| s.params.clone())
3770 .unwrap_or_default();
3771 let mut args = Vec::new();
3772 for (i, a) in c.args.iter().enumerate() {
3773 let want = ptys.get(i).cloned();
3774 args.push(self.expr_at(a, want.as_ref())?);
3775 }
3776 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
3777
3778 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3779 // `Ok`/`Err` must name the *whole* Result type, not just the half
3780 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
3781 match name.as_str() {
3782 "Some" => {
3783 let inner = match expect {
3784 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
3785 _ => {
3786 return Err("`Some(..)` needs a known `Option<T>` type here; \
3787 annotate the binding or the return type"
3788 .into())
3789 }
3790 };
3791 return Ok(Val::new(
3792 format!("rsSome[{}]({})", inner, codes.join(", ")),
3793 expect.cloned(),
3794 ));
3795 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago3796 "Ok" if self.fmt_param.is_some()
3797 && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) =>
3798 {
3799 // `Ok(())` ends a `fmt` body: nothing more is written.
3800 return Ok(Val::new(String::new(), Some(Nim::Unit)));
3801 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3802 "Ok" | "Err" => {
3803 let (t, e) = match expect {
3804 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
3805 (a[0].render(), a[1].render())
3806 }
3807 _ => {
3808 return Err(format!(
3809 "`{name}(..)` needs a known `Result<T, E>` type here; \
3810 annotate the binding or the return type"
3811 ))
3812 }
3813 };
3814 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
3815 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
3816 return Ok(Val::new(
3817 format!("{}[{}, {}]({})", ctor, t, e, arg),
3818 expect.cloned(),
3819 ));
3820 }
3821 _ => {}
3822 }
3823
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago3824 // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's
3825 // object constructor names its fields even when Rust's does not.
3826 if let Some(fields) = self.structs.get(&name).cloned() {
3827 if fields.len() == c.args.len() {
3828 let mut parts = Vec::new();
3829 for (i, a) in c.args.iter().enumerate() {
3830 let v = self.expr_at(a, Some(&fields[i].1))?;
3831 parts.push(format!("{}: {}", ident(&fields[i].0), v.code));
3832 }
3833 return Ok(Val::new(
3834 format!("{}({})", ident(&name), parts.join(", ")),
3835 Some(Nim::Named(name.clone(), vec![])),
3836 ));
3837 }
3838 }
3839
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3840 // `Spacing::from(d)`: a `From` impl called through its target type.
3841 // Rust picks the impl by the argument's type, and so do we -- Nim
3842 // cannot overload on return type, so each impl has its own proc name.
3843 if name == "from" && codes.len() == 1 {
3844 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3845 let q = if q == "Self" {
3846 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3847 } else {
3848 q
3849 };
3850 if let Some(src) = args[0].ty.as_ref().map(type_name) {
3851 if let Some(f) = self.from_impls.get(&(src, q.clone())).cloned() {
3852 return Ok(Val::new(
3853 format!("{}({})", f, codes[0]),
3854 Some(Nim::Named(q, vec![])),
3855 ));
3856 }
3857 }
3858 }
3859 }
3860
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago3861 // `u32::from(b)`: `From` between primitives is lossless by definition
3862 // -- it is the widening direction only -- so a plain Nim conversion is
3863 // exact. (The truncating direction is `as`, which is `cast`.)
3864 if name == "from" && codes.len() == 1 {
3865 if let Some(q) = p.path.segments.iter().rev().nth(1) {
3866 if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) {
3867 return Ok(Val::new(
3868 format!("{}({})", t, codes[0]),
3869 Some(Nim::Prim(t)),
3870 ));
3871 }
3872 }
3873 }
3874
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago3875 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
3876 // string view; no copy, no validation, same memory.
3877 if name == "from_utf8_unchecked" && codes.len() == 1 {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago3878 // `String::from_utf8_unchecked(v)` takes ownership and yields an
3879 // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields
3880 // a view. Same name, different operations -- the qualifier says
3881 // which, and an unqualified call is ambiguous.
3882 let q = p
3883 .path
3884 .segments
3885 .iter()
3886 .rev()
3887 .nth(1)
3888 .map(|s| s.ident.to_string());
3889 return match q.as_deref() {
3890 Some("String") => Ok(Val::new(
3891 format!("rsStringOf({})", codes[0]),
3892 Some(Nim::Prim("string".into())),
3893 )),
3894 Some("str") => Ok(Val::new(
3895 format!("rsStrView({})", codes[0]),
3896 Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
3897 )),
3898 _ => Err(
3899 "`from_utf8_unchecked` must be written as `str::..` (a \
3900 borrowed view) or `String::..` (an owned string); the two \
3901 are different operations"
3902 .into(),
3903 ),
3904 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago3905 }
3906
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3907 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
3908 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3909 let (ty, _) = self.variant_type(&def, expect)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3910 return Ok(Val::new(
3911 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3912 Some(ty),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3913 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3914 }
3915
3916 // A bare path that names a primitive type is Rust's tuple-struct-like
3917 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3918 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
3919 // is invoked.
3920 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
3921 return Ok(Val::new(
3922 format!("{}({})", ident(&name), codes.join(", ")),
3923 Some((*ret).clone()),
3924 ));
3925 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago3926 // `Adler32::new()` / `Adler32::default()`: a method called through
3927 // its type rather than through a receiver.
3928 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3929 // `Self::new()` inside an `impl` names the type being implemented.
3930 let q = if q == "Self" {
3931 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3932 } else {
3933 q
3934 };
3935 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3936 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
3937 let ret = Self::instantiate(sig, &arg_tys);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago3938 let nim = self
3939 .statics
3940 .get(&(q.clone(), name.clone()))
3941 .cloned()
3942 .unwrap_or_else(|| ident(&name));
3943 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
3944 }
3945 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago3946 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
3947 let ret = target
3948 .as_ref()
3949 .and_then(|k| self.fns.get(k))
3950 .map(|sig| Self::instantiate(sig, &arg_tys));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3951 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 9h ago3952 return Err(format!(
3953 "call to unknown function `{name}`; only functions defined in \
3954 this file and the supported standard-library subset can be lowered"
3955 ));
3956 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago3957 let nim = match &target {
3958 Some((m, n)) => self.fn_name(m, n),
3959 None => ident(&name),
3960 };
3961 Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago3962 }
3963
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago3964 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 9h ago3965 let name = m.method.to_string();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago3966 // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield.
3967 if name == "remainder" && m.args.is_empty() {
3968 if let Expr::Path(p) = &*m.receiver {
3969 if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) {
3970 if let Iter::Chunks { code, base, len, k, elem, .. } = &*it {
3971 let kept = format!("(({} div int({})) * int({}))", len, k, k);
3972 let mut v = Val::new(
3973 String::new(),
3974 elem.clone().map(|e| Nim::OpenArray(Box::new(e))),
3975 );
3976 v.window = Some(Alias::Window {
3977 code: code.clone(),
3978 off: format!("({} + {})", base, kept),
3979 len: format!("({} - {})", len, kept),
3980 elem: elem.clone(),
3981 });
3982 return Ok(v);
3983 }
3984 return Err(
3985 "`.remainder()` is only defined for a `chunks_exact` iterator".into(),
3986 );
3987 }
3988 }
3989 return Err("`.remainder()` needs an iterator bound by `let`".into());
3990 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago3991 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
3992 match name.as_str() {
3993 "len" => {
3994 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
3995 }
3996 "is_empty" => {
3997 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
3998 }
3999 other => {
4000 return Err(format!(
4001 "`.{other}()` on a slice window from `chunks_exact`/\
4002 `windows` is not implemented; only indexing and \
4003 `len()` are"
4004 ))
4005 }
4006 }
4007 }
4008 let recv = self.expr(&m.receiver)?;
4009 let rt0 = recv.ty.clone();
4010
4011// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
4012 // way to put a view in an object, so instead of materialising an
4013 // Option the view and its validity condition travel together until
4014 // an `ok_or`/`?`/`unwrap` resolves them.
4015 if matches!(name.as_str(), "get" | "get_mut")
4016 && matches!(m.args.first(), Some(Expr::Range(_)))
4017 {
4018 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 8h ago4019 let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4020 let lo = match &r.start {
4021 Some(e) => format!("int({})", self.expr(e)?.code),
4022 None => "0".into(),
4023 };
4024 let len = match (&r.end, r.limits) {
4025 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
4026 format!("(int({}) - {})", self.expr(e)?.code, lo)
4027 }
4028 (Some(e), syn::RangeLimits::Closed(_)) => {
4029 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
4030 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4031 (None, _) => format!("({} - {})", blen, lo),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4032 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4033 // Hoisted, so the bounds are computed once -- as Rust computes
4034 // them once -- and cannot be re-evaluated later in a scope where
4035 // the names they mention have been shadowed by a loop pattern.
4036 let off_t = self.fresh("Off");
4037 let len_t = self.fresh("Len");
4038 self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
4039 self.line(&format!("let {}: int = {}", len_t, len));
4040 let elem = belem
4041 .or_else(|| elem_of(&rt0))
4042 .ok_or("cannot infer the element type of this slice")?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4043 let mut v = Val::new(
4044 String::new(),
4045 Some(Nim::Named(
4046 "Option".into(),
4047 vec![Nim::OpenArray(Box::new(elem.clone()))],
4048 )),
4049 );
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4050 v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4051 v.window = Some(Alias::Window {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4052 code,
4053 off: off_t,
4054 len: len_t,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4055 elem: Some(elem),
4056 });
4057 return Ok(v);
4058 }
4059
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4060 // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
4061 // parameter type comes from the receiver, so they are handled before
4062 // the arguments are lowered. The closure is expanded inline, with its
4063 // parameter aliased to the payload: that keeps the whole thing an
4064 // expression and avoids handing a view to a generic proc.
4065 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
4066 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
4067 (recv.ty.clone(), &m.args[0])
4068 {
4069 if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
4070 {
4071 return self.map_closure(&name, &recv, &kind, &targs, c);
4072 }
4073 }
4074 }
4075
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4076 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
4077 // own type; `v.push(e)` takes the element type.
4078 let arg_want = match (name.as_str(), &recv.ty) {
4079 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
4080 (_, t) => t.clone(),
4081 };
4082 let mut args = Vec::new();
4083 for a in &m.args {
4084 args.push(self.expr_at(a, arg_want.as_ref())?);
4085 }
4086 let a0 = args.first().map(|a| a.code.clone());
4087 let rt = recv.ty.clone();
4088
4089 let (code, ty) = match name.as_str() {
4090 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
4091 // explicit so that a `usize` binding type-checks on the Nim side.
4092 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
4093 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
4094 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
4095 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
4096 | "into_iter" => (recv.code.clone(), rt.clone()),
4097 "unwrap" | "expect" => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago4098 // Expanded inline rather than called as a generic proc: when
4099 // the payload is a view, Nim can only borrow from a path
4100 // expression, which a proc body containing the panic is not.
4101 let (kind, inner) = match &rt {
4102 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
4103 ("Option", a[0].clone())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4104 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago4105 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4106 ("Result", a[0].clone())
4107 }
4108 _ => {
4109 return Err(format!(
4110 "`.{name}()` needs a known `Option`/`Result` receiver type"
4111 ))
4112 }
4113 };
4114 if self.in_loop_cond {
4115 return Err(format!(
4116 "`.{name}()` in a loop condition is not implemented yet: the \
4117 check it expands to would run once, before the loop"
4118 ));
4119 }
4120 let tmp = self.fresh("Unwrap");
4121 let rty = rt.clone().unwrap();
4122 self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code));
4123 let (test, msg) = if kind == "Option" {
4124 (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value")
4125 } else {
4126 (format!("{}.ok", tmp), "called `Result::unwrap()` on an `Err` value")
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4127 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago4128 let msg = if name == "expect" {
4129 args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg))
4130 } else {
4131 fmt::nim_str(msg)
4132 };
4133 self.line(&format!("if not {}:", test));
4134 self.line(&format!(" rsPanic({})", msg));
4135 // If the payload is a view, hand back an alias rather than a
4136 // value: Nim will not let a `let` borrow out of a local, and a
4137 // view is a reference anyway, so there is nothing to bind.
4138 // `{tmp}.val` is a plain field access, so substituting it at
4139 // each use re-evaluates nothing.
4140 if matches!(inner, Nim::OpenArray(_)) {
4141 let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone()));
4142 v.window = Some(Alias::Value {
4143 code: format!("{}.val", tmp),
4144 ty: Some(inner),
4145 });
4146 return Ok(v);
4147 }
4148 (format!("{}.val", tmp), Some(inner))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4149 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4150 "ok_or" if recv.guard.is_some() => {
4151 let e = args.first().ok_or("`ok_or` takes one argument")?;
4152 let ety = e.ty.clone();
4153 let mut v = recv.clone();
4154 v.guard_err = Some(e.code.clone());
4155 v.ty = match (&recv.ty, ety) {
4156 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
4157 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
4158 }
4159 _ => None,
4160 };
4161 return Ok(v);
4162 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago4163 "ok_or" => {
4164 let inner = match &rt {
4165 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
4166 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
4167 };
4168 let e = args.first().ok_or("`ok_or` takes one argument")?;
4169 let ety = e
4170 .ty
4171 .clone()
4172 .ok_or("`ok_or` needs a known error type for its argument")?;
4173 (
4174 format!(
4175 "rsOkOr[{}, {}]({}, {})",
4176 inner.render(),
4177 ety.render(),
4178 recv.code,
4179 e.code
4180 ),
4181 Some(Nim::Named("Result".into(), vec![inner, ety])),
4182 )
4183 }
4184 "unwrap_or" => {
4185 let inner = match &rt {
4186 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
4187 Some(a[0].clone())
4188 }
4189 _ => None,
4190 };
4191 (
4192 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
4193 inner,
4194 )
4195 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4196 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
4197 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
4198 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
4199 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
4200
4201 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
4202 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
4203 // Nim raises OverflowDefect, so the operation is routed through
4204 // the unsigned view of the same width, which is what Rust's
4205 // wrapping_* is defined to compute.
4206 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
4207 let op = match name.as_str() {
4208 "wrapping_add" => "+",
4209 "wrapping_sub" => "-",
4210 _ => "*",
4211 };
4212 let t = rt.clone().ok_or_else(|| {
4213 format!("`{name}` needs a known receiver type to pick the wrapping width")
4214 })?;
4215 if !t.is_integer() {
4216 return Err(format!("`{name}` on a non-integer type"));
4217 }
4218 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
4219 if t.is_unsigned() {
4220 (format!("({} {} {})", recv.code, op, arg), Some(t))
4221 } else {
4222 let u = unsigned_peer(&t)?;
4223 (
4224 format!(
4225 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
4226 t.render(), u, recv.code, op, u, arg
4227 ),
4228 Some(t),
4229 )
4230 }
4231 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4232 // Inside a formatting impl, a write through the `Formatter` *is*
4233 // 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 7h ago4234 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => {
4235 let a = args.first().ok_or("`write_str` takes one argument")?;
4236 // A `&str` argument is a character view, not a Nim string.
4237 let text = match &a.ty {
4238 Some(Nim::Prim(p)) if p == "string" => a.code.clone(),
4239 _ => format!("rsDisplay({})", a.code),
4240 };
4241 (format!("result.add({})", text), Some(Nim::Unit))
4242 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago4243 "saturating_add" | "saturating_sub" | "saturating_mul" | "checked_add"
4244 | "checked_sub" | "checked_mul" => {
4245 let t = rt
4246 .clone()
4247 .filter(|t| t.is_integer())
4248 .ok_or_else(|| format!("`{name}` needs a known integer receiver"))?;
4249 let arg = args
4250 .first()
4251 .ok_or_else(|| format!("`{name}` takes one argument"))?;
4252 let f = match name.as_str() {
4253 "saturating_add" => "rsSatAdd",
4254 "saturating_sub" => "rsSatSub",
4255 "saturating_mul" => "rsSatMul",
4256 "checked_add" => "rsChkAdd",
4257 "checked_sub" => "rsChkSub",
4258 _ => "rsChkMul",
4259 };
4260 let out = if name.starts_with("checked") {
4261 Nim::Named("Option".into(), vec![t])
4262 } else {
4263 t
4264 };
4265 (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
4266 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4267 "abs" => (format!("abs({})", recv.code), rt.clone()),
4268 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4269 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4270 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
4271 "as_bytes" | "into_bytes" => (
4272 format!("rsBytes({})", recv.code),
4273 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
4274 ),
4275
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4276 "into" => {
4277 // `.into()` resolves through the `impl From` declarations, and
4278 // needs the target type to pick one.
4279 let from = rt
4280 .clone()
4281 .ok_or("`.into()` needs a known receiver type")?;
4282 let to = expect
4283 .ok_or("`.into()` needs a known target type; annotate the binding")?;
4284 let key = (type_name(&from), type_name(to));
4285 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4286 format!(
4287 "no `impl From<{}> for {}` in this file, so `.into()` has \
4288 no conversion to call",
4289 key.0, key.1
4290 )
4291 })?;
4292 (format!("{}({})", f, recv.code), Some(to.clone()))
4293 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4294 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4295 // A method defined in this file via `impl`, found by the
4296 // receiver's type rather than by name alone.
4297 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 6h ago4298 // Re-lower the arguments with the declared parameter types:
4299 // a method's own signature says what width its literals are,
4300 // which the receiver's type does not.
4301 let declared: Option<Vec<Nim>> = key
4302 .as_ref()
4303 .and_then(|k| self.methods.get(k))
4304 .map(|s| s.params.clone());
4305 if let Some(d) = &declared {
4306 // params[0] is the receiver for a method with `self`.
4307 let skip = usize::from(d.len() == m.args.len() + 1);
4308 for (i, a) in m.args.iter().enumerate() {
4309 if let Some(want) = d.get(i + skip) {
4310 let want = want.clone().unvar();
4311 args[i] = self.expr_at(a, Some(&want))?;
4312 }
4313 }
4314 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago4315 let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
4316 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago4317 let sig = key
4318 .as_ref()
4319 .and_then(|k| self.methods.get(k))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 6h ago4320 .map(|s| Self::instantiate(s, &arg_tys));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4321 if let Some(ret) = sig {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago4322 // Use the name the proc was actually emitted under: an
4323 // inherent method is qualified by its module, a trait
4324 // method by its trait.
4325 let nim = key
4326 .and_then(|k| self.statics.get(&k).cloned())
4327 .unwrap_or_else(|| ident(&name));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4328 let mut all = vec![recv.code.clone()];
4329 all.extend(args.iter().map(|a| a.code.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago4330 (format!("{}({})", nim, all.join(", ")), Some(ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4331 } else {
4332 return Err(format!(
4333 "unsupported method `.{name}()`; it is neither defined in \
4334 this file nor part of the standard-library subset that \
4335 has a verified Nim equivalent"
4336 ));
4337 }
4338 }
4339 };
4340 Ok(Val::new(code, ty))
4341 }
4342
4343 // -------------------------------------------------------------- macros
4344
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4345 /// The element type of a `vec![..]`, from its first element.
4346 fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
4347 let body = mac.tokens.to_string();
4348 if body.trim().is_empty() {
4349 return Ok(None);
4350 }
4351 let first: Option<Expr> = if body.contains(';') {
4352 // The whole body must be consumed or the parse fails, so the
4353 // length is parsed too even though only the element is wanted.
4354 mac.parse_body_with(|input: syn::parse::ParseStream| {
4355 let v: Expr = input.parse()?;
4356 input.parse::<syn::Token![;]>()?;
4357 let _len: Expr = input.parse()?;
4358 Ok(v)
4359 })
4360 .ok()
4361 } else {
4362 mac.parse_body_with(
4363 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4364 )
4365 .ok()
4366 .and_then(|p| p.into_iter().next())
4367 };
4368 match first {
4369 Some(e) => Ok(self.expr(&e)?.ty),
4370 None => Ok(None),
4371 }
4372 }
4373
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4374 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
4375 let name = path_name(&mac.path);
4376 match name.as_str() {
4377 "println" | "print" | "eprintln" | "eprint" => {
4378 let s = self.format_args(mac)?;
4379 let nl = name.ends_with("ln");
4380 Ok(match (name.starts_with('e'), nl) {
4381 (false, true) => format!("echo {s}"),
4382 (false, false) => format!("stdout.write({s})"),
4383 (true, true) => format!("stderr.writeLine({s})"),
4384 (true, false) => format!("stderr.write({s})"),
4385 })
4386 }
4387 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4388 "write" | "writeln" => {
4389 // `write!(f, "..", ..)` inside a formatting impl: the first
4390 // argument is the sink, the rest is an ordinary format call.
4391 let args: Vec<Expr> = mac
4392 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
4393 .map_err(|e| format!("write!: {e}"))?
4394 .into_iter()
4395 .collect();
4396 let sink = args.first().ok_or("`write!` needs a sink")?;
4397 if !self.is_fmt_param(sink) {
4398 return Err("`write!` to anything but the `Formatter` of the \
4399 enclosing formatting impl is not implemented"
4400 .into());
4401 }
4402 let s = self.format_pieces(&args[1..])?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago4403 let s = if name == "writeln" {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4404 format!("({} & \"\\n\")", s)
4405 } else {
4406 s
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago4407 };
4408 Ok(format!("result.add({})", s))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4409 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4410 "panic" => {
4411 let s = self.format_args(mac)?;
4412 Ok(format!("rsPanic({s})"))
4413 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago4414 // `debug_assert*` fires in debug builds, which is the profile
4415 // this project models, so it lowers the same as `assert*`.
4416 "assert" | "debug_assert" => {
4417 let args: Vec<Expr> = mac
4418 .parse_body_with(
4419 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4420 )
4421 .map_err(|e| format!("{name}!: {e}"))?
4422 .into_iter()
4423 .collect();
4424 let cond = args.first().ok_or("`assert!` needs a condition")?;
4425 let v = self.expr(cond)?;
4426 let msg = if args.len() > 1 {
4427 self.format_pieces(&args[1..])?
4428 } else {
4429 fmt::nim_str("assertion failed")
4430 };
4431 Ok(format!("(if not ({}): rsPanic({}))", v.code, msg))
4432 }
4433 "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => {
4434 let args: Vec<Expr> = mac
4435 .parse_body_with(
4436 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4437 )
4438 .map_err(|e| format!("{name}!: {e}"))?
4439 .into_iter()
4440 .collect();
4441 if args.len() < 2 {
4442 return Err(format!("`{name}!` takes two operands"));
4443 }
4444 let a = self.expr(&args[0])?;
4445 let b = self.expr_at(&args[1], a.ty.as_ref())?;
4446 let ne = name.ends_with("_ne");
4447 let op = if ne { "!=" } else { "==" };
4448 // Rust's message shows both sides; reproducing it keeps a
4449 // failing assertion as informative as the original.
4450 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 9h ago4451 Ok(format!(
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago4452 "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))",
4453 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 9h ago4454 ))
4455 }
4456 "vec" => {
4457 let body = mac.tokens.to_string();
4458 if body.trim().is_empty() {
4459 return Ok("@[]".into());
4460 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago4461 // `vec![elem; n]` is the repeat form, not a list. The macro
4462 // body has no brackets, so it is parsed directly.
4463 if body.contains(';') {
4464 let (v, n) = mac
4465 .parse_body_with(|input: syn::parse::ParseStream| {
4466 let v: Expr = input.parse()?;
4467 input.parse::<syn::Token![;]>()?;
4468 let n: Expr = input.parse()?;
4469 Ok((v, n))
4470 })
4471 .map_err(|e| format!("vec![elem; n]: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4472 let want = self.vec_expect.clone();
4473 let v = self.expr_at(&v, want.as_ref())?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago4474 let n = self.expr(&n)?;
4475 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
4476 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4477 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
4478 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
4479 .map_err(|e| format!("vec!: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4480 let want = self.vec_expect.clone();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4481 let mut parts = Vec::new();
4482 for e in &elems {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4483 parts.push(self.expr_at(e, want.as_ref())?.code);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4484 }
4485 Ok(format!("@[{}]", parts.join(", ")))
4486 }
4487 other => Err(format!(
4488 "unsupported macro `{other}!`; a macro whose expansion is not \
4489 known cannot be lowered faithfully"
4490 )),
4491 }
4492 }
4493
4494 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
4495 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 8h ago4496 let args: Vec<Expr> = mac
4497 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
4498 .map_err(|e| format!("format arguments: {e}"))?
4499 .into_iter()
4500 .collect();
4501 self.format_pieces(&args)
4502 }
4503
4504 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
4505 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
4506 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 9h ago4507 if args.is_empty() {
4508 return Ok("\"\"".into());
4509 }
4510 return Err("the first argument must be a literal format string".into());
4511 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4512 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4513
4514 let pieces = fmt::parse(&s.value())?;
4515 let mut parts: Vec<String> = Vec::new();
4516 let mut next = 0usize;
4517 let mut used = vec![false; rest.len()];
4518 for p in &pieces {
4519 match p {
4520 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
4521 fmt::Piece::Arg { r#ref, spec } => {
4522 let v = match r#ref {
4523 fmt::Ref::Next => {
4524 let e = rest.get(next).ok_or("too few arguments for format string")?;
4525 used[next] = true;
4526 next += 1;
4527 self.expr(e)?
4528 }
4529 fmt::Ref::Index(i) => {
4530 let e = rest.get(*i).ok_or("format index out of range")?;
4531 used[*i] = true;
4532 self.expr(e)?
4533 }
4534 fmt::Ref::Named(n) => {
4535 let t = self.lookup(n).ok_or_else(|| {
4536 format!("`{{{n}}}` captures `{n}`, which is not in scope")
4537 })?;
4538 Val::new(ident(n), Some(t))
4539 }
4540 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 7h ago4541 let integer = v.ty.as_ref().is_some_and(|t| t.is_integer());
4542 if spec.radix.is_some() && !integer && v.ty.is_none() {
4543 return Err(
4544 "a radix format (`{:x}`, `{:b}`, ...) needs a known \
4545 argument type: on an integer it formats the bit \
4546 pattern, on anything else it calls that type's own \
4547 impl"
4548 .into(),
4549 );
4550 }
4551 parts.push(fmt::render_arg(&v.code, spec, integer));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4552 }
4553 }
4554 }
4555 // Rust rejects an argument that no `{}` consumes; so do we, rather
4556 // than dropping it from the output.
4557 if let Some(i) = used.iter().position(|u| !u) {
4558 return Err(format!(
4559 "argument {} is never used by the format string",
4560 i + 1
4561 ));
4562 }
4563 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
4564 }
4565}
4566
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago4567/// Whether a pattern introduces a binding.
4568fn binds(p: &Pat) -> bool {
4569 match p {
4570 Pat::Ident(_) => true,
4571 Pat::Guard(g) => binds(&g.pat),
4572 Pat::Paren(x) => binds(&x.pat),
4573 Pat::Reference(r) => binds(&r.pat),
4574 Pat::Or(o) => o.cases.iter().any(binds),
4575 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
4576 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
4577 _ => false,
4578 }
4579}
4580
4581/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
4582fn destructures(p: &Pat) -> bool {
4583 matches!(
4584 p,
4585 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
4586 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
4587 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
4588 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
4589}
4590
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4591/// Whether an expression has a direct Nim expression form.
4592///
4593/// Nim's `if` is an expression only when every arm is a single expression, and
4594/// its `case` is never one here. Anything else has to be lowered as statements
4595/// that assign into a target.
4596fn expressible(e: &Expr) -> bool {
4597 match e {
4598 Expr::If(i) => {
4599 let Some(then) = single_expr(&i.then_branch) else { return false };
4600 if !expressible(then) {
4601 return false;
4602 }
4603 match &i.else_branch {
4604 None => false,
4605 Some((_, els)) => match &**els {
4606 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
4607 other => expressible(other),
4608 },
4609 }
4610 }
4611 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
4612 _ => true,
4613 }
4614}
4615
4616/// The single expression a block consists of, if that is all it is. An `if`
4617/// can only be lowered as a Nim `if`-expression when both arms are this shape.
4618fn single_expr(b: &syn::Block) -> Option<&Expr> {
4619 match (b.stmts.len(), b.stmts.first()) {
4620 (1, Some(Stmt::Expr(e, None))) => Some(e),
4621 _ => None,
4622 }
4623}
4624
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago4625/// Substitute `params[i] -> args[i]` through a type. Enough of the type
4626/// grammar is covered to expand the aliases we accept; anything else is left
4627/// alone and will be reported by `ty::map` if it is unsupported.
4628fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
4629 use syn::Type;
4630 match t {
4631 Type::Path(p) => {
4632 if p.qself.is_none() && p.path.segments.len() == 1 {
4633 let seg = &p.path.segments[0];
4634 if seg.arguments.is_empty() {
4635 let name = seg.ident.to_string();
4636 if let Some(i) = params.iter().position(|x| *x == name) {
4637 return args[i].clone();
4638 }
4639 }
4640 }
4641 let mut p = p.clone();
4642 for seg in &mut p.path.segments {
4643 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
4644 for g in &mut a.args {
4645 if let syn::GenericArgument::Type(t) = g {
4646 *t = substitute(t, params, args);
4647 }
4648 }
4649 }
4650 }
4651 Type::Path(p)
4652 }
4653 Type::Reference(r) => {
4654 let mut r = r.clone();
4655 r.elem = Box::new(substitute(&r.elem, params, args));
4656 Type::Reference(r)
4657 }
4658 Type::Slice(sl) => {
4659 let mut sl = sl.clone();
4660 sl.elem = Box::new(substitute(&sl.elem, params, args));
4661 Type::Slice(sl)
4662 }
4663 Type::Array(a) => {
4664 let mut a = a.clone();
4665 a.elem = Box::new(substitute(&a.elem, params, args));
4666 Type::Array(a)
4667 }
4668 Type::Tuple(tp) => {
4669 let mut tp = tp.clone();
4670 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
4671 Type::Tuple(tp)
4672 }
4673 Type::Paren(p) => substitute(&p.elem, params, args),
4674 Type::Group(g) => substitute(&g.elem, params, args),
4675 other => other.clone(),
4676 }
4677}
4678
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4679// --------------------------------------------------------------- utilities
4680
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4681/// Whether a return type is a borrow of one of the arguments, which Nim
4682/// models with a view rather than with an owned copy.
4683fn returns_borrow(t: &syn::Type) -> bool {
4684 match t {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4685 syn::Type::Reference(r) => match &*r.elem {
4686 syn::Type::Slice(_) => true,
4687 // `&str` is a borrow of someone else's bytes too, and returning it
4688 // means returning a view, not an owned string.
4689 syn::Type::Path(p) => p.path.is_ident("str"),
4690 _ => false,
4691 },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4692 syn::Type::Paren(p) => returns_borrow(&p.elem),
4693 syn::Type::Group(g) => returns_borrow(&g.elem),
4694 _ => false,
4695 }
4696}
4697
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 8h ago4698/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
4699/// to the crate root, which is where a flattened module's items live unless
4700/// they came from one of the extra input files.
4701fn module_of(prefix: &[String]) -> String {
4702 match prefix.last() {
4703 Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
4704 _ => String::new(),
4705 }
4706}
4707
4708/// The first type argument of an `Option[T]` / `Result[T, E]`.
4709fn elem_arg(t: &Nim) -> Nim {
4710 match t {
4711 Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
4712 other => other.clone(),
4713 }
4714}
4715
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4716/// The element type of a sequence-like Nim type.
4717fn elem_of(t: &Option<Nim>) -> Option<Nim> {
4718 match t {
4719 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
4720 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
4721 _ => None,
4722 }
4723}
4724
4725/// The short name a Nim type is known by, for keying method tables.
4726fn type_name(t: &Nim) -> String {
4727 match t {
4728 Nim::Named(n, _) => n.clone(),
4729 Nim::Prim(p) => p.clone(),
4730 other => other.render(),
4731 }
4732}
4733
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago4734/// `(trait, operator)` for every operator trait we dispatch.
4735const OPERATOR_TRAITS: &[(&str, &str)] = &[
4736 ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"),
4737 ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"),
4738 ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="),
4739 ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="),
4740 ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="),
4741 ("Neg", "neg"), ("Not", "not"),
4742];
4743
4744/// `(operator, trait method name)`.
4745const OP_METHOD: &[(&str, &str)] = &[
4746 ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"),
4747 ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"),
4748 ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"),
4749 ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"),
4750 ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"),
4751 (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"),
4752];
4753
4754fn op_method(op: &str) -> &'static str {
4755 OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("")
4756}
4757
4758/// The operator symbol a compound assignment applies.
4759fn compound_symbol(op: &BinOp) -> &'static str {
4760 match op {
4761 BinOp::AddAssign(_) => "+=",
4762 BinOp::SubAssign(_) => "-=",
4763 BinOp::MulAssign(_) => "*=",
4764 BinOp::DivAssign(_) => "/=",
4765 BinOp::RemAssign(_) => "%=",
4766 BinOp::BitAndAssign(_) => "&=",
4767 BinOp::BitOrAssign(_) => "|=",
4768 BinOp::BitXorAssign(_) => "^=",
4769 BinOp::ShlAssign(_) => "<<=",
4770 BinOp::ShrAssign(_) => ">>=",
4771 _ => "",
4772 }
4773}
4774
4775fn binary_symbol(op: &BinOp) -> &'static str {
4776 match op {
4777 BinOp::Add(_) => "+",
4778 BinOp::Sub(_) => "-",
4779 BinOp::Mul(_) => "*",
4780 BinOp::Div(_) => "/",
4781 BinOp::Rem(_) => "%",
4782 BinOp::BitAnd(_) => "&",
4783 BinOp::BitOr(_) => "|",
4784 BinOp::BitXor(_) => "^",
4785 BinOp::Shl(_) => "<<",
4786 BinOp::Shr(_) => ">>",
4787 _ => "",
4788 }
4789}
4790
4791/// The operator a trait overloads, if it is one of the operator traits.
4792fn operator_trait(t: &str) -> Option<&'static str> {
4793 Some(match t {
4794 "Add" => "+",
4795 "Sub" => "-",
4796 "Mul" => "*",
4797 "Div" => "/",
4798 "Rem" => "%",
4799 "BitAnd" => "&",
4800 "BitOr" => "|",
4801 "BitXor" => "^",
4802 "Shl" => "<<",
4803 "Shr" => ">>",
4804 "AddAssign" => "+=",
4805 "SubAssign" => "-=",
4806 "MulAssign" => "*=",
4807 "DivAssign" => "/=",
4808 "RemAssign" => "%=",
4809 "BitAndAssign" => "&=",
4810 "BitOrAssign" => "|=",
4811 "BitXorAssign" => "^=",
4812 "ShlAssign" => "<<=",
4813 "ShrAssign" => ">>=",
4814 "Neg" => "neg",
4815 "Not" => "not",
4816 _ => return None,
4817 })
4818}
4819
4820/// The Nim proc name for a trait method, qualified by trait and type so that
4821/// two traits declaring the same method name cannot collide.
4822fn trait_method_name(ty: &str, tr: &str, m: &str) -> String {
4823 format!("rs{}_{}_{}", tr, ty, m)
4824}
4825
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago4826fn is_fmt_trait(t: &str) -> bool {
4827 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
4828}
4829
4830/// The prelude proc a formatting trait's output is produced by.
4831fn fmt_proc(t: &str) -> &'static str {
4832 match t {
4833 "Display" => "rsDisplay",
4834 "Debug" => "rsDebug",
4835 "LowerHex" => "rsLowerHex",
4836 "UpperHex" => "rsUpperHex",
4837 "Binary" => "rsBinary",
4838 _ => "rsOctal",
4839 }
4840}
4841
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 7h ago4842/// Whether an expression is an iterator-producing chain rather than a value.
4843fn is_iterator_expr(e: &Expr) -> bool {
4844 match e {
4845 Expr::MethodCall(m) => matches!(
4846 m.method.to_string().as_str(),
4847 "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact"
4848 | "chunks_exact_mut" | "windows"
4849 ),
4850 Expr::Paren(p) => is_iterator_expr(&p.expr),
4851 _ => false,
4852 }
4853}
4854
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 7h ago4855/// Whether an expression denotes a place -- a variable, a field, or an index
4856/// or slice of one -- and so may be re-evaluated with no side effect.
4857fn is_pure_place(e: &Expr) -> bool {
4858 match e {
4859 Expr::Path(_) => true,
4860 Expr::Field(f) => is_pure_place(&f.base),
4861 Expr::Index(i) => {
4862 is_pure_place(&i.expr)
4863 && match &*i.index {
4864 Expr::Range(r) => {
4865 r.start.as_deref().map_or(true, is_pure_place)
4866 && r.end.as_deref().map_or(true, is_pure_place)
4867 }
4868 other => is_pure_place(other),
4869 }
4870 }
4871 Expr::Lit(_) => true,
4872 Expr::Reference(r) => is_pure_place(&r.expr),
4873 Expr::Paren(p) => is_pure_place(&p.expr),
4874 Expr::Group(g) => is_pure_place(&g.expr),
4875 // Arithmetic on places is still side-effect free, so a bound like
4876 // `..want - 1` does not stop the binding being an alias.
4877 Expr::Binary(b) if !is_compound(&b.op) => {
4878 is_pure_place(&b.left) && is_pure_place(&b.right)
4879 }
4880 Expr::Unary(u) => is_pure_place(&u.expr),
4881 Expr::Cast(c) => is_pure_place(&c.expr),
4882 _ => false,
4883 }
4884}
4885
4886/// Whether an expression is a `&mut` borrow, directly or through parens.
4887fn is_mut_borrow(e: &Expr) -> bool {
4888 match e {
4889 Expr::Reference(r) => r.mutability.is_some(),
4890 Expr::Paren(p) => is_mut_borrow(&p.expr),
4891 Expr::Group(g) => is_mut_borrow(&g.expr),
4892 _ => false,
4893 }
4894}
4895
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4896fn takes_self(sig: &syn::Signature) -> bool {
4897 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
4898}
4899
4900fn path_name(p: &syn::Path) -> String {
4901 p.segments
4902 .last()
4903 .map(|s| s.ident.to_string())
4904 .unwrap_or_default()
4905}
4906
4907fn is_compound(op: &BinOp) -> bool {
4908 matches!(
4909 op,
4910 BinOp::AddAssign(_)
4911 | BinOp::SubAssign(_)
4912 | BinOp::MulAssign(_)
4913 | BinOp::DivAssign(_)
4914 | BinOp::RemAssign(_)
4915 | BinOp::BitAndAssign(_)
4916 | BinOp::BitOrAssign(_)
4917 | BinOp::BitXorAssign(_)
4918 | BinOp::ShlAssign(_)
4919 | BinOp::ShrAssign(_)
4920 )
4921}
4922
4923/// The Nim literal suffix for an integer type (`5'i32`).
4924fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
4925 let Nim::Prim(p) = t else {
4926 return Err("not a primitive integer".into());
4927 };
4928 Ok(match p.as_str() {
4929 "int8" => "i8",
4930 "int16" => "i16",
4931 "int32" => "i32",
4932 "int64" => "i64",
4933 "int" => "i",
4934 "uint8" => "u8",
4935 "uint16" => "u16",
4936 "uint32" => "u32",
4937 "uint64" => "u64",
4938 "uint" => "u",
4939 other => return Err(format!("no Nim literal suffix for `{other}`")),
4940 })
4941}
4942
4943/// The unsigned integer type of the same width, used to spell `wrapping_*`.
4944fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
4945 let Nim::Prim(p) = t else {
4946 return Err("not a primitive integer".into());
4947 };
4948 Ok(match p.as_str() {
4949 "int8" => "uint8",
4950 "int16" => "uint16",
4951 "int32" => "uint32",
4952 "int64" => "uint64",
4953 "int" => "uint",
4954 other => return Err(format!("`{other}` has no unsigned peer")),
4955 })
4956}
4957
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago4958fn quote_meta(m: &syn::Meta) -> String {
4959 match m {
4960 syn::Meta::Path(p) => path_name(p),
4961 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
4962 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
4963 }
4964}
4965
4966fn item_attrs(i: &Item) -> &[syn::Attribute] {
4967 match i {
4968 Item::Fn(f) => &f.attrs,
4969 Item::Struct(s) => &s.attrs,
4970 Item::Enum(e) => &e.attrs,
4971 Item::Impl(x) => &x.attrs,
4972 Item::Const(c) => &c.attrs,
4973 Item::Type(t) => &t.attrs,
4974 Item::Mod(m) => &m.attrs,
4975 Item::Use(u) => &u.attrs,
4976 Item::ExternCrate(e) => &e.attrs,
4977 Item::Static(s) => &s.attrs,
4978 _ => &[],
4979 }
4980}
4981
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 9h ago4982fn item_kind(i: &Item) -> &'static str {
4983 match i {
4984 Item::Trait(_) => "`trait`",
4985 Item::Static(_) => "`static`",
4986 Item::Macro(_) => "macro definition",
4987 Item::Union(_) => "`union`",
4988 Item::ForeignMod(_) => "`extern` block",
4989 _ => "item",
4990 }
4991}
4992
4993fn expr_kind(e: &Expr) -> &'static str {
4994 match e {
4995 Expr::Async(_) => "`async` block",
4996 Expr::Await(_) => "`.await`",
4997 Expr::Try(_) => "`?`",
4998 Expr::Range(_) => "range",
4999 Expr::Match(_) => "`match` (only statement position is implemented)",
5000 Expr::Let(_) => "`let` expression",
5001 Expr::Unsafe(_) => "`unsafe` block",
5002 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
5003 _ => "expression",
5004 }
5005}