nandi/rustnimpublic Fork 0
7db79919131ab55e23a1730bf78c360e05d9977e
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 · 5533 lines · 234.4 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h 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 18h 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 18h 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 17h 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 17h 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 17h 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 17h 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 17h 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 17h 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 17h 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 18h ago103 }
104}
105
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h 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 16h 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 17h ago117}
118
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h 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 17h 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 18h 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 17h 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 18h ago144 }
145 fn untyped(code: impl Into<String>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago146 Val::new(code, None)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h 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 15h 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 18h ago156}
157
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago158/// One variant of a Rust enum.
159#[derive(Clone)]
160struct Variant {
161 name: String,
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago162 /// `Error = 1` — Nim enums take explicit ordinals too, so the value is
163 /// preserved rather than the variant being renumbered.
164 discriminant: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago165 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
166 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
167 /// Nim requires the branches of a variant object to have distinct fields.
168 fields: Vec<(String, Nim)>,
169}
170
171#[derive(Clone)]
172struct EnumDef {
173 name: String,
174 /// True when every variant is a unit variant, which Nim represents as a
175 /// plain `enum` rather than an object variant.
176 simple: bool,
177 variants: Vec<Variant>,
178}
179
180impl EnumDef {
181 fn kind_ident(&self, v: &str) -> String {
182 format!("k{}{}", self.name, v)
183 }
184 fn ctor_ident(&self, v: &str) -> String {
185 format!("{}{}", self.name, v)
186 }
187 fn get(&self, v: &str) -> Option<&Variant> {
188 self.variants.iter().find(|x| x.name == v)
189 }
190}
191
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago192pub struct Lowerer {
193 out: String,
194 indent: usize,
195 scopes: Vec<HashMap<String, Nim>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago196 /// Names introduced by a `for` pattern that stand for an lvalue or a
197 /// window into a container, rather than for a variable of their own.
198 alias_scopes: Vec<HashMap<String, Alias>>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago199 /// `(module, name) -> signature`. Rust keeps `lower::decode` and
200 /// `mixed::decode` apart by module; flattening into one Nim module would
201 /// merge them, so the module is part of the key and of the emitted name.
202 fns: HashMap<(String, String), Sig>,
203 /// Module being lowered: the file stem, or empty for the crate root.
204 cur_mod: String,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago205 /// The type of the `impl` block being lowered, which `Self` names.
206 self_ty: Option<Nim>,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago207 /// Type parameters of the enclosing `impl`, which its methods share.
208 impl_generics: Vec<String>,
209 /// Type parameters of the proc being lowered, impl's included.
210 fn_generics: Vec<String>,
211 /// Type parameters declared by each generic struct or enum.
212 type_generics: HashMap<String, Vec<String>>,
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago213 /// `(type, name) -> type` for `type Item = ..;` inside an `impl`. Rust
214 /// writes those as `Self::Item`, which has to resolve before any
215 /// signature mentioning it is mapped.
216 assoc: HashMap<(String, String), Nim>,
217 /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`.
218 assoc_consts: HashMap<(String, String), (String, Nim)>,
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago219 /// Symbols declared by an `extern "C"` block.
220 foreign: std::collections::HashSet<String>,
221 /// Const-qualified C pointer aliases already declared.
222 const_ptrs: std::collections::HashSet<String>,
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago223 /// Types declared by a `bitflags!` invocation.
224 bitflags: std::collections::HashSet<String>,
225 /// `(type, flag) -> nim const name`.
226 flag_consts: HashMap<(String, String), String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago227 /// `use` brings a name into scope from another module. Flattening loses
228 /// the module structure, so the mapping is recorded and consulted when a
229 /// bare call is resolved.
230 use_map: HashMap<String, String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago231 /// struct name -> (field, type)
232 structs: HashMap<String, Vec<(String, Nim)>>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago233 enums: HashMap<String, EnumDef>,
234 /// variant name -> enums declaring it. A variant named by more than one
235 /// enum must be written qualified, or it is rejected as ambiguous.
236 variant_owner: HashMap<String, Vec<String>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago237 /// `(receiver type, method) -> signature`. Keyed by type because two
238 /// types may define the same method name, and Nim tells them apart by
239 /// overload resolution on the first parameter.
240 methods: HashMap<(String, String), Sig>,
241 /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
242 /// on a user type can be checked rather than assumed.
243 fmt_impls: HashMap<(String, String), ()>,
244 /// `(from, to)` conversions declared by `impl From<A> for B`.
245 from_impls: HashMap<(String, String), String>,
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago246 /// Operator traits implemented for a type, so `a += b` on a user type can
247 /// be dispatched to the impl rather than to Nim's built-in operator.
248 op_impls: HashMap<(String, String), ()>,
249 /// `(type, method) -> nim name`, for calls written as `Type::method(..)`.
250 statics: HashMap<(String, String), String>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago251 /// Forward declarations, emitted between the type definitions and the
252 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
253 /// proc is declared up front rather than the input being reordered --
254 /// which would not work for mutual recursion anyway.
255 forwards: Vec<String>,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago256 /// Element type a `vec![..]` should build, from the binding's annotation.
257 vec_expect: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago258 /// While lowering a formatting impl: the `Formatter` parameter's name.
259 /// Writes through it produce the proc's string result.
260 fmt_param: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago261 /// `type X<T> = ...`, expanded before any type is mapped.
262 aliases: HashMap<String, (Vec<String>, syn::Type)>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago263 /// Module names supplied as separate input files. A `mod x;` naming one
264 /// of these is satisfied by that file having been passed in.
265 pub modules: Vec<String>,
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 16h ago266 /// How many items were actually translated. If this is zero the input
267 /// produced nothing but the prelude, and reporting success for that is
268 /// the precise failure this project exists to avoid -- see `findings/`.
269 emitted: usize,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago270 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
271 /// evaluated against these exactly as rustc would, so an item that is
272 /// dropped here is genuinely not part of the program being compiled.
273 pub features: Vec<String>,
274 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago275 /// Return type of the proc being lowered, so `return e` and a trailing
276 /// expression can type their literals the way Rust's inference would.
277 ret: Option<Nim>,
278 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
279 /// statement must assign their value to.
280 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago281 /// Set while lowering a `while` condition, which Nim re-evaluates each
282 /// iteration and so cannot have statements hoisted out of it.
283 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago284 tmp: usize,
285}
286
287impl Lowerer {
288 pub fn new() -> Self {
289 Lowerer {
290 out: String::new(),
291 indent: 0,
292 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago293 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago294 fns: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago295 cur_mod: String::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago296 self_ty: None,
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago297 impl_generics: Vec::new(),
298 fn_generics: Vec::new(),
299 type_generics: HashMap::new(),
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago300 assoc: HashMap::new(),
301 assoc_consts: HashMap::new(),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago302 foreign: std::collections::HashSet::new(),
303 const_ptrs: std::collections::HashSet::new(),
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago304 bitflags: std::collections::HashSet::new(),
305 flag_consts: HashMap::new(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago306 use_map: HashMap::new(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago307 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago308 enums: HashMap::new(),
309 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago310 methods: HashMap::new(),
311 fmt_impls: HashMap::new(),
312 from_impls: HashMap::new(),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago313 op_impls: HashMap::new(),
314 statics: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago315 fmt_param: None,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago316 vec_expect: None,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago317 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago318 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago319 modules: Vec::new(),
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 16h ago320 emitted: 0,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago321 features: Vec::new(),
322 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago323 ret: None,
324 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago325 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago326 tmp: 0,
327 }
328 }
329
330 // ------------------------------------------------------------ emission
331
332 fn line(&mut self, s: &str) {
333 for _ in 0..self.indent {
334 self.out.push_str(" ");
335 }
336 self.out.push_str(s);
337 self.out.push('\n');
338 }
339
340 fn blank(&mut self) {
341 self.out.push('\n');
342 }
343
344 fn fresh(&mut self, hint: &str) -> String {
345 self.tmp += 1;
346 format!("rsTmp{}{}", hint, self.tmp)
347 }
348
349 // --------------------------------------------------------------- scope
350
351 fn push_scope(&mut self) {
352 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago353 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago354 }
355 fn pop_scope(&mut self) {
356 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago357 self.alias_scopes.pop();
358 }
359 fn bind_alias(&mut self, name: &str, a: Alias) {
360 self.alias_scopes
361 .last_mut()
362 .unwrap()
363 .insert(name.to_string(), a);
364 }
365 fn lookup_alias(&self, name: &str) -> Option<Alias> {
366 self.alias_scopes
367 .iter()
368 .rev()
369 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago370 }
371 fn bind(&mut self, name: &str, t: Nim) {
372 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
373 }
374 fn lookup(&self, name: &str) -> Option<Nim> {
375 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
376 }
377
378 // ---------------------------------------------------------------- file
379
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago380 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 18h ago381 self.out.push_str(include_str!("prelude.nim"));
382 self.blank();
383
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago384 // Pass 0: type aliases. A signature in one file may use an alias
385 // declared in another, and inputs are given in whatever order suits
386 // 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 17h ago387 for (m, f) in files {
388 self.cur_mod = m.clone();
389 for item in &f.items {
390 self.collect_aliases(item)?;
391 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago392 }
393
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago394 // Pass 1: signatures and struct shapes, so that a call can be typed
395 // regardless of declaration order (Rust has no forward declarations).
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago396 for (m, f) in files {
397 self.cur_mod = m.clone();
398 for item in &f.items {
399 self.collect(item)?;
400 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago401 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago402 // Pass 2: type definitions, which every signature may mention.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago403 for (m, f) in files {
404 self.cur_mod = m.clone();
405 for item in &f.items {
406 self.item_types(item)?;
407 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago408 }
409
410 // Pass 3: forward declarations. Rust imposes no declaration order and
411 // Nim does, so everything is declared before any body is emitted;
412 // reordering the input would not handle mutual recursion anyway.
413 if !self.forwards.is_empty() {
414 for f in self.forwards.clone() {
415 self.line(&f);
416 }
417 self.blank();
418 }
419
420 // Pass 4: bodies.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago421 for (m, f) in files {
422 self.cur_mod = m.clone();
423 for item in &f.items {
424 self.item(item)?;
425 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago426 }
427
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 16h ago428 // An input that translates to nothing is a failure, however plausible
429 // the output file looks. The prelude alone is not a translation.
430 if self.emitted == 0 {
431 return Err(format!(
432 "nothing was translated: the input has no items this lowering \
433 emits{}. Writing a file containing only the prelude would \
434 report success for work that was not done",
435 if self.dropped_by_cfg > 0 {
436 format!(
437 " ({} item(s) were dropped by `#[cfg]`; enable them with \
438 `--cfg feature=<name>`)",
439 self.dropped_by_cfg
440 )
441 } else {
442 String::new()
443 }
444 ));
445 }
446
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago447 if self.fns.contains_key(&(String::new(), "main".to_string())) {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago448 self.blank();
449 self.line("when isMainModule:");
450 self.indent += 1;
451 self.line("try:");
452 self.line(" main()");
453 // Rust's panic exits 101 with a message on stderr. Nim's Defects
454 // exit 1. Mapping them here is what keeps the differential runner's
455 // exit-status comparison meaningful for panicking programs.
456 self.line("except RustPanic as e:");
457 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
458 self.line(" quit(101)");
459 self.line("except Defect as e:");
460 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
461 self.line(" quit(101)");
462 self.indent -= 1;
463 }
464 Ok(std::mem::take(&mut self.out))
465 }
466
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago467 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
468 if !self.cfg_keeps(item_attrs(item))? {
469 return Ok(());
470 }
471 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago472 Item::Use(u) => self.collect_use(&u.tree, &[]),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago473 Item::Type(t) => {
474 let params: Vec<String> = t
475 .generics
476 .params
477 .iter()
478 .filter_map(|g| match g {
479 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
480 _ => None,
481 })
482 .collect();
483 self.aliases
484 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
485 }
486 Item::Mod(m) if m.content.is_some() => {
487 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
488 for i in &items {
489 self.collect_aliases(i)?;
490 }
491 }
492 _ => {}
493 }
494 Ok(())
495 }
496
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago497 /// Record what a `use` brings into scope, as `name -> module`.
498 fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
499 use syn::UseTree;
500 match t {
501 UseTree::Path(p) => {
502 let mut pre = prefix.to_vec();
503 pre.push(p.ident.to_string());
504 self.collect_use(&p.tree, &pre);
505 }
506 UseTree::Group(g) => {
507 for t in &g.items {
508 self.collect_use(t, prefix);
509 }
510 }
511 UseTree::Name(n) => {
512 let m = module_of(prefix);
513 self.use_map.insert(n.ident.to_string(), m);
514 }
515 UseTree::Rename(r) => {
516 let m = module_of(prefix);
517 self.use_map.insert(r.rename.to_string(), m);
518 }
519 // A glob brings in an unknown set of names; resolution falls back
520 // to the current module and the root, as it would without it.
521 UseTree::Glob(_) => {}
522 }
523 }
524
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago525 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago526 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
527 // silently would change what the program does; picking a feature set
528 // on the user's behalf would be a guess. So it is reported, except on
529 // items that carry no runtime meaning here anyway.
530 if !self.cfg_keeps(item_attrs(item))? {
531 self.dropped_by_cfg += 1;
532 return Ok(());
533 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago534 match item {
535 Item::Fn(f) => {
536 let (params, ret) = self.signature(&f.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago537 let gen_names = Self::generics_of(&f.sig.generics);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago538 let name = f.sig.ident.to_string();
539 let nim = self.fn_name(&self.cur_mod, &name);
540 self.forwards.push(self.head_of(&nim, &f.sig, None)?);
541 self.fns
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago542 .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 18h ago543 }
544 Item::Struct(s) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago545 if s.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
546 return Err(format!(
547 "`struct {}` has a const generic parameter, which Nim has \
548 no equivalent for",
549 s.ident
550 ));
551 }
552 let g = Self::generics_of(&s.generics);
553 // The parameters must be in scope while the field types are
554 // mapped, so that `T` resolves to itself rather than to an
555 // unknown named type.
556 self.type_generics.insert(s.ident.to_string(), g);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago557 let mut fields = Vec::new();
558 for (i, f) in s.fields.iter().enumerate() {
559 let name = match &f.ident {
560 Some(id) => id.to_string(),
561 None => format!("f{i}"), // tuple struct
562 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago563 // A field of `&[T]` / `&str` type is a borrow, and Nim's
564 // view types allow it as an object field, so it stays a
565 // view rather than being copied into a `seq`.
566 let t = self.map_ty(&f.ty)?;
567 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
568 fields.push((name, t));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago569 }
570 self.structs.insert(s.ident.to_string(), fields);
571 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago572 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => {
573 self.collect_bitflags(&m.mac)?;
574 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago575 Item::ForeignMod(f) => {
576 for it in &f.items {
577 if let syn::ForeignItem::Fn(fi) = it {
578 let (params, ret) = self.signature(&fi.sig)?;
579 self.fns.insert(
580 (self.cur_mod.clone(), fi.sig.ident.to_string()),
581 Sig { params, ret, generics: Vec::new() },
582 );
583 }
584 }
585 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago586 Item::Mod(m) if m.content.is_some() => {
587 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
588 for i in &items {
589 self.collect(i)?;
590 }
591 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago592 Item::Type(t) => {
593 let params: Vec<String> = t
594 .generics
595 .params
596 .iter()
597 .filter_map(|g| match g {
598 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
599 _ => None,
600 })
601 .collect();
602 self.aliases
603 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
604 }
605 Item::Enum(e) => {
606 let name = e.ident.to_string();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago607 if e.generics.params.iter().any(|p| matches!(p, syn::GenericParam::Const(_))) {
608 return Err(format!(
609 "`enum {name}` has a const generic parameter, which Nim \
610 has no equivalent for"
611 ));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago612 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago613 self.type_generics
614 .insert(name.clone(), Self::generics_of(&e.generics));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago615 let mut variants = Vec::new();
616 for v in &e.variants {
617 let vname = v.ident.to_string();
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago618 let discriminant = match &v.discriminant {
619 Some((_, e)) => Some(self.expr(e)?.code),
620 None => None,
621 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago622 let mut fields = Vec::new();
623 for (i, f) in v.fields.iter().enumerate() {
624 // Nim requires the branches of a variant object to have
625 // distinct field names, so each is prefixed.
626 let fname = match &f.ident {
627 Some(id) => format!("{vname}_{id}"),
628 None => format!("{vname}_f{i}"),
629 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago630 let t = self.map_ty(&f.ty)?;
631 let t = if returns_borrow(&f.ty) { t.unvar() } else { t.owned() };
632 fields.push((fname, t));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago633 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago634 variants.push(Variant { name: vname, discriminant, fields });
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago635 }
636 let simple = variants.iter().all(|v| v.fields.is_empty());
637 for v in &variants {
638 self.variant_owner
639 .entry(v.name.clone())
640 .or_default()
641 .push(name.clone());
642 }
643 self.enums.insert(
644 name.clone(),
645 EnumDef { name, simple, variants },
646 );
647 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago648 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago649 let outer_g =
650 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 18h ago651 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago652 let outer_self = self.self_ty.replace(self_ty.clone());
653 let r = self.collect_impl(im, &self_ty);
654 self.self_ty = outer_self;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago655 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago656 return r;
657 }
658 _ => {}
659 }
660 Ok(())
661 }
662
663 fn collect_impl(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
664 {
665 let self_ty = self_ty.clone();
666 let tyname = type_name(&self_ty);
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago667 // Associated types first: a signature in the same block may name
668 // one, and it has to resolve by the time that signature is mapped.
669 for it in &im.items {
670 if let syn::ImplItem::Type(t) = it {
671 let v = self.map_ty(&t.ty)?;
672 self.assoc.insert((tyname.clone(), t.ident.to_string()), v);
673 }
674 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago675 if let Some((path, _)) = &im.trait_ {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago676 let tr = path_name(path);
677 if im.items.is_empty() {
678 // A marker trait with no items. We do not model trait
679 // resolution at all, so it generates nothing; any use
680 // that actually needed the trait (a `dyn`, a bound) is
681 // rejected where it appears.
682 return Ok(());
683 }
684 if is_fmt_trait(&tr) {
685 self.forwards.push(format!(
686 "proc {}*(self: {}): string",
687 fmt_proc(&tr),
688 self_ty.render()
689 ));
690 self.fmt_impls.insert((tyname, tr), ());
691 return Ok(());
692 }
693 if tr == "From" {
694 let syn::ImplItem::Fn(m) = &im.items[0] else {
695 return Err("`impl From` must contain `fn from`".into());
696 };
697 let (params, _) = self.signature(&m.sig)?;
698 let src = params
699 .first()
700 .ok_or("`fn from` takes one argument")?
701 .clone();
702 let name = format!("rsFrom{}{}", tyname, type_name(&src));
703 self.forwards.push(self.head_of(&name, &m.sig, None)?);
704 self.from_impls
705 .insert((type_name(&src), tyname), name);
706 return Ok(());
707 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago708 // Any other trait: its methods are emitted as procs on
709 // the type, named after the trait so two traits declaring
710 // the same method name do not collide. The *trait* is not
711 // modelled -- no dynamic dispatch, no bounds -- and a use
712 // that needs it is rejected where it appears.
713 if let Some(op) = operator_trait(&tr) {
714 self.op_impls.insert((tyname.clone(), op.to_string()), ());
715 }
716 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago717 // Already recorded above; a const is emitted with the
718 // bodies.
719 if matches!(it, syn::ImplItem::Type(_) | syn::ImplItem::Const(_)) {
720 continue;
721 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago722 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago723 return Err(format!(
724 "unsupported item in `impl {tr}`: only `fn`, \
725 `type` and `const` are implemented"
726 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago727 };
728 let mname = m.sig.ident.to_string();
729 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago730 let mut gen_names = self.impl_generics.clone();
731 gen_names.extend(Self::generics_of(&m.sig.generics));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago732 let recv = if takes_self(&m.sig) {
733 params.insert(0, self_ty.clone());
734 Some(self_ty.clone())
735 } else {
736 None
737 };
738 let nim = trait_method_name(&tyname, &tr, &mname);
739 self.forwards.push(self.head_of(&nim, &m.sig, recv.as_ref())?);
740 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago741 .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 16h ago742 self.statics.insert((tyname.clone(), mname), nim);
743 }
744 return Ok(());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago745 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago746 for it in &im.items {
747 if let syn::ImplItem::Fn(m) = it {
748 let (mut params, ret) = self.signature(&m.sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago749 let mut gen_names = self.impl_generics.clone();
750 gen_names.extend(Self::generics_of(&m.sig.generics));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago751 if takes_self(&m.sig) {
752 params.insert(0, self_ty.clone());
753 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago754 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 16h ago755 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
756 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 17h ago757 self.forwards.push(head);
758 self.methods
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago759 .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 16h ago760 self.statics
761 .insert((tyname.clone(), m.sig.ident.to_string()), nim);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago762 }
763 }
764 }
765 Ok(())
766 }
767
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago768 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
769 ///
770 /// This is evaluation, not approximation: rustc does the same thing, and
771 /// an item whose predicate is false is not part of the compiled program.
772 /// A predicate that cannot be evaluated is reported rather than assumed.
773 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
774 for a in attrs {
775 if a.path().is_ident("cfg") {
776 let pred: syn::Meta = a
777 .parse_args()
778 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
779 if !self.cfg_eval(&pred)? {
780 return Ok(false);
781 }
782 }
783 }
784 Ok(true)
785 }
786
787 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
788 match m {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago789 // Bare flags whose value is determined by the profile this project
790 // models: a normal (non-`--test`) debug build, not a docs build.
791 // Anything platform-specific stays rejected, since we would be
792 // picking a target on the user's behalf.
793 syn::Meta::Path(p) if p.is_ident("test") => Ok(false),
794 syn::Meta::Path(p) if p.is_ident("debug_assertions") => Ok(true),
795 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 15h ago796 syn::Meta::Path(p) if p.is_ident("doctest") || p.is_ident("miri") => Ok(false),
797 // Host facts. The generated Nim is compiled for this machine, so
798 // these are known rather than chosen. See DESIGN.md item 10: it
799 // does make the output host-shaped.
800 syn::Meta::Path(p) if p.is_ident("unix") => Ok(cfg!(unix)),
801 syn::Meta::Path(p) if p.is_ident("windows") => Ok(cfg!(windows)),
802 syn::Meta::NameValue(nv)
803 if nv.path.is_ident("target_os")
804 || nv.path.is_ident("target_arch")
805 || nv.path.is_ident("target_family")
806 || nv.path.is_ident("target_vendor") =>
807 {
808 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
809 return Err("this `cfg` key expects a string".into());
810 };
811 let key = nv.path.get_ident().map(|i| i.to_string()).unwrap_or_default();
812 Ok(s.value()
813 == match key.as_str() {
814 "target_os" => std::env::consts::OS,
815 "target_arch" => std::env::consts::ARCH,
816 "target_family" => std::env::consts::FAMILY,
817 _ => "unknown",
818 })
819 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago820 // The generated Nim is compiled for the same machine, so the
821 // target's word size and endianness are known rather than
822 // guessed. This does mean the output is host-shaped: a crate that
823 // branches on pointer width has had that branch decided here.
824 syn::Meta::NameValue(nv) if nv.path.is_ident("target_pointer_width") => {
825 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
826 return Err("`target_pointer_width = ..` expects a string".into());
827 };
828 Ok(s.value() == (usize::BITS).to_string())
829 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago830 // Every integer width and pointer-sized atomic exists on the
831 // targets Nim builds for here; like the other host facts this is
832 // read off the machine rather than chosen.
833 syn::Meta::NameValue(nv) if nv.path.is_ident("target_has_atomic") => {
834 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
835 return Err("`target_has_atomic = ..` expects a string".into());
836 };
837 Ok(matches!(
838 s.value().as_str(),
839 "8" | "16" | "32" | "64" | "ptr"
840 ))
841 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago842 syn::Meta::NameValue(nv) if nv.path.is_ident("target_endian") => {
843 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
844 return Err("`target_endian = ..` expects a string".into());
845 };
846 Ok(s.value() == if cfg!(target_endian = "big") { "big" } else { "little" })
847 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago848 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
849 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
850 return Err("`feature = ..` expects a string".into());
851 };
852 Ok(self.features.iter().any(|f| *f == s.value()))
853 }
854 syn::Meta::List(l) if l.path.is_ident("not") => {
855 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
856 Ok(!self.cfg_eval(&inner)?)
857 }
858 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
859 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
860 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
861 .map_err(|e| e.to_string())?;
862 let all = l.path.is_ident("all");
863 let mut acc = all;
864 for i in &items {
865 let v = self.cfg_eval(i)?;
866 acc = if all { acc && v } else { acc || v };
867 }
868 Ok(acc)
869 }
870 other => Err(format!(
Evaluate host cfg predicates, and measure what that actually buys 0e6c394 nandithebull 15h ago871 "`#[cfg({})]` is not a predicate rustnim can evaluate. \
872 Features (`--cfg feature=..`), host facts (`unix`, `windows`, \
873 `target_os`, `target_arch`, `target_family`, \
874 `target_pointer_width`, `target_endian`), `doc`/`doctest`/\
875 `miri`, and `not`/`all`/`any` over those are. A custom or \
876 build-script `cfg` has no value we could know",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago877 quote_meta(other)
878 )),
879 }
880 }
881
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago882 /// 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 18h ago883 /// lowering goes through here rather than calling `ty::map` directly, so
884 /// an alias cannot be missed in one position and honoured in another.
885 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 15h ago886 // `Self::Item` names an associated type of the enclosing `impl`.
887 if let syn::Type::Path(p) = t {
888 let segs: Vec<String> =
889 p.path.segments.iter().map(|s| s.ident.to_string()).collect();
890 if segs.len() == 2 {
891 let owner = if segs[0] == "Self" {
892 self.self_ty.as_ref().map(type_name)
893 } else {
894 Some(segs[0].clone())
895 };
896 if let Some(o) = owner {
897 if let Some(a) = self.assoc.get(&(o, segs[1].clone())) {
898 return Ok(a.clone());
899 }
900 }
901 }
902 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago903 let n = ty::map(&self.expand(t, 0)?)?;
904 Ok(self.subst_self(n))
905 }
906
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago907 /// Substitute a generic type's parameters with the arguments the use site
908 /// supplies: a field of `Holder<T>` read through a `Holder<i32>` is `i32`.
909 fn subst_type_args(&self, name: &str, used_as: &Nim, field: Nim) -> Nim {
910 let Some(params) = self.type_generics.get(name) else { return field };
911 if params.is_empty() {
912 return field;
913 }
914 let Nim::Named(n, args) = used_as else { return field };
915 if n != name || args.len() != params.len() {
916 return field;
917 }
918 let map: HashMap<String, Nim> =
919 params.iter().cloned().zip(args.iter().cloned()).collect();
920 Self::subst(&field, &map)
921 }
922
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago923 /// `Self` inside an `impl` block names the type being implemented.
924 fn subst_self(&self, t: Nim) -> Nim {
925 let Some(me) = &self.self_ty else { return t };
926 match t {
927 Nim::Named(n, _) if n == "Self" => me.clone(),
928 Nim::Seq(e) => Nim::Seq(Box::new(self.subst_self(*e))),
929 Nim::OpenArray(e) => Nim::OpenArray(Box::new(self.subst_self(*e))),
930 Nim::Array(n, e) => Nim::Array(n, Box::new(self.subst_self(*e))),
931 Nim::Var(e) => Nim::Var(Box::new(self.subst_self(*e))),
932 Nim::Named(n, a) => {
933 Nim::Named(n, a.into_iter().map(|x| self.subst_self(x)).collect())
934 }
935 other => other,
936 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago937 }
938
939 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
940 if depth > 16 {
941 return Err("type alias expansion did not terminate; is it cyclic?".into());
942 }
943 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
944 // Only an unqualified name can be one of this file's aliases.
945 // `fmt::Result` and `core::result::Result` are different types that
946 // merely end in the same segment.
947 if p.path.segments.len() != 1 {
948 return Ok(t.clone());
949 }
950 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
951 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
952 return Ok(t.clone());
953 };
954 let args: Vec<syn::Type> = match &seg.arguments {
955 syn::PathArguments::AngleBracketed(a) => a
956 .args
957 .iter()
958 .filter_map(|g| match g {
959 GenericArgument::Type(t) => Some(t.clone()),
960 _ => None,
961 })
962 .collect(),
963 _ => vec![],
964 };
965 if args.len() != params.len() {
966 // Flattening several files into one module can bring a crate's own
967 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
968 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
969 // module; here they are told apart by arity, and a use that fits
970 // neither is left for `ty::map` to report.
971 return Ok(t.clone());
972 }
973 self.expand(&substitute(target, params, &args), depth + 1)
974 }
975
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago976 /// The type parameters a generic item declares.
977 ///
978 /// Trait bounds and `where` clauses are dropped. Nim instantiates a
979 /// generic structurally: an operation the bound would have permitted
980 /// either exists for the instantiated type or is a compile error at the
981 /// instantiation site. So dropping a bound cannot make an accepted
982 /// program mean something different — it only makes rustnim accept some
983 /// programs rustc would have rejected, which does not matter when the
984 /// input is known-good Rust.
985 fn generics_of(g: &syn::Generics) -> Vec<String> {
986 g.params
987 .iter()
988 .filter_map(|p| match p {
989 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
990 _ => None,
991 })
992 .collect()
993 }
994
995 /// Bind a signature's type parameters by matching its declared parameter
996 /// types against the actual argument types, then substitute into `ret`.
997 ///
998 /// This is the small amount of inference a call site needs: Nim will
999 /// resolve the instantiation itself, but the *binding* still has to be
1000 /// annotated with a concrete type, and `T` is not one.
1001 fn instantiate(sig: &Sig, args: &[Option<Nim>]) -> Nim {
1002 if sig.generics.is_empty() {
1003 return sig.ret.clone();
1004 }
1005 let mut bound: HashMap<String, Nim> = HashMap::new();
1006 for (decl, actual) in sig.params.iter().zip(args) {
1007 if let Some(a) = actual {
1008 Self::unify(decl, a, &sig.generics, &mut bound);
1009 }
1010 }
1011 Self::subst(&sig.ret, &bound)
1012 }
1013
1014 fn unify(decl: &Nim, actual: &Nim, params: &[String], out: &mut HashMap<String, Nim>) {
1015 match (decl, actual) {
1016 (Nim::Named(n, da), _) if params.iter().any(|p| p == n) && da.is_empty() => {
1017 out.entry(n.clone()).or_insert_with(|| actual.clone());
1018 }
1019 (Nim::Named(_, da), Nim::Named(_, aa)) if da.len() == aa.len() => {
1020 for (d, a) in da.iter().zip(aa) {
1021 Self::unify(d, a, params, out);
1022 }
1023 }
1024 (Nim::Seq(d), Nim::Seq(a))
1025 | (Nim::OpenArray(d), Nim::OpenArray(a))
1026 | (Nim::Seq(d), Nim::OpenArray(a))
1027 | (Nim::OpenArray(d), Nim::Seq(a))
1028 | (Nim::Var(d), Nim::Var(a))
1029 | (Nim::Array(_, d), Nim::Array(_, a)) => Self::unify(d, a, params, out),
1030 (Nim::Var(d), a) => Self::unify(d, a, params, out),
1031 (d, Nim::Var(a)) => Self::unify(d, a, params, out),
1032 (Nim::Tuple(d), Nim::Tuple(a)) if d.len() == a.len() => {
1033 for (d, a) in d.iter().zip(a) {
1034 Self::unify(d, a, params, out);
1035 }
1036 }
1037 _ => {}
1038 }
1039 }
1040
1041 fn subst(t: &Nim, m: &HashMap<String, Nim>) -> Nim {
1042 match t {
1043 Nim::Named(n, a) if a.is_empty() => m.get(n).cloned().unwrap_or_else(|| t.clone()),
1044 Nim::Named(n, a) => {
1045 Nim::Named(n.clone(), a.iter().map(|x| Self::subst(x, m)).collect())
1046 }
1047 Nim::Seq(e) => Nim::Seq(Box::new(Self::subst(e, m))),
1048 Nim::OpenArray(e) => Nim::OpenArray(Box::new(Self::subst(e, m))),
1049 Nim::Array(n, e) => Nim::Array(*n, Box::new(Self::subst(e, m))),
1050 Nim::Var(e) => Nim::Var(Box::new(Self::subst(e, m))),
1051 Nim::Tuple(ts) => Nim::Tuple(ts.iter().map(|x| Self::subst(x, m)).collect()),
1052 other => other.clone(),
1053 }
1054 }
1055
1056 /// Whether a type mentions a type parameter that is in scope here. Such a
1057 /// type cannot be used as a Nim annotation at an instantiation site: Nim
1058 /// infers it, and writing `T` would name something that is not bound.
1059 fn mentions_type_param(&self, t: &Nim) -> bool {
1060 match t {
1061 Nim::Named(n, a) => {
1062 self.fn_generics.iter().any(|g| g == n)
1063 || a.iter().any(|x| self.mentions_type_param(x))
1064 }
1065 Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) => {
1066 self.mentions_type_param(e)
1067 }
1068 Nim::Tuple(ts) => ts.iter().any(|x| self.mentions_type_param(x)),
1069 Nim::Proc(a, r) => {
1070 a.iter().any(|x| self.mentions_type_param(x)) || self.mentions_type_param(r)
1071 }
1072 _ => false,
1073 }
1074 }
1075
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago1076 /// The Nim name of a `log::Level` written as a path.
1077 fn log_level_of(&mut self, e: &Expr) -> Result<String, String> {
1078 let Expr::Path(p) = e else {
1079 return Err("a log level must be written as `Level::Info`".into());
1080 };
1081 let last = path_name(&p.path);
1082 Ok(match last.as_str() {
1083 "Error" => "rsLvlError",
1084 "Warn" => "rsLvlWarn",
1085 "Info" => "rsLvlInfo",
1086 "Debug" => "rsLvlDebug",
1087 "Trace" => "rsLvlTrace",
1088 other => return Err(format!("`Level::{other}` is not a log level")),
1089 }
1090 .to_string())
1091 }
1092
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1093 /// `[T, U]`, or empty.
1094 fn gen_list(params: &[String]) -> String {
1095 if params.is_empty() {
1096 String::new()
1097 } else {
1098 format!("[{}]", params.join(", "))
1099 }
1100 }
1101
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago1102 /// The Nim name for a function, qualified by its module.
1103 fn fn_name(&self, module: &str, name: &str) -> String {
1104 if module.is_empty() {
1105 ident(name)
1106 } else {
1107 format!("{}_{}", module, ident(name))
1108 }
1109 }
1110
1111 /// Resolve a call path to the module and name it refers to: an explicit
1112 /// `mixed::decode`, then the current module, then the crate root.
1113 fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
1114 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1115 let last = segs.last()?.clone();
1116 if segs.len() >= 2 {
1117 let q = &segs[segs.len() - 2];
1118 if self.fns.contains_key(&(q.clone(), last.clone())) {
1119 return Some((q.clone(), last));
1120 }
1121 }
1122 let imported = self.use_map.get(&last).cloned();
1123 for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
1124 .into_iter()
1125 .flatten()
1126 {
1127 if self.fns.contains_key(&(m.clone(), last.clone())) {
1128 return Some((m, last));
1129 }
1130 }
1131 None
1132 }
1133
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1134 /// The Nim `proc` head for a Rust signature, used both for the forward
1135 /// declaration and for the definition, so the two cannot drift apart.
1136 fn head_of(
1137 &self,
1138 name: &str,
1139 sig: &syn::Signature,
1140 recv: Option<&Nim>,
1141 ) -> Result<String, String> {
1142 let (ptys, ret) = self.signature(sig)?;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1143 // A method inside `impl<T> Foo<T>` is generic in the impl's
1144 // parameters as well as its own.
1145 let mut params = self.impl_generics.clone();
1146 for g in Self::generics_of(&sig.generics) {
1147 if !params.contains(&g) {
1148 params.push(g);
1149 }
1150 }
1151 let gens = Self::gen_list(&params);
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1152 let mut parts = Vec::new();
1153 if let Some(self_ty) = recv {
1154 let mutable = matches!(
1155 sig.inputs.first(),
1156 Some(FnArg::Receiver(r))
1157 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1158 );
1159 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1160 parts.push(format!("self: {}", t.render()));
1161 }
1162 let typed: Vec<&syn::PatType> = sig
1163 .inputs
1164 .iter()
1165 .filter_map(|a| match a {
1166 FnArg::Typed(t) => Some(t),
1167 _ => None,
1168 })
1169 .collect();
1170 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
1171 let pname = match &*p.pat {
1172 Pat::Ident(id) => id.ident.to_string(),
1173 Pat::Wild(_) => format!("unused{}", parts.len()),
1174 _ => return Err("only plain identifier parameters are supported".into()),
1175 };
1176 let _ = i;
1177 parts.push(format!("{}: {}", ident(&pname), t.render()));
1178 }
1179 Ok(if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1180 format!("proc {}*{}({})", ident(name), gens, parts.join(", "))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1181 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1182 format!(
1183 "proc {}*{}({}): {}",
1184 ident(name),
1185 gens,
1186 parts.join(", "),
1187 ret.render()
1188 )
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1189 })
1190 }
1191
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1192 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 17h ago1193 // `unsafe fn` marks a contract for callers; it does not change what
1194 // the body means, so it lowers like any other proc.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1195 if sig.asyncness.is_some() {
1196 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
1197 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1198 // 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 15h ago1199 // they disappear. Type parameters become Nim generic parameters.
1200 // Const parameters have no Nim equivalent and are still rejected.
1201 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 18h ago1202 return Err(format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1203 "`fn {}` has a const generic parameter, which Nim has no \
1204 equivalent for",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1205 sig.ident
1206 ));
1207 }
1208 let mut params = Vec::new();
1209 for a in &sig.inputs {
1210 if let FnArg::Typed(t) = a {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1211 params.push(self.map_ty(&t.ty)?);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1212 }
1213 }
1214 let ret = match &sig.output {
1215 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1216 // A returned `&[T]` is a borrow of the caller's buffer, so it
1217 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
1218 // a `seq`, which `owned()` would do to both.
1219 ReturnType::Type(_, t) => {
1220 let n = self.map_ty(t)?;
1221 if returns_borrow(t) { n } else { n.owned() }
1222 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1223 };
1224 Ok((params, ret))
1225 }
1226
1227 // --------------------------------------------------------------- items
1228
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1229 /// Emit the type definitions only: they must precede every signature.
1230 fn item_types(&mut self, item: &Item) -> Result<(), String> {
1231 if !self.cfg_keeps(item_attrs(item))? {
1232 return Ok(());
1233 }
1234 match item {
1235 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago1236 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.item_inner(item),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago1237 Item::ForeignMod(_) => self.item_inner(item),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1238 Item::Mod(m) if m.content.is_some() => {
1239 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1240 for i in &items {
1241 self.item_types(i)?;
1242 }
1243 Ok(())
1244 }
1245 _ => Ok(()),
1246 }
1247 }
1248
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1249 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1250 if !self.cfg_keeps(item_attrs(item))? {
1251 return Ok(());
1252 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1253 // Types were emitted in their own pass.
1254 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
1255 return Ok(());
1256 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago1257 if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags")
1258 || matches!(item, Item::ForeignMod(_))
1259 {
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago1260 return Ok(()); // emitted with the types
1261 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1262 self.item_inner(item)
1263 }
1264
1265 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Fail when nothing was translated, not just when the file is empty 0777223 nandithebull 16h ago1266 if !matches!(item, Item::Use(_) | Item::ExternCrate(_) | Item::Mod(_) | Item::Type(_)) {
1267 self.emitted += 1;
1268 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1269 match item {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago1270 Item::Fn(f) => {
1271 let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
1272 self.func_named(&nim, &f.sig, &f.block, None)
1273 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1274 Item::Struct(s) => {
1275 let name = s.ident.to_string();
1276 let fields = self.structs[&name].clone();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1277 let g = Self::gen_list(self.type_generics.get(&name).map(|v| &v[..]).unwrap_or(&[]));
1278 self.line(&format!("type {}*{} = object", ident(&name), g));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1279 self.indent += 1;
1280 if fields.is_empty() {
1281 self.line("discard");
1282 }
1283 for (fname, fty) in &fields {
1284 self.line(&format!("{}*: {}", ident(fname), fty.render()));
1285 }
1286 self.indent -= 1;
1287 self.blank();
1288 Ok(())
1289 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago1290 Item::Macro(m) if path_name(&m.mac.path) == "bitflags" => self.emit_bitflags(&m.mac),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1291 Item::Type(_) => Ok(()), // expanded at every use site
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago1292 Item::Trait(t) => {
1293 // We do not model trait resolution, so a declaration generates
1294 // nothing and a use that needed it is rejected where it
1295 // appears. A *default body*, though, is code: dropping it
1296 // would silently remove a method the impls inherit.
1297 for it in &t.items {
1298 if let syn::TraitItem::Fn(f) = it {
1299 if f.default.is_some() {
1300 return Err(format!(
1301 "`trait {}` gives `{}` a default body; trait \
1302 resolution is not modelled, so that body has no \
1303 impl to be emitted into and dropping it would \
1304 remove code",
1305 t.ident, f.sig.ident
1306 ));
1307 }
1308 }
1309 }
1310 Ok(())
1311 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1312 Item::Enum(e) => {
1313 let def = self.enums[&e.ident.to_string()].clone();
1314 self.emit_enum(&def);
1315 Ok(())
1316 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1317 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1318 let t = self.map_ty(&c.ty)?.owned();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1319 // The annotation types the initialiser, exactly as it does for
1320 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
1321 let v = self.expr_at(&c.expr, Some(&t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1322 self.bind(&c.ident.to_string(), t.clone());
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1323 // Only a top-level const is exported; `*` on a local is not
1324 // Nim syntax.
1325 let star = if self.indent == 0 { "*" } else { "" };
1326 let line = format!(
1327 "const {}{}: {} = {}",
1328 ident(&c.ident.to_string()),
1329 star,
1330 t.render(),
1331 v.code
1332 );
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1333 self.line(&line);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1334 if self.indent == 0 {
1335 self.blank();
1336 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1337 Ok(())
1338 }
1339 Item::Impl(im) => {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1340 let outer_g =
1341 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 18h ago1342 let self_ty = self.map_ty(&im.self_ty)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1343 let outer = self.self_ty.replace(self_ty.clone());
1344 let r = self.impl_body(im, &self_ty);
1345 self.self_ty = outer;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1346 self.impl_generics = outer_g;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1347 r
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1348 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1349 // `use` and `extern crate` are resolution directives with no Nim
1350 // analogue once everything is one module.
1351 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago1352 Item::ForeignMod(f) => self.foreign_mod(f),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1353 Item::Mod(m) if m.content.is_some() => {
1354 // An inline `mod` is flattened; Nim has no nested modules in a
1355 // single file.
1356 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
1357 for i in &items {
1358 self.item(i)?;
1359 }
1360 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1361 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1362 Item::Mod(m) => {
1363 // Satisfied if that file was passed in too; everything is one
1364 // Nim module, so the declaration itself emits nothing.
1365 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
1366 return Ok(());
1367 }
1368 Err(format!(
1369 "`mod {};` refers to another file that was not passed to \
1370 rustnim; add it to the input list",
1371 m.ident
1372 ))
1373 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago1374 other => Err(format!("unsupported item: {}", item_kind(other))),
1375 }
1376 }
1377
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1378 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
1379 fn none_of(&self, expect: Option<&Nim>) -> String {
1380 match expect {
1381 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
1382 format!("rsNone[{}]()", a[0].render())
1383 }
1384 _ => "rsNone()".to_string(),
1385 }
1386 }
1387
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1388 fn impl_body(&mut self, im: &syn::ItemImpl, self_ty: &Nim) -> Result<(), String> {
1389 if let Some((path, _)) = &im.trait_ {
1390 let tr = path_name(path);
1391 if im.items.is_empty() {
1392 return Ok(());
1393 }
1394 if is_fmt_trait(&tr) {
1395 let syn::ImplItem::Fn(m) = &im.items[0] else {
1396 return Err(format!("unsupported item in `impl {tr}`"));
1397 };
1398 return self.fmt_impl(&tr, self_ty, &m.sig, &m.block);
1399 }
1400 if tr == "From" {
1401 let syn::ImplItem::Fn(m) = &im.items[0] else {
1402 return Err("`impl From` must contain `fn from`".into());
1403 };
1404 let name = {
1405 let (params, _) = self.signature(&m.sig)?;
1406 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
1407 self.from_impls[&(type_name(&src), type_name(self_ty))].clone()
1408 };
1409 return self.func_named(&name, &m.sig, &m.block, None);
1410 }
1411 let tyname = type_name(self_ty);
1412 for it in &im.items {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago1413 if let syn::ImplItem::Const(c) = it {
1414 self.assoc_const(&tyname, c)?;
1415 continue;
1416 }
1417 if matches!(it, syn::ImplItem::Type(_)) {
1418 continue; // a type binding emits nothing
1419 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1420 let syn::ImplItem::Fn(m) = it else {
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago1421 return Err(format!(
1422 "unsupported item in `impl {tr}`: only `fn`, `type` and \
1423 `const` are implemented"
1424 ));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1425 };
1426 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1427 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
1428 self.func_named(&nim, &m.sig, &m.block, recv)?;
1429 }
1430 return Ok(());
1431 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago1432 let tyname = type_name(self_ty);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1433 for it in &im.items {
1434 match it {
1435 syn::ImplItem::Fn(m) => {
1436 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1437 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
1438 self.func_named(&nim, &m.sig, &m.block, recv)?;
1439 }
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago1440 syn::ImplItem::Type(_) => {}
1441 syn::ImplItem::Const(c) => self.assoc_const(&tyname, c)?,
1442 _ => {
1443 return Err("only `fn`, `type` and `const` items are supported \
1444 inside `impl`"
1445 .into())
1446 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1447 }
1448 }
1449 Ok(())
1450 }
1451
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago1452 /// An `extern "C" { .. }` block: declarations of symbols someone else
1453 /// defines. Nim's `importc` is the same statement, and both are bound by
1454 /// the C ABI, so the two declarations describe one symbol rather than one
1455 /// being a translation of the other.
1456 fn foreign_mod(&mut self, f: &syn::ItemForeignMod) -> Result<(), String> {
1457 let abi = f
1458 .abi
1459 .name
1460 .as_ref()
1461 .map(|s| s.value())
1462 .unwrap_or_else(|| "C".into());
1463 if abi != "C" {
1464 return Err(format!(
1465 "`extern \"{abi}\"` is not the C ABI; only that one has a Nim \
1466 equivalent"
1467 ));
1468 }
1469 // A const-qualified C pointer needs a type whose C spelling carries
1470 // the `const`; Nim has no such type built in, so one is declared per
1471 // element type actually used.
1472 let mut needed: Vec<Nim> = Vec::new();
1473 for it in &f.items {
1474 if let syn::ForeignItem::Fn(fi) = it {
1475 let (params, ret) = self.signature(&fi.sig)?;
1476 for t in params.iter().chain(std::iter::once(&ret)) {
1477 if let Nim::ConstPtr(inner) = t {
1478 if !matches!(&**inner, Nim::Prim(p) if p == "void")
1479 && !needed.contains(t)
1480 {
1481 needed.push(t.clone());
1482 }
1483 }
1484 }
1485 }
1486 }
1487 for t in &needed {
1488 let Nim::ConstPtr(inner) = t else { continue };
1489 let alias = ty::const_ptr_alias(inner);
1490 if !self.const_ptrs.insert(alias.clone()) {
1491 continue;
1492 }
1493 let c = ty::c_spelling(inner).ok_or_else(|| {
1494 format!(
1495 "`*const {}` has no C spelling we know, so a const-qualified \
1496 declaration cannot be emitted for it",
1497 inner.render()
1498 )
1499 })?;
1500 self.line(&format!(
1501 "type {}* {{.importc: \"const {} *\", nodecl.}} = distinct pointer",
1502 alias, c
1503 ));
1504 }
1505
1506 for it in &f.items {
1507 match it {
1508 syn::ForeignItem::Fn(fi) => {
1509 if fi.sig.variadic.is_some() {
1510 return Err(format!(
1511 "`{}` is variadic; Nim needs `varargs` with a fixed \
1512 calling shape, which this does not give us",
1513 fi.sig.ident
1514 ));
1515 }
1516 let name = fi.sig.ident.to_string();
1517 let head = self.head_of(&name, &fi.sig, None)?;
1518 // `importc` names the C symbol, so the Nim name may differ
1519 // from it without changing what is linked.
1520 self.line(&format!(
1521 "{} {{.importc: \"{}\", cdecl.}}",
1522 head, name
1523 ));
1524 let (params, ret) = self.signature(&fi.sig)?;
1525 self.fns.insert(
1526 (self.cur_mod.clone(), name.clone()),
1527 Sig { params, ret, generics: Vec::new() },
1528 );
1529 self.foreign.insert(name);
1530 }
1531 syn::ForeignItem::Static(st) => {
1532 let t = self.map_ty(&st.ty)?;
1533 let name = st.ident.to_string();
1534 self.line(&format!(
1535 "var {}* {{.importc: \"{}\".}}: {}",
1536 ident(&name),
1537 name,
1538 t.render()
1539 ));
1540 self.bind(&name, t);
1541 }
1542 syn::ForeignItem::Type(_) => {
1543 // An opaque C type: Nim spells it as a distinct object.
1544 continue;
1545 }
1546 _ => return Err("unsupported item in an `extern` block".into()),
1547 }
1548 }
1549 self.blank();
1550 Ok(())
1551 }
1552
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago1553 /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant
1554 /// namespace, so it becomes a module-level const named for both.
1555 fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {
1556 let t = self.map_ty(&c.ty)?.owned();
1557 let v = self.expr_at(&c.expr, Some(&t))?;
1558 let name = format!("{}_{}", tyname, c.ident);
1559 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
1560 self.blank();
1561 self.assoc_consts
1562 .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));
1563 Ok(())
1564 }
1565
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago1566 /// The type an operator impl declares for its right-hand operand.
1567 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
1568 let n = type_name(t.as_ref()?);
1569 let sig = self.methods.get(&(n, op_method(op).to_string()))?;
1570 sig.params.get(1).cloned().map(|t| t.unvar())
1571 }
1572
1573 /// The proc implementing `op` for a user type, if there is one.
1574 fn op_proc(&self, t: &Option<Nim>, op: &str) -> Option<String> {
1575 let n = type_name(t.as_ref()?);
1576 let tr = OPERATOR_TRAITS.iter().find(|(_, o)| *o == op)?.0;
1577 if self.op_impls.contains_key(&(n.clone(), op.to_string())) {
1578 Some(trait_method_name(&n, tr, OP_METHOD.iter().find(|(o, _)| *o == op)?.1))
1579 } else {
1580 None
1581 }
1582 }
1583
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago1584 /// Register a `bitflags!` type's operations so call sites resolve.
1585 fn collect_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> {
1586 let input: crate::macros::BitflagsInput = mac
1587 .parse_body()
1588 .map_err(|e| format!("`bitflags!`: {e}"))?;
1589 for def in &input.0 {
1590 let name = def.name.to_string();
1591 let repr = self.map_ty(&def.repr)?;
1592 if !repr.is_integer() {
1593 return Err(format!("`bitflags! {name}` needs an integer representation"));
1594 }
1595 let me = Nim::Named(name.clone(), vec![]);
1596 let b = Nim::Prim("bool".into());
1597 self.structs
1598 .insert(name.clone(), vec![("bitsField".into(), repr.clone())]);
1599 self.type_generics.insert(name.clone(), Vec::new());
1600
1601 let mut m = |n: &str, params: Vec<Nim>, ret: Nim, nim: String| {
1602 self.methods.insert(
1603 (name.clone(), n.to_string()),
1604 Sig { params, ret, generics: Vec::new() },
1605 );
1606 self.statics.insert((name.clone(), n.to_string()), nim);
1607 };
1608 let s1 = vec![me.clone()];
1609 let s2 = vec![me.clone(), me.clone()];
1610 let vs2 = vec![Nim::Var(Box::new(me.clone())), me.clone()];
1611 m("bits", s1.clone(), repr.clone(), format!("{name}_bits"));
1612 m("is_empty", s1.clone(), b.clone(), format!("{name}_is_empty"));
1613 m("is_all", s1.clone(), b.clone(), format!("{name}_is_all"));
1614 m("contains", s2.clone(), b.clone(), format!("{name}_contains"));
1615 m("intersects", s2.clone(), b.clone(), format!("{name}_intersects"));
1616 for (rust, nim) in [
1617 ("union", "union"),
1618 ("intersection", "intersection"),
1619 ("difference", "difference"),
1620 ("symmetric_difference", "symmetric_difference"),
1621 ] {
1622 m(rust, s2.clone(), me.clone(), format!("{name}_{nim}"));
1623 }
1624 for n in ["insert", "remove", "toggle"] {
1625 m(n, vs2.clone(), Nim::Unit, format!("{name}_{n}"));
1626 }
1627 m(
1628 "set",
1629 vec![Nim::Var(Box::new(me.clone())), me.clone(), b.clone()],
1630 Nim::Unit,
1631 format!("{name}_set"),
1632 );
1633 m("empty", vec![], me.clone(), format!("{name}_empty"));
1634 m("all", vec![], me.clone(), format!("{name}_all"));
1635 m(
1636 "from_bits",
1637 vec![repr.clone()],
1638 Nim::Named("Option".into(), vec![me.clone()]),
1639 format!("{name}_from_bits"),
1640 );
1641 m(
1642 "from_bits_truncate",
1643 vec![repr.clone()],
1644 me.clone(),
1645 format!("{name}_from_bits_truncate"),
1646 );
1647 m("complement", s1.clone(), me.clone(), format!("{name}_complement"));
1648
1649 // The operator forms, routed through the same dispatch that a
1650 // hand-written `impl BitOr` would use.
1651 for (op, trait_name, method) in [
1652 ("|", "BitOr", "bitor"),
1653 ("&", "BitAnd", "bitand"),
1654 ("^", "BitXor", "bitxor"),
1655 ("-", "Sub", "sub"),
1656 ("not", "Not", "not"),
1657 ] {
1658 self.op_impls.insert((name.clone(), op.to_string()), ());
1659 let params = if op == "not" { s1.clone() } else { s2.clone() };
1660 self.methods.insert(
1661 (name.clone(), method.to_string()),
1662 Sig { params, ret: me.clone(), generics: Vec::new() },
1663 );
1664 let _ = trait_name;
1665 }
1666 self.bitflags.insert(name);
1667 }
1668 Ok(())
1669 }
1670
1671 /// Emit the Nim for a `bitflags!` type. See `src/macros.rs` for why this
1672 /// is lowered directly rather than by expanding the macro.
1673 fn emit_bitflags(&mut self, mac: &syn::Macro) -> Result<(), String> {
1674 let input: crate::macros::BitflagsInput = mac
1675 .parse_body()
1676 .map_err(|e| format!("`bitflags!`: {e}"))?;
1677 for def in &input.0 {
1678 let name = def.name.to_string();
1679 let repr = self.map_ty(&def.repr)?;
1680 let r = repr.render();
1681
1682 self.line(&format!("type {name}* = object"));
1683 self.line(&format!(" bitsField*: {r}"));
1684 self.blank();
1685 self.line(&format!("proc {name}_bits*(x: {name}): {r} = x.bitsField"));
1686
1687 // The constants. A flag's value may name earlier flags, as
1688 // `const ALL = Self::READ.bits() | ..` does, so they are emitted
1689 // in order and each is in scope for the next.
1690 self.push_scope();
1691 self.bind_static_type(&name, &repr);
1692 for (fname, value) in &def.flags {
1693 let v = self.expr_at(value, Some(&repr))?;
1694 self.line(&format!(
1695 "const {}{}* = {}(bitsField: {})",
1696 name, fname, name, v.code
1697 ));
1698 self.flag_consts
1699 .insert((name.clone(), fname.to_string()), format!("{name}{fname}"));
1700 }
1701 self.pop_scope();
1702
1703 let all: Vec<String> = def
1704 .flags
1705 .iter()
1706 .map(|(f, _)| format!("{name}{f}.bitsField"))
1707 .collect();
1708 let all_bits = if all.is_empty() {
1709 format!("{}(0)", r)
1710 } else {
1711 all.join(" or ")
1712 };
1713 self.blank();
1714 self.line(&format!("const {name}AllBits: {r} = {all_bits}"));
1715 self.blank();
1716
1717 for l in [
1718 format!("proc {name}_empty*(): {name} = {name}(bitsField: {r}(0))"),
1719 format!("proc {name}_all*(): {name} = {name}(bitsField: {name}AllBits)"),
1720 format!("proc {name}_is_empty*(x: {name}): bool = x.bitsField == {r}(0)"),
1721 format!("proc {name}_is_all*(x: {name}): bool = (x.bitsField and {name}AllBits) == {name}AllBits"),
1722 format!("proc {name}_contains*(a, b: {name}): bool = (a.bitsField and b.bitsField) == b.bitsField"),
1723 format!("proc {name}_intersects*(a, b: {name}): bool = (a.bitsField and b.bitsField) != {r}(0)"),
1724 format!("proc {name}_union*(a, b: {name}): {name} = {name}(bitsField: a.bitsField or b.bitsField)"),
1725 format!("proc {name}_intersection*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and b.bitsField)"),
1726 format!("proc {name}_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField and (not b.bitsField))"),
1727 format!("proc {name}_symmetric_difference*(a, b: {name}): {name} = {name}(bitsField: a.bitsField xor b.bitsField)"),
1728 // `!x` complements and then masks to `all()`, which is what
1729 // bitflags does and not what a plain `not` would give.
1730 format!("proc {name}_complement*(x: {name}): {name} = {name}(bitsField: (not x.bitsField) and {name}AllBits)"),
1731 format!("proc {name}_from_bits_truncate*(b: {r}): {name} = {name}(bitsField: b and {name}AllBits)"),
1732 format!("proc {name}_from_bits*(b: {r}): Option[{name}] ="),
1733 format!(" if (b and (not {name}AllBits)) != {r}(0): rsNone[{name}]() else: rsSome({name}(bitsField: b))"),
1734 format!("proc {name}_insert*(x: var {name}, o: {name}) = x.bitsField = x.bitsField or o.bitsField"),
1735 format!("proc {name}_remove*(x: var {name}, o: {name}) = x.bitsField = x.bitsField and (not o.bitsField)"),
1736 format!("proc {name}_toggle*(x: var {name}, o: {name}) = x.bitsField = x.bitsField xor o.bitsField"),
1737 format!("proc {name}_set*(x: var {name}, o: {name}, on: bool) ="),
1738 format!(" if on: {name}_insert(x, o) else: {name}_remove(x, o)"),
1739 format!("proc rsBitOr_{name}_bitor*(a, b: {name}): {name} = {name}_union(a, b)"),
1740 format!("proc rsBitAnd_{name}_bitand*(a, b: {name}): {name} = {name}_intersection(a, b)"),
1741 format!("proc rsBitXor_{name}_bitxor*(a, b: {name}): {name} = {name}_symmetric_difference(a, b)"),
1742 format!("proc rsSub_{name}_sub*(a, b: {name}): {name} = {name}_difference(a, b)"),
1743 format!("proc rsNot_{name}_not*(a: {name}): {name} = {name}_complement(a)"),
1744 ] {
1745 self.line(&l);
1746 }
1747
1748 // Debug prints the set flag names, or `0x0` when empty -- again
1749 // matching the crate rather than a guess.
1750 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1751 self.line(&format!(" result = \"{name}(\""));
1752 self.line(" var first = true");
1753 for (fname, _) in &def.flags {
1754 self.line(&format!(
1755 " if (x.bitsField and {name}{f}.bitsField) == {name}{f}.bitsField and {name}{f}.bitsField != {r}(0):",
1756 f = fname
1757 ));
1758 self.line(" if not first: result.add(\" | \")");
1759 self.line(&format!(" result.add(\"{fname}\")"));
1760 self.line(" first = false");
1761 }
1762 self.line(" if first: result.add(\"0x0\")");
1763 self.line(" result.add(\")\")");
1764 self.blank();
1765 }
1766 Ok(())
1767 }
1768
1769 fn bind_static_type(&mut self, _name: &str, _repr: &Nim) {}
1770
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1771 fn emit_enum(&mut self, def: &EnumDef) {
1772 let name = ident(&def.name);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1773 let g = Self::gen_list(
1774 self.type_generics.get(&def.name).map(|v| &v[..]).unwrap_or(&[]),
1775 );
1776 if def.simple && !g.is_empty() {
1777 // A Nim `enum` cannot take parameters; an all-unit generic enum
1778 // has no payload to be generic in anyway, so this would be a
1779 // parameter that never appears.
1780 // Fall through to the object-variant form instead.
1781 }
1782 if def.simple && g.is_empty() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1783 // Every variant is a unit variant, so a plain Nim enum is an exact
1784 // fit: it compares, orders and `case`-checks like Rust's.
1785 self.line(&format!("type {name}* = enum"));
1786 self.indent += 1;
1787 for v in &def.variants {
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago1788 match &v.discriminant {
1789 Some(d) => self.line(&format!("{} = {}", ident(&v.name), d)),
1790 None => self.line(&ident(&v.name)),
1791 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1792 }
1793 self.indent -= 1;
1794 self.blank();
1795 self.line(&format!("proc rsDebug*(x: {name}): string ="));
1796 self.indent += 1;
1797 self.line("case x");
1798 for v in &def.variants {
1799 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
1800 }
1801 self.indent -= 1;
1802 self.blank();
1803 return;
1804 }
1805
1806 // A data-carrying enum is a Nim object variant: one discriminant enum
1807 // plus a branch per variant. This is the same shape the prelude uses
1808 // for `Option` and `Result`.
1809 self.line("type");
1810 self.indent += 1;
1811 self.line(&format!("{}Kind* = enum", name));
1812 self.indent += 1;
1813 for v in &def.variants {
1814 self.line(&def.kind_ident(&v.name));
1815 }
1816 self.indent -= 1;
1817 self.blank();
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1818 self.line(&format!("{}*{} = object", name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1819 self.indent += 1;
1820 self.line(&format!("case kind*: {}Kind", name));
1821 for v in &def.variants {
1822 if v.fields.is_empty() {
1823 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
1824 } else {
1825 self.line(&format!("of {}:", def.kind_ident(&v.name)));
1826 self.indent += 1;
1827 for (f, t) in &v.fields {
1828 self.line(&format!("{}*: {}", ident(f), t.render()));
1829 }
1830 self.indent -= 1;
1831 }
1832 }
1833 self.indent -= 2;
1834 self.blank();
1835
1836 for v in &def.variants {
1837 let args: Vec<String> = v
1838 .fields
1839 .iter()
1840 .enumerate()
1841 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
1842 .collect();
1843 let inits: Vec<String> = v
1844 .fields
1845 .iter()
1846 .enumerate()
1847 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
1848 .collect();
1849 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
1850 all.extend(inits);
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1851 let ret = format!("{}{}", name, g);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1852 self.line(&format!(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1853 "proc {}*{}({}): {} = {}({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1854 def.ctor_ident(&v.name),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1855 g,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1856 args.join(", "),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1857 ret,
1858 ret,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1859 all.join(", ")
1860 ));
1861 }
1862 self.blank();
1863
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1864 self.line(&format!("proc rsDebug*{}(x: {}{}): string =", g, name, g));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1865 self.indent += 1;
1866 self.line("case x.kind");
1867 for v in &def.variants {
1868 if v.fields.is_empty() {
1869 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
1870 } else {
1871 let parts: Vec<String> = v
1872 .fields
1873 .iter()
1874 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
1875 .collect();
1876 self.line(&format!(
1877 "of {}: \"{}(\" & {} & \")\"",
1878 def.kind_ident(&v.name),
1879 v.name,
1880 parts.join(" & \", \" & ")
1881 ));
1882 }
1883 }
1884 self.indent -= 1;
1885 self.blank();
1886 }
1887
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago1888 /// The concrete type an enum variant constructs, and the `[T]` list to
1889 /// spell at the constructor when the enum is generic.
1890 fn variant_type(
1891 &self,
1892 def: &EnumDef,
1893 expect: Option<&Nim>,
1894 ) -> Result<(Nim, String), String> {
1895 let params = self.type_generics.get(&def.name).cloned().unwrap_or_default();
1896 if params.is_empty() {
1897 return Ok((Nim::Named(def.name.clone(), vec![]), String::new()));
1898 }
1899 match expect {
1900 Some(Nim::Named(n, a)) if *n == def.name && a.len() == params.len() => Ok((
1901 Nim::Named(def.name.clone(), a.clone()),
1902 format!("[{}]", a.iter().map(|t| t.render()).collect::<Vec<_>>().join(", ")),
1903 )),
1904 _ => Err(format!(
1905 "`{}` is a variant of a generic enum, and its type parameters \
1906 cannot be inferred here; annotate the binding or the return type",
1907 def.name
1908 )),
1909 }
1910 }
1911
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago1912 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
1913 /// to the enum that declares it.
1914 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
1915 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
1916 let last = segs.last()?.clone();
1917 if segs.len() >= 2 {
1918 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
1919 if def.get(&last).is_some() {
1920 return Some((def.clone(), last));
1921 }
1922 }
1923 }
1924 // Unqualified: only unambiguous if exactly one enum declares it.
1925 match self.variant_owner.get(&last) {
1926 Some(owners) if owners.len() == 1 => {
1927 let def = self.enums.get(&owners[0])?;
1928 Some((def.clone(), last))
1929 }
1930 _ => None,
1931 }
1932 }
1933
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1934 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
1935 ///
1936 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
1937 /// observable result of `{}` is exactly the bytes written. So the method
1938 /// becomes `proc rsDisplay(self: T): string` and every write through the
1939 /// formatter produces that string. A `fmt` body that does anything else
1940 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
1941 /// because those affect the output and this model does not carry them.
1942 /// The window an expression names, if it names one.
1943 fn window_of(&self, e: &Expr) -> Option<Alias> {
1944 match e {
1945 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
1946 Some(a @ Alias::Window { .. }) => Some(a),
1947 _ => None,
1948 },
1949 Expr::Reference(r) => self.window_of(&r.expr),
1950 Expr::Paren(p) => self.window_of(&p.expr),
1951 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
1952 _ => None,
1953 }
1954 }
1955
1956 /// Whether an expression is the `Formatter` parameter of the formatting
1957 /// impl currently being lowered.
1958 fn is_fmt_param(&self, e: &Expr) -> bool {
1959 let Some(f) = &self.fmt_param else { return false };
1960 match e {
1961 Expr::Path(p) => path_name(&p.path) == *f,
1962 Expr::Reference(r) => self.is_fmt_param(&r.expr),
1963 Expr::Paren(p) => self.is_fmt_param(&p.expr),
1964 _ => false,
1965 }
1966 }
1967
1968 fn fmt_impl(
1969 &mut self,
1970 tr: &str,
1971 self_ty: &Nim,
1972 sig: &syn::Signature,
1973 body: &syn::Block,
1974 ) -> Result<(), String> {
1975 let proc_name = fmt_proc(tr);
1976 // The formatter is the parameter after `self`.
1977 let f = sig
1978 .inputs
1979 .iter()
1980 .filter_map(|a| match a {
1981 FnArg::Typed(t) => match &*t.pat {
1982 Pat::Ident(i) => Some(i.ident.to_string()),
1983 _ => None,
1984 },
1985 _ => None,
1986 })
1987 .next()
1988 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1989
1990 self.push_scope();
1991 self.bind("self", self_ty.clone());
1992 let saved = self.fmt_param.replace(f);
1993 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 16h ago1994 // No assignment target: a formatter write *appends*, because a `fmt`
1995 // body may write repeatedly -- `UpperHex` writes once per byte in a
1996 // loop -- and assigning would keep only the last one.
1997 let outer_target = self.target.take();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago1998
1999 self.line(&format!(
2000 "proc {}*(self: {}): string =",
2001 proc_name,
2002 self_ty.render()
2003 ));
2004 self.indent += 1;
2005 let before = self.out.len();
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago2006 let tail = self.block_body(body)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2007 self.emit_tail(tail);
2008 if self.out.len() == before {
2009 self.line("discard");
2010 }
2011 self.indent -= 1;
2012
2013 self.target = outer_target;
2014 self.ret = outer_ret;
2015 self.fmt_param = saved;
2016 self.pop_scope();
2017 self.blank();
2018 Ok(())
2019 }
2020
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2021 fn func(
2022 &mut self,
2023 sig: &syn::Signature,
2024 body: &syn::Block,
2025 recv: Option<Nim>,
2026 ) -> Result<(), String> {
2027 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2028 self.func_named(&name.clone(), sig, body, recv)
2029 }
2030
2031 fn func_named(
2032 &mut self,
2033 name: &str,
2034 sig: &syn::Signature,
2035 body: &syn::Block,
2036 recv: Option<Nim>,
2037 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2038 let (ptys, ret) = self.signature(sig)?;
2039
2040 self.push_scope();
2041 let mut rendered: Vec<String> = Vec::new();
2042
2043 if let Some(self_ty) = recv {
2044 // `&mut self` and `mut self` both mean the body may mutate the
2045 // receiver; only the former is observable by the caller, and a Nim
2046 // `var` parameter is the faithful spelling of that.
2047 let mutable = matches!(
2048 sig.inputs.first(),
2049 Some(FnArg::Receiver(r))
2050 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
2051 );
2052 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
2053 rendered.push(format!("self: {}", t.render()));
2054 self.bind("self", self_ty);
2055 }
2056
2057 let typed: Vec<&syn::PatType> = sig
2058 .inputs
2059 .iter()
2060 .filter_map(|a| match a {
2061 FnArg::Typed(t) => Some(t),
2062 _ => None,
2063 })
2064 .collect();
2065 for (p, t) in typed.iter().zip(ptys.iter()) {
2066 let pname = match &*p.pat {
2067 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2068 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
2069 // still needs a name for it.
2070 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2071 _ => return Err("only plain identifier parameters are supported".into()),
2072 };
2073 rendered.push(format!("{}: {}", ident(&pname), t.render()));
2074 // Inside the body a `var T` parameter is used exactly like a `T`.
2075 self.bind(&pname, t.clone().owned());
2076 }
2077
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago2078 let mut gparams = self.impl_generics.clone();
2079 for g in Self::generics_of(&sig.generics) {
2080 if !gparams.contains(&g) {
2081 gparams.push(g);
2082 }
2083 }
2084 let gens = Self::gen_list(&gparams);
2085 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 18h ago2086 let head = if ret == Nim::Unit {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago2087 format!("proc {}*{}({}) =", ident(name), gens, rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2088 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago2089 format!(
2090 "proc {}*{}({}): {} =",
2091 ident(name),
2092 gens,
2093 rendered.join(", "),
2094 ret.render()
2095 )
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2096 };
2097 self.line(&head);
2098 self.indent += 1;
2099 let outer_ret = self.ret.replace(ret.clone());
2100
2101 // A Rust fn's trailing expression is its return value. Naming Nim's
2102 // implicit `result` as the target makes that true whether the tail is
2103 // a plain expression or an `if`/`match` with statement arms.
2104 let outer_target = if ret == Nim::Unit {
2105 self.target.take()
2106 } else {
2107 self.target.replace(("result".to_string(), Some(ret.clone())))
2108 };
2109 let before = self.out.len();
2110 let tail = self.block_body_at(body, Some(&ret))?;
2111 self.target = outer_target;
2112 match tail {
2113 Some(v) if ret != Nim::Unit => {
2114 let code = v.code.clone();
2115 self.line(&format!("result = {code}"));
2116 }
2117 Some(v) => {
2118 // A trailing expression in a `()`-returning fn is evaluated for
2119 // its effect; Nim requires an explicit discard.
2120 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2121 if needs_discard && !v.code.is_empty() {
2122 let code = v.code.clone();
2123 self.line(&format!("discard {code}"));
2124 }
2125 }
2126 None => {}
2127 }
2128 if self.out.len() == before {
2129 self.line("discard");
2130 }
2131
2132 self.indent -= 1;
2133 self.ret = outer_ret;
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago2134 self.fn_generics = outer_fg;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2135 self.pop_scope();
2136 self.blank();
2137 Ok(())
2138 }
2139
2140 // ---------------------------------------------------------- statements
2141
2142 /// Lower a block's statements. Returns the block's trailing expression,
2143 /// if it has one, *without* emitting it — the caller decides whether that
2144 /// value is a return value, a binding, or discarded.
2145 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
2146 self.block_body_at(b, None)
2147 }
2148
2149 fn block_body_at(
2150 &mut self,
2151 b: &syn::Block,
2152 expect: Option<&Nim>,
2153 ) -> Result<Option<Val>, String> {
2154 // An assignment target belongs to *this* block's trailing expression
2155 // only. A non-final `if` is a statement and must not assign anything.
2156 let target = self.target.take();
2157 let n = b.stmts.len();
2158 let mut tail = None;
2159 for (i, st) in b.stmts.iter().enumerate() {
2160 let last = i + 1 == n;
2161 match st {
2162 Stmt::Expr(e, None) if last && expressible(e) => {
2163 tail = Some(self.expr_at(e, expect)?)
2164 }
2165 Stmt::Expr(e, None) if last => {
2166 // A trailing `if`/`match` with statement arms, or a loop.
2167 // Lower it as statements; if this block's value is wanted,
2168 // each arm assigns it.
2169 match &target {
2170 Some((t, ty)) => {
2171 let (t, ty) = (t.clone(), ty.clone());
2172 self.assign_from(e, &t, ty.as_ref())?;
2173 }
2174 None => self.stmt(st)?,
2175 }
2176 }
2177 _ => self.stmt(st)?,
2178 }
2179 }
2180 self.target = target;
2181 Ok(tail)
2182 }
2183
2184 /// Lower a block in statement position (loop bodies, `if` arms).
2185 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
2186 self.push_scope();
2187 self.indent += 1;
2188 let before = self.out.len();
2189 let want = self.target.clone().and_then(|(_, t)| t);
2190 let tail = self.block_body_at(b, want.as_ref())?;
2191 self.emit_tail(tail);
2192 if self.out.len() == before {
2193 self.line("discard");
2194 }
2195 self.indent -= 1;
2196 self.pop_scope();
2197 Ok(())
2198 }
2199
2200 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
2201 match s {
2202 Stmt::Local(l) => self.local(l),
2203 Stmt::Expr(e, _) => {
2204 let v = self.expr_stmt(e)?;
2205 if let Some(v) = v {
2206 // A bare expression with a value must be discarded in Nim.
2207 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2208 let code = v.code.clone();
2209 if needs {
2210 self.line(&format!("discard {code}"));
2211 } else if !code.is_empty() {
2212 self.line(&code);
2213 }
2214 }
2215 Ok(())
2216 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago2217 // A `const` declared inside a function body is local to it, and
2218 // must be emitted here rather than skipped as an already-emitted
2219 // top-level type.
2220 Stmt::Item(i) => self.item_inner(i),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2221 Stmt::Macro(m) => {
2222 let line = self.macro_call(&m.mac)?;
2223 self.line(&line);
2224 Ok(())
2225 }
2226 }
2227 }
2228
2229 fn local(&mut self, l: &Local) -> Result<(), String> {
2230 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
2231 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
2232 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2233 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 18h ago2234 _ => return Err("only `let <ident>` bindings are supported".into()),
2235 },
2236 Pat::Wild(_) => ("_".into(), false, None),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago2237 Pat::Tuple(t) => return self.local_tuple(l, t),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2238 _ => return Err("destructuring `let` is not implemented yet".into()),
2239 };
2240
2241 let Some(init) = &l.init else {
2242 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
2243 // not. Rust's own rules make reading it before assignment illegal,
2244 // so the two agree on every program rustc accepts.
2245 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
2246 let t = t.owned();
2247 self.line(&format!("var {}: {}", ident(&name), t.render()));
2248 self.bind(&name, t);
2249 return Ok(());
2250 };
2251 if init.diverge.is_some() {
2252 return Err("`let ... else` is not implemented yet".into());
2253 }
2254
2255 if !expressible(&init.expr) && name != "_" {
2256 // The initialiser is an `if`/`match` whose arms are statements.
2257 // Declare first, then let each arm assign into the binding.
2258 let t = ann
2259 .clone()
2260 .ok_or_else(|| {
2261 format!(
2262 "`let {name} = match/if ...` needs a type annotation: \
2263 its arms are statements, so the binding must be \
2264 declared before they run"
2265 )
2266 })?
2267 .owned();
2268 self.line(&format!("var {}: {}", ident(&name), t.render()));
2269 self.bind(&name, t.clone());
2270 let target = ident(&name);
2271 return self.assign_from(&init.expr, &target, Some(&t));
2272 }
2273
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago2274 // `let it = xs.chunks_exact(k)` binds an iterator, not a value.
2275 if is_iterator_expr(&init.expr) {
2276 let it = self.resolve_iter(&init.expr)?;
2277 self.bind_alias(&name, Alias::Iterator(Box::new(it)));
2278 return Ok(());
2279 }
2280
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2281 let v = self.expr_at(&init.expr, ann.as_ref())?;
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 16h ago2282
2283 // `let s = &buf[..n]` binds a view of a place that is already in
2284 // scope. Nim's borrow checker will not let a `let` borrow out of a
2285 // local, and there is nothing to materialise anyway -- a view is a
2286 // reference. Binding it as an alias substitutes the same expression at
2287 // each use, which re-evaluates nothing because the initialiser is a
2288 // place expression with no side effects.
2289 if v.window.is_none()
2290 && matches!(v.ty, Some(Nim::OpenArray(_)))
2291 && is_pure_place(&init.expr)
2292 {
2293 let t = v.ty.clone().unwrap();
2294 let elem = match &t {
2295 Nim::OpenArray(e) => Some((**e).clone()),
2296 _ => None,
2297 };
2298 self.bind_alias(&name, Alias::Value { code: v.code.clone(), ty: Some(t) });
2299 let _ = elem;
2300 return Ok(());
2301 }
2302
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2303 if let Some(w) = v.window.clone() {
2304 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
2305 // view into the caller's buffer. Copying it into a `seq` would
2306 // still print the right bytes but would stop writes reaching the
2307 // caller, so it is bound as an alias.
2308 if v.guard.is_some() && v.guard_err.is_some() {
2309 return Err(format!(
2310 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
2311 which Nim cannot represent; apply `?` or `unwrap()` to it \
2312 in the same expression"
2313 ));
2314 }
2315 self.bind_alias(&name, w);
2316 return Ok(());
2317 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago2318 // A `let` binding a borrow keeps the view: `let res = encode(..)?`
2319 // names the caller's buffer, and copying it into a `seq` would still
2320 // print the right bytes while silently breaking the aliasing.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2321 let t = match (ann, &v.ty) {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago2322 (Some(a), _) => a.unvar(),
2323 (None, Some(t)) => t.clone().unvar(),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2324 (None, None) => {
2325 return Err(format!(
2326 "cannot infer the type of `let {name}`; annotate it — \
2327 guessing here would change integer width, and with it the \
2328 meaning of any arithmetic on `{name}`"
2329 ))
2330 }
2331 };
2332
2333 if name == "_" {
2334 let code = v.code.clone();
2335 self.line(&format!("discard {code}"));
2336 return Ok(());
2337 }
2338 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
2339 // 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 16h ago2340 //
2341 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
2342 // Rust may write through it, and Nim only accepts a `var` where a
2343 // `var` parameter is wanted, so the binding has to be one.
2344 let mutable = mutable || is_mut_borrow(&init.expr);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2345 let kw = if mutable { "var" } else { "let" };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago2346 // Inside a generic proc the binding's type may mention a parameter Nim
2347 // will infer; naming it in an annotation would not resolve.
2348 let line = if self.mentions_type_param(&t) {
2349 format!("{} {} = {}", kw, ident(&name), v.code)
2350 } else {
2351 format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)
2352 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2353 self.line(&line);
2354 self.bind(&name, t);
2355 Ok(())
2356 }
2357
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago2358 /// `let (a, b) = ..` — tuple destructuring.
2359 fn local_tuple(&mut self, l: &Local, t: &syn::PatTuple) -> Result<(), String> {
2360 let Some(init) = &l.init else {
2361 return Err("a destructuring `let` needs an initialiser".into());
2362 };
2363 let names: Vec<(String, bool)> = t
2364 .elems
2365 .iter()
2366 .map(|p| match p {
2367 Pat::Ident(i) => Ok((i.ident.to_string(), i.mutability.is_some())),
2368 Pat::Wild(_) => Ok(("_".to_string(), false)),
2369 _ => Err("only plain identifiers are supported in a destructuring `let`"),
2370 })
2371 .collect::<Result<_, _>>()?;
2372
2373 // `split_at` hands back two *views* of the same slice. Nim has no
2374 // tuple of views, and there is nothing to materialise anyway, so each
2375 // name becomes a window into the original.
2376 if let Expr::MethodCall(m) = &*init.expr {
2377 let mname = m.method.to_string();
2378 if (mname == "split_at" || mname == "split_at_mut")
2379 && m.args.len() == 1
2380 && names.len() == 2
2381 {
2382 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
2383 let at = self.expr(&m.args[0])?;
2384 let cut = self.fresh("Cut");
2385 self.line(&format!("let {}: int = int({})", cut, at.code));
2386 self.bind_alias(
2387 &names[0].0,
2388 Alias::Window {
2389 code: code.clone(),
2390 off: base.clone(),
2391 len: cut.clone(),
2392 elem: elem.clone(),
2393 },
2394 );
2395 self.bind_alias(
2396 &names[1].0,
2397 Alias::Window {
2398 code,
2399 off: format!("({} + {})", base, cut),
2400 len: format!("({} - {})", len, cut),
2401 elem,
2402 },
2403 );
2404 return Ok(());
2405 }
2406 }
2407
2408 let v = self.expr(&init.expr)?;
2409 let tys = match &v.ty {
2410 Some(Nim::Tuple(ts)) if ts.len() == names.len() => ts.clone(),
2411 _ => {
2412 return Err(format!(
2413 "cannot destructure this into {} bindings: its type is not a \
2414 tuple of that many elements",
2415 names.len()
2416 ))
2417 }
2418 };
2419 let kw = if names.iter().any(|(_, m)| *m) { "var" } else { "let" };
2420 let lhs: Vec<String> = names.iter().map(|(n, _)| ident(n)).collect();
2421 self.line(&format!("{} ({}) = {}", kw, lhs.join(", "), v.code));
2422 for ((n, _), t) in names.iter().zip(tys) {
2423 self.bind(n, t);
2424 }
2425 Ok(())
2426 }
2427
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2428 /// Expressions that are statements in Rust and statements in Nim too
2429 /// (control flow). Returns `None` when it emitted lines itself.
2430 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
2431 match e {
2432 Expr::If(_) => {
2433 self.if_stmt(e)?;
2434 Ok(None)
2435 }
2436 Expr::While(w) => {
2437 if w.label.is_some() {
2438 return Err("loop labels are not implemented yet".into());
2439 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2440 self.in_loop_cond = true;
2441 let c = self.expr(&w.cond);
2442 self.in_loop_cond = false;
2443 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2444 self.line(&format!("while {}:", c.code));
2445 let saved = self.target.take();
2446 self.nested_block(&w.body)?;
2447 self.target = saved;
2448 Ok(None)
2449 }
2450 Expr::Loop(l) => {
2451 if l.label.is_some() {
2452 return Err("loop labels are not implemented yet".into());
2453 }
2454 self.line("while true:");
2455 let saved = self.target.take();
2456 self.nested_block(&l.body)?;
2457 self.target = saved;
2458 Ok(None)
2459 }
2460 Expr::ForLoop(f) => {
2461 self.for_loop(f)?;
2462 Ok(None)
2463 }
2464 Expr::Block(b) => {
2465 if b.label.is_some() {
2466 return Err("block labels are not implemented yet".into());
2467 }
2468 self.line("block:");
2469 self.nested_block(&b.block)?;
2470 Ok(None)
2471 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2472 Expr::Unsafe(u) => {
2473 // Transparent in statement position too, for the same reason.
2474 self.nested_block_flat(&u.block)?;
2475 Ok(None)
2476 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2477 Expr::Match(_) => {
2478 self.match_stmt(e)?;
2479 Ok(None)
2480 }
2481 Expr::Return(r) => {
2482 match &r.expr {
2483 Some(e) => {
2484 let want = self.ret.clone();
2485 let v = self.expr_at(e, want.as_ref())?;
2486 self.line(&format!("return {}", v.code));
2487 }
2488 None => self.line("return"),
2489 }
2490 Ok(None)
2491 }
2492 Expr::Break(b) => {
2493 if b.expr.is_some() || b.label.is_some() {
2494 return Err("`break` with a value or a label is not implemented yet".into());
2495 }
2496 self.line("break");
2497 Ok(None)
2498 }
2499 Expr::Continue(c) => {
2500 if c.label.is_some() {
2501 return Err("labelled `continue` is not implemented yet".into());
2502 }
2503 self.line("continue");
2504 Ok(None)
2505 }
2506 Expr::Assign(a) => {
2507 let lhs = self.expr(&a.left)?;
2508 if !expressible(&a.right) {
2509 let target = lhs.code.clone();
2510 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
2511 }
2512 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
2513 self.line(&format!("{} = {}", lhs.code, rhs.code));
2514 Ok(None)
2515 }
2516 Expr::Binary(b) if is_compound(&b.op) => {
2517 let lhs = self.expr(&b.left)?;
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago2518 // A compound assignment on a user type goes to that type's own
2519 // `impl OpAssign`, not to Nim's built-in operator.
2520 if let Some(f) = self.op_proc(&lhs.ty, compound_symbol(&b.op)) {
2521 // The impl's own parameter type types the right operand,
2522 // so `b_vec *= 4` takes 4 at the width the impl declares.
2523 let want = self.op_param(&lhs.ty, compound_symbol(&b.op));
2524 let rhs = self.expr_at(&b.right, want.as_ref())?;
2525 self.line(&format!("{}({}, {})", f, lhs.code, rhs.code));
2526 return Ok(None);
2527 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2528 // `i += 1` must widen the literal to `i`'s type, not to the
2529 // i32 an unconstrained Rust literal would default to.
2530 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
2531 let op = self.bin_op(&b.op, &lhs, &rhs)?;
2532 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
2533 // both languages, so the expanded form is always correct.
2534 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
2535 Ok(None)
2536 }
2537 Expr::Macro(m) => {
2538 let line = self.macro_call(&m.mac)?;
2539 self.line(&line);
2540 Ok(None)
2541 }
2542 _ => Ok(Some(self.expr(e)?)),
2543 }
2544 }
2545
2546 /// Lower `e` in statement position, assigning each arm's value to
2547 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
2548 /// the trip when their arms are too big for a Nim `if`-expression.
2549 fn assign_from(
2550 &mut self,
2551 e: &Expr,
2552 target: &str,
2553 expect: Option<&Nim>,
2554 ) -> Result<(), String> {
2555 let saved = self.target.replace((target.to_string(), expect.cloned()));
2556 let r = match e {
2557 Expr::If(_) => self.if_stmt(e),
2558 Expr::Match(_) => self.match_stmt(e),
2559 other => {
2560 let v = self.expr_at(other, expect)?;
2561 self.line(&format!("{} = {}", target, v.code));
2562 Ok(())
2563 }
2564 };
2565 self.target = saved;
2566 r
2567 }
2568
2569 /// Emit a block's value into the active assignment target, if there is
2570 /// one, or discard it if there is not.
2571 fn emit_tail(&mut self, v: Option<Val>) {
2572 let Some(v) = v else { return };
2573 match self.target.clone() {
2574 Some((t, _)) => {
2575 let code = v.code.clone();
2576 self.line(&format!("{t} = {code}"));
2577 }
2578 None => {
2579 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
2580 let code = v.code.clone();
2581 if needs {
2582 self.line(&format!("discard {code}"));
2583 } else if !code.is_empty() {
2584 self.line(&code);
2585 }
2586 }
2587 }
2588 }
2589
2590 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
2591 let Expr::If(i) = e else { unreachable!() };
2592 if let Expr::Let(_) = &*i.cond {
2593 return Err("`if let` is not implemented yet".into());
2594 }
2595 let c = self.expr(&i.cond)?;
2596 self.line(&format!("if {}:", c.code));
2597 self.nested_block(&i.then_branch)?;
2598 match &i.else_branch {
2599 None => {}
2600 Some((_, els)) => match &**els {
2601 Expr::If(_) => {
2602 // Nim needs `elif`; splice the nested `if` in as one.
2603 let mark = self.out.len();
2604 self.if_stmt(els)?;
2605 let tail = self.out.split_off(mark);
2606 let indent = " ".repeat(self.indent);
2607 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
2608 }
2609 Expr::Block(b) => {
2610 self.line("else:");
2611 self.nested_block(&b.block)?;
2612 }
2613 _ => return Err("unsupported `else` form".into()),
2614 },
2615 }
2616 Ok(())
2617 }
2618
2619 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
2620 if f.label.is_some() {
2621 return Err("loop labels are not implemented yet".into());
2622 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2623 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2624
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2625 // One index loop drives the whole chain. Rust's adaptors are lazy and
2626 // compose; resolving them to an index and binding each name to an
2627 // lvalue reproduces that without materialising anything.
2628 let i = self.fresh("Idx");
2629 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
2630 self.indent += 1;
2631 self.push_scope();
2632 let before = self.out.len();
2633
2634 self.bind_pattern(&f.pat, &it, &i)?;
2635
2636 let saved = self.target.take();
2637 if let Some(v) = self.block_body(&f.body)? {
2638 let code = v.code.clone();
2639 self.line(&format!("discard {code}"));
2640 }
2641 self.target = saved;
2642 if self.out.len() == before {
2643 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2644 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2645 self.pop_scope();
2646 self.indent -= 1;
2647 Ok(())
2648 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2649
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2650 /// Resolve a chain of iterator adaptors into a single `Iter`.
2651 ///
2652 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
2653 /// `filter`, `take_while` and friends are rejected rather than partially
2654 /// honoured: silently dropping an adaptor would change which elements the
2655 /// loop visits.
2656 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
2657 match e {
2658 Expr::Reference(r) => self.resolve_iter(&r.expr),
2659 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2660 Expr::Range(r) => {
2661 let lo = match &r.start {
2662 Some(e) => self.expr(e)?,
2663 None => return Err("a `for` over `..n` needs a start bound".into()),
2664 };
2665 let hi = match &r.end {
2666 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2667 None => {
2668 return Err("a `for` over an unbounded range would not terminate".into())
2669 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2670 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2671 let ty = lo.ty.clone().or(hi.ty.clone());
2672 Ok(Iter::Range {
2673 lo: lo.code,
2674 hi: hi.code,
2675 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
2676 ty,
2677 })
2678 }
2679 Expr::MethodCall(m) => {
2680 let name = m.method.to_string();
2681 match name.as_str() {
2682 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
2683 let mut it = self.resolve_iter(&m.receiver)?;
2684 if name == "iter_mut" {
2685 if let Iter::Elems { mutable, .. } = &mut it {
2686 *mutable = true;
2687 }
2688 }
2689 Ok(it)
2690 }
2691 "enumerate" if m.args.is_empty() => {
2692 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
2693 }
2694 "zip" if m.args.len() == 1 => {
2695 let a = self.resolve_iter(&m.receiver)?;
2696 let b = self.resolve_iter(&m.args[0])?;
2697 Ok(Iter::Zip(Box::new(a), Box::new(b)))
2698 }
2699 "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 17h ago2700 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2701 let k = self.expr(&m.args[0])?;
2702 Ok(Iter::Chunks {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2703 code,
2704 base,
2705 len,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2706 k: k.code,
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2707 elem,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2708 mutable: name.ends_with("_mut"),
2709 })
2710 }
2711 "windows" if m.args.len() == 1 => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2712 let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2713 let k = self.expr(&m.args[0])?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2714 Ok(Iter::Windows { code, base, len, k: k.code, elem })
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2715 }
2716 other => Err(format!(
2717 "iterator adaptor `.{other}()` is not implemented; it has \
2718 no index-loop equivalent here, and dropping it would \
2719 change which elements the loop visits"
2720 )),
2721 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2722 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago2723 Expr::Path(p) => {
2724 let n = path_name(&p.path);
2725 if let Some(Alias::Iterator(it)) = self.lookup_alias(&n) {
2726 return Ok((*it).clone());
2727 }
2728 if let Some(Alias::Window { code, off, len, elem }) = self.lookup_alias(&n) {
2729 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
2730 }
2731 let v = self.expr(e)?;
2732 Ok(Iter::Elems {
2733 len: format!("{}.len", v.code),
2734 elem: elem_of(&v.ty),
2735 code: v.code,
2736 off: "0".into(),
2737 mutable: false,
2738 })
2739 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2740 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2741 // A `for` binding that is itself a window iterates that window,
2742 // not the whole container it points into.
2743 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 17h ago2744 return Ok(Iter::Elems { code, off, len, elem, mutable: false });
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2745 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2746 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2747 Ok(Iter::Elems {
2748 len: format!("{}.len", v.code),
2749 elem: elem_of(&v.ty),
2750 code: v.code,
2751 off: "0".into(),
2752 mutable: false,
2753 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2754 }
2755 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2756 }
2757
2758 /// Bind a `for` pattern against a resolved iterator at index `i`.
2759 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
2760 match (p, it) {
2761 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
2762 self.bind_pattern(&t.elems[0], a, i)?;
2763 self.bind_pattern(&t.elems[1], b, i)
2764 }
2765 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
2766 if let Pat::Ident(id) = &t.elems[0] {
2767 let n = id.ident.to_string();
2768 // Rust's `enumerate` counts in `usize`.
2769 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
2770 self.bind(&n, Nim::Prim("uint".into()));
2771 }
2772 self.bind_pattern(&t.elems[1], inner, i)
2773 }
2774 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
2775 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
2776 ),
2777 (Pat::Wild(_), _) => Ok(()),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2778 // `for &byte in xs` — the `&` destructures the reference, which in
2779 // Nim is already the value.
2780 (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
2781 (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2782 (Pat::Ident(id), _) => {
2783 let name = id.ident.to_string();
2784 match it {
2785 Iter::Range { lo, ty, .. } => {
2786 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
2787 // The loop counts from zero; the range's own start is
2788 // added back so the binding has Rust's value and type.
2789 self.line(&format!(
2790 "let {}: {} = {}({}) + {}",
2791 ident(&name),
2792 t.render(),
2793 t.render(),
2794 i,
2795 lo
2796 ));
2797 self.bind(&name, t);
2798 Ok(())
2799 }
2800 Iter::Elems { code, off, elem, mutable, .. } => {
2801 let access = if off == "0" {
2802 format!("{}[{}]", code, i)
2803 } else {
2804 format!("{}[{} + {}]", code, off, i)
2805 };
2806 if *mutable {
2807 // An alias, not a copy: assigning through the
2808 // binding must reach the original element.
2809 self.bind_alias(
2810 &name,
2811 Alias::Value { code: access, ty: elem.clone() },
2812 );
2813 } else {
2814 let t = elem
2815 .clone()
2816 .ok_or("cannot infer the element type of this `for`")?;
2817 self.line(&format!(
2818 "let {}: {} = {}",
2819 ident(&name),
2820 t.render(),
2821 access
2822 ));
2823 self.bind(&name, t);
2824 }
2825 Ok(())
2826 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2827 Iter::Chunks { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2828 self.bind_alias(
2829 &name,
2830 Alias::Window {
2831 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2832 off: format!("({} + {} * int({}))", base, i, k),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2833 len: format!("int({})", k),
2834 elem: elem.clone(),
2835 },
2836 );
2837 Ok(())
2838 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2839 Iter::Windows { code, base, k, elem, .. } => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2840 self.bind_alias(
2841 &name,
2842 Alias::Window {
2843 code: code.clone(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago2844 off: format!("({} + {})", base, i),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago2845 len: format!("int({})", k),
2846 elem: elem.clone(),
2847 },
2848 );
2849 Ok(())
2850 }
2851 // Handled above: a zip or enumerate needs a tuple pattern,
2852 // and binding one name to the pair is not supported.
2853 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
2854 }
2855 }
2856 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2857 }
2858 }
2859
2860 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
2861 let Expr::Match(m) = e else { unreachable!() };
2862 let scrut = self.expr(&m.expr)?;
2863 let t = scrut
2864 .ty
2865 .clone()
2866 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2867 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2868 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2869
2870 // A `match` whose arms neither bind nor guard is a Nim `case`, which
2871 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
2872 // an if/elif chain, because Nim's `case` cannot destructure.
2873 let plain = m.arms.iter().all(|a| {
2874 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
2875 });
2876 if plain {
2877 self.match_case(m, &name, &t)
2878 } else {
2879 self.match_chain(m, &name, &t)
2880 }
2881 }
2882
2883 fn match_case(
2884 &mut self,
2885 m: &syn::ExprMatch,
2886 name: &str,
2887 t: &Nim,
2888 ) -> Result<(), String> {
2889 // A variant object is discriminated by its `kind` field.
2890 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
2891 self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2892
2893 let mut saw_wild = false;
2894 for arm in &m.arms {
2895 match &arm.pat {
2896 Pat::Wild(_) => {
2897 saw_wild = true;
2898 self.line("else:");
2899 }
2900 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2901 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2902 self.line(&format!("of {}:", labels.join(", ")));
2903 }
2904 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2905 self.arm_body(&arm.body)?;
2906 }
2907 if !saw_wild && !self.case_is_total(t, m) {
2908 // Rust checked exhaustiveness already, but Nim cannot always see
2909 // it -- an integer `case` needs every value covered -- so make the
2910 // unreachable arm explicit rather than leave a compile error.
2911 self.line("else:");
2912 self.line(" rsPanic(\"unreachable match arm\")");
2913 }
2914 Ok(())
2915 }
2916
2917 /// Whether a Nim `case` over this type is already total, in which case
2918 /// adding an `else` would be a compile error rather than a safety net.
2919 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
2920 let Nim::Named(n, _) = t else { return false };
2921 let Some(def) = self.enums.get(n) else { return false };
2922 def.variants.len() == m.arms.len()
2923 }
2924
2925 /// The if/elif form, for arms that bind or destructure.
2926 fn match_chain(
2927 &mut self,
2928 m: &syn::ExprMatch,
2929 name: &str,
2930 t: &Nim,
2931 ) -> Result<(), String> {
2932 let mut first = true;
2933 let mut closed = false;
2934 for arm in &m.arms {
2935 let (pat, guard) = match &arm.pat {
2936 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
2937 p => (p, None),
2938 };
2939 if guard.is_some() && binds(pat) {
2940 return Err("a `match` guard on a binding pattern is not \
2941 implemented yet"
2942 .into());
2943 }
2944 let test = self.pat_test(pat, name, t)?;
2945 let test = match (test, guard) {
2946 (Some(t), Some(g)) => {
2947 let g = self.expr(g)?;
2948 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2949 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2950 (None, Some(g)) => Some(self.expr(g)?.code),
2951 (t, None) => t,
2952 };
2953 match test {
2954 Some(test) => {
2955 self.line(&format!(
2956 "{} {}:",
2957 if first { "if" } else { "elif" },
2958 test
2959 ));
2960 first = false;
2961 }
2962 None => {
2963 // An irrefutable pattern: everything left falls here.
2964 if first {
2965 self.line("block:");
2966 } else {
2967 self.line("else:");
2968 }
2969 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2970 }
2971 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2972 self.indent += 1;
2973 self.push_scope();
2974 let before = self.out.len();
2975 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2976 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2977 self.arm_body_at(&arm.body, before)?;
2978 self.pop_scope();
2979 if closed {
2980 break;
2981 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2982 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2983 if !closed {
2984 // Rust proved this unreachable; Nim cannot see that, and leaving
2985 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago2986 self.line("else:");
2987 self.line(" rsPanic(\"unreachable match arm\")");
2988 }
2989 Ok(())
2990 }
2991
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago2992 /// The condition that selects this arm, or `None` if it always matches.
2993 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
2994 Ok(match p {
2995 Pat::Wild(_) => None,
2996 Pat::Ident(i) if i.subpat.is_none() => None,
2997 Pat::Or(o) => {
2998 let mut parts = Vec::new();
2999 for c in &o.cases {
3000 match self.pat_test(c, name, t)? {
3001 Some(x) => parts.push(x),
3002 None => return Ok(None),
3003 }
3004 }
3005 Some(format!("({})", parts.join(" or ")))
3006 }
3007 Pat::Lit(_) | Pat::Range(_) => {
3008 let labels = self.pat_labels(p, Some(t))?;
3009 Some(match p {
3010 Pat::Range(_) => format!("({} in {})", name, labels[0]),
3011 _ => format!("({} == {})", name, labels[0]),
3012 })
3013 }
3014 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
3015 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
3016 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
3017 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
3018 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
3019 _ => return Err("unsupported `match` pattern".into()),
3020 })
3021 }
3022
3023 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
3024 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
3025 let last = path_name(path);
3026 match last.as_str() {
3027 "Ok" => return Ok(format!("{name}.ok")),
3028 "Err" => return Ok(format!("(not {name}.ok)")),
3029 "Some" => return Ok(format!("{name}.has")),
3030 "None" => return Ok(format!("(not {name}.has)")),
3031 _ => {}
3032 }
3033 let Some((def, v)) = self.resolve_variant(path) else {
3034 return Err(format!(
3035 "`{last}` in a pattern is not a known enum variant; if it names \
3036 an enum declared in another module, that is not implemented yet"
3037 ));
3038 };
3039 if let Nim::Named(n, _) = t {
3040 if *n != def.name {
3041 return Err(format!(
3042 "pattern `{}::{}` does not match the scrutinee type `{}`",
3043 def.name, v, n
3044 ));
3045 }
3046 }
3047 Ok(if def.simple {
3048 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
3049 } else {
3050 format!("({}.kind == {})", name, def.kind_ident(&v))
3051 })
3052 }
3053
3054 /// Emit the `let`s that a pattern's bindings introduce.
3055 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
3056 match p {
3057 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
3058 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
3059 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
3060 Pat::Ident(i) if i.subpat.is_none() => {
3061 let b = i.ident.to_string();
3062 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
3063 self.bind(&b, t.clone());
3064 Ok(())
3065 }
3066 Pat::TupleStruct(ts) => {
3067 let fields = self.variant_fields(&ts.path, t)?;
3068 for (i, sub) in ts.elems.iter().enumerate() {
3069 let Some((fname, fty)) = fields.get(i) else {
3070 return Err(format!(
3071 "pattern binds {} field(s) but the variant has {}",
3072 ts.elems.len(),
3073 fields.len()
3074 ));
3075 };
3076 let access = format!("{}.{}", name, ident(fname));
3077 self.pat_bind(sub, &access, fty)?;
3078 }
3079 Ok(())
3080 }
3081 Pat::Struct(st) => {
3082 let fields = self.variant_fields(&st.path, t)?;
3083 for f in &st.fields {
3084 let syn::Member::Named(m) = &f.member else {
3085 return Err("unsupported struct pattern field".into());
3086 };
3087 let m = m.to_string();
3088 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
3089 return Err(format!("unknown field `{m}` in pattern"));
3090 };
3091 let access = format!("{}.{}", name, ident(fname));
3092 self.pat_bind(&f.pat, &access, fty)?;
3093 }
3094 Ok(())
3095 }
3096 _ => Err("unsupported `match` pattern".into()),
3097 }
3098 }
3099
3100 /// The payload fields a variant pattern destructures.
3101 fn variant_fields(
3102 &self,
3103 path: &syn::Path,
3104 t: &Nim,
3105 ) -> Result<Vec<(String, Nim)>, String> {
3106 let last = path_name(path);
3107 // `Ok`/`Err`/`Some` read the prelude's own field names.
3108 if let Nim::Named(n, a) = t {
3109 match (n.as_str(), last.as_str()) {
3110 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
3111 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
3112 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
3113 _ => {}
3114 }
3115 }
3116 let Some((def, v)) = self.resolve_variant(path) else {
3117 return Err(format!("`{last}` is not a known enum variant"));
3118 };
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago3119 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
3120 // The variant's payload is declared in the enum's own parameters; the
3121 // scrutinee says what they are here.
3122 Ok(fields
3123 .into_iter()
3124 .map(|(n, ft)| (n, self.subst_type_args(&def.name, t, ft)))
3125 .collect())
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3126 }
3127
3128 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
3129 self.indent += 1;
3130 let before = self.out.len();
3131 self.indent -= 1;
3132 self.arm_body_at(body, before)
3133 }
3134
3135 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
3136 match body {
3137 Expr::Block(b) => self.nested_block(&b.block)?,
3138 other => {
3139 self.indent += 1;
3140 // An arm's value is the `match`'s value, so it is typed by
3141 // whatever the `match` is being assigned to -- without which
3142 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
3143 let want = self.target.clone().and_then(|(_, t)| t);
3144 let v = match (want, expressible(other)) {
3145 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
3146 _ => self.expr_stmt(other)?,
3147 };
3148 self.emit_tail(v);
3149 self.indent -= 1;
3150 }
3151 }
3152 if self.out.len() == before {
3153 self.indent += 1;
3154 self.line("discard");
3155 self.indent -= 1;
3156 }
3157 Ok(())
3158 }
3159
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3160 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
3161 match p {
3162 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
3163 Pat::Or(o) => {
3164 let mut out = Vec::new();
3165 for p in &o.cases {
3166 out.extend(self.pat_labels(p, expect)?);
3167 }
3168 Ok(out)
3169 }
3170 Pat::Range(r) => {
3171 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
3172 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
3173 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
3174 let op = match r.limits {
3175 syn::RangeLimits::HalfOpen(_) => "..<",
3176 syn::RangeLimits::Closed(_) => "..",
3177 };
3178 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
3179 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3180 Pat::Path(pp) => {
3181 if let Some((def, v)) = self.resolve_variant(&pp.path) {
3182 return Ok(vec![if def.simple {
3183 format!("{}.{}", ident(&def.name), ident(&v))
3184 } else {
3185 def.kind_ident(&v)
3186 }]);
3187 }
3188 Ok(vec![ident(&path_name(&pp.path))])
3189 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3190 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3191 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3192 .into()),
3193 }
3194 }
3195
3196 // --------------------------------------------------------- expressions
3197
3198 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
3199 self.expr_at(e, None)
3200 }
3201
3202 /// Lower `e`, with the type the surrounding code expects of it.
3203 ///
3204 /// Rust infers an unsuffixed integer literal's type from its context and
3205 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
3206 /// expected type down to the literal is what makes `let x: u8 = 255` and
3207 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
3208 /// widths silently diverge, which is exactly the class of bug this
3209 /// project refuses to ship.
3210 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
3211 match e {
3212 Expr::Lit(l) => self.lit_at(&l.lit, expect),
3213 Expr::Path(p) => {
3214 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3215 if name == "None" {
3216 return Ok(Val::new(self.none_of(expect), expect.cloned()));
3217 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago3218 // `log::Level` and `log::LevelFilter` come from the facade
3219 // shim, under names no crate can collide with.
3220 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3221 if (q == "Level" || q == "LevelFilter") && !self.enums.contains_key(&q) {
3222 let pre = if q == "Level" { "rsLvl" } else { "rsFlt" };
3223 let t = if q == "Level" { "RsLogLevel" } else { "RsLogFilter" };
3224 if q == "LevelFilter" && name == "Off" {
3225 return Ok(Val::new("rsFltOff", Some(Nim::Prim(t.into()))));
3226 }
3227 if matches!(name.as_str(), "Error" | "Warn" | "Info" | "Debug" | "Trace") {
3228 return Ok(Val::new(
3229 format!("{pre}{name}"),
3230 Some(Nim::Prim(t.into())),
3231 ));
3232 }
3233 }
3234 }
3235
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago3236 // `Perms::READ`: a constant of a `bitflags!` type.
3237 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3238 let q = if q == "Self" {
3239 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3240 } else {
3241 q
3242 };
3243 if let Some(c) = self.flag_consts.get(&(q.clone(), name.clone())) {
3244 return Ok(Val::new(c.clone(), Some(Nim::Named(q, vec![]))));
3245 }
3246 }
3247
Add associated types and consts, and test whether the blocker stack bottoms out 8354895 nandithebull 15h ago3248 // `Grid::BORDER`: a `const` declared inside an `impl`.
3249 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3250 let q = if q == "Self" {
3251 self.self_ty.as_ref().map(type_name).unwrap_or(q)
3252 } else {
3253 q
3254 };
3255 if let Some((nim, t)) = self.assoc_consts.get(&(q, name.clone())) {
3256 return Ok(Val::new(nim.clone(), Some(t.clone())));
3257 }
3258 }
3259
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago3260 // `i32::MAX` and friends: an associated const on a primitive.
3261 if matches!(name.as_str(), "MAX" | "MIN") {
3262 if let Some(q) = p.path.segments.iter().rev().nth(1) {
3263 if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) {
3264 if t.is_integer() {
3265 let f = if name == "MAX" { "high" } else { "low" };
3266 return Ok(Val::new(
3267 format!("{}({})", f, t.render()),
3268 Some(t),
3269 ));
3270 }
3271 }
3272 }
3273 }
3274
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago3275 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
3276 // declared here. In Nim that is a constructor call.
3277 if p.path.segments.len() > 1 {
3278 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
3279 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
3280 if n == "FmtError" {
3281 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
3282 }
3283 }
3284 }
3285 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
3286 return Ok(Val::new(
3287 format!("{}()", ident(&name)),
3288 Some(Nim::Named(name.clone(), vec![])),
3289 ));
3290 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3291 // A unit enum variant used as a value: `Error::InvalidLength`.
3292 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago3293 let (ty, targs) = self.variant_type(&def, expect)?;
3294 return Ok(if def.simple && targs.is_empty() {
3295 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), Some(ty))
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3296 } else {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago3297 // A unit variant of a generic enum has no argument to
3298 // infer the parameters from, so they are written out.
3299 Val::new(
3300 format!("{}{}()", def.ctor_ident(&v), targs),
3301 Some(ty),
3302 )
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3303 });
3304 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago3305 // A `for` binding that stands for an element of the container
3306 // it came from: using it must read (and assigning through it
3307 // must write) that element, not a copy.
3308 if let Some(a) = self.lookup_alias(&name) {
3309 return Ok(match a {
3310 Alias::Value { code, ty } => Val::new(code, ty),
3311 // A window *is* a slice; as a value it is the view it
3312 // denotes, which is what Rust's `&[T]` means too.
3313 Alias::Window { code, off, len, elem } => Val::new(
3314 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
3315 elem.map(|e| Nim::OpenArray(Box::new(e))),
3316 ),
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago3317 // An iterator is not a value here: it is consumed by a
3318 // `for`, or asked for its `.remainder()`.
3319 Alias::Iterator(_) => {
3320 return Err(format!(
3321 "`{name}` is an iterator; it can be iterated or asked \
3322 for its `remainder()`, but not used as a value"
3323 ))
3324 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago3325 });
3326 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3327 if let Some(t) = self.lookup(&name) {
3328 return Ok(Val::new(ident(&name), Some(t)));
3329 }
3330 // A top-level function used as a value, e.g. passed to a
3331 // parameter of `impl Fn(..)` type.
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago3332 if let Some(k) = self.resolve_fn(&p.path) {
3333 let sig = &self.fns[&k];
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3334 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 17h ago3335 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 18h ago3336 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3337 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3338 }
3339 Expr::Paren(p) => {
3340 let v = self.expr_at(&p.expr, expect)?;
3341 Ok(Val::new(format!("({})", v.code), v.ty))
3342 }
3343 Expr::Group(g) => self.expr_at(&g.expr, expect),
3344 // `&x` is a value in Nim; `&mut x` in an argument position binds to
3345 // a `var` parameter, which is also just `x` at the call site.
3346 Expr::Reference(r) => self.expr_at(&r.expr, expect),
3347 Expr::Unary(u) => self.unary(u, expect),
3348 Expr::Binary(b) => self.binary(b, expect),
3349 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago3350 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
3351 let Expr::Range(r) = &*i.index else { unreachable!() };
3352 let base = self.expr(&i.expr)?;
3353 let lo = match &r.start {
3354 Some(e) => format!("int({})", self.expr(e)?.code),
3355 None => "0".into(),
3356 };
3357 // Nim's `toOpenArray` takes an inclusive upper bound.
3358 let hi = match (&r.end, r.limits) {
3359 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
3360 format!("int({}) - 1", self.expr(e)?.code)
3361 }
3362 (Some(e), syn::RangeLimits::Closed(_)) => {
3363 format!("int({})", self.expr(e)?.code)
3364 }
3365 (None, _) => format!("{}.len - 1", base.code),
3366 };
3367 let elem = elem_of(&base.ty)
3368 .ok_or("cannot infer the element type of this slice")?;
3369 Ok(Val::new(
3370 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
3371 Some(Nim::OpenArray(Box::new(elem))),
3372 ))
3373 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3374 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago3375 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
3376 let idx = self.expr(&i.index)?;
3377 return Ok(Val::new(
3378 format!("{}[{} + int({})]", code, off, idx.code),
3379 elem,
3380 ));
3381 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3382 let base = self.expr(&i.expr)?;
3383 let idx = self.expr(&i.index)?;
3384 // Rust indexes with usize; Nim wants an `int`, and a `uint`
3385 // index is a type error there rather than a silent conversion.
3386 let idx_code = match &idx.ty {
3387 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
3388 _ => idx.code.clone(),
3389 };
3390 let elem = match base.ty.clone() {
3391 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
3392 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
3393 _ => None,
3394 };
3395 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
3396 }
3397 Expr::Field(f) => {
3398 let base = self.expr(&f.base)?;
3399 let name = match &f.member {
3400 syn::Member::Named(n) => n.to_string(),
3401 syn::Member::Unnamed(i) => format!("f{}", i.index),
3402 };
3403 let t = match &base.ty {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago3404 Some(bt @ Nim::Named(s, _)) => self
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3405 .structs
3406 .get(s)
3407 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago3408 .map(|(_, t)| self.subst_type_args(s, bt, t.clone())),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3409 _ => None,
3410 };
3411 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
3412 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago3413 // `unsafe` is a permission marker, not a semantic change: it does
3414 // not alter what the enclosed operations mean. So the block is
3415 // transparent here, and each operation inside still goes through
3416 // the ordinary lowering -- and is still rejected if it has no
3417 // faithful mapping.
3418 Expr::Unsafe(u) => match single_expr(&u.block) {
3419 Some(e) => self.expr_at(e, expect),
3420 None => Err("an `unsafe` block used as a value must be a single \
3421 expression"
3422 .into()),
3423 },
3424 Expr::Closure(c) => self.closure(c, expect),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3425 Expr::Try(t) => self.try_op(t),
3426 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago3427 Expr::MethodCall(m) => self.method(m, expect),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago3428 Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
3429 // `vec![..]`'s elements take their type from the annotation on
3430 // the binding, exactly as Rust's would.
3431 let want = match expect {
3432 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
3433 _ => None,
3434 };
3435 let saved = std::mem::replace(&mut self.vec_expect, want.clone());
3436 let code = self.macro_call(&m.mac);
3437 self.vec_expect = saved;
3438 let code = code?;
3439 let ty = match want {
3440 Some(e) => Some(Nim::Seq(Box::new(e))),
3441 None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
3442 };
3443 Ok(Val::new(code, ty))
3444 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3445 Expr::Macro(m) => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago3446 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 18h ago3447 let code = self.macro_call(&m.mac)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago3448 // A formatter write is a statement that appends, not a value.
3449 let ty = if is_write { Some(Nim::Unit) } else { None };
3450 Ok(Val::new(code, ty))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3451 }
3452 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3453 if s.rest.is_some() {
3454 return Err("struct update syntax `..rest` is not implemented yet".into());
3455 }
3456 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
3457 // which is constructed positionally in Nim.
3458 if let Some((def, v)) = self.resolve_variant(&s.path) {
3459 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
3460 let mut args = vec![String::new(); fields.len()];
3461 for f in &s.fields {
3462 let syn::Member::Named(m) = &f.member else {
3463 return Err("unsupported enum variant field".into());
3464 };
3465 let want = format!("{}_{}", v, m);
3466 let i = fields
3467 .iter()
3468 .position(|(n, _)| *n == want)
3469 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
3470 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
3471 }
3472 if let Some(i) = args.iter().position(|a| a.is_empty()) {
3473 return Err(format!(
3474 "`{}::{}` is missing field `{}`",
3475 def.name, v, fields[i].0
3476 ));
3477 }
3478 return Ok(Val::new(
3479 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
3480 Some(Nim::Named(def.name.clone(), vec![])),
3481 ));
3482 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago3483 // `Self { .. }` inside an `impl` names the type being
3484 // implemented, and its fields are that type's fields.
3485 let name = match path_name(&s.path).as_str() {
3486 "Self" => self
3487 .self_ty
3488 .as_ref()
3489 .map(type_name)
3490 .ok_or("`Self` outside an `impl` block")?,
3491 other => other.to_string(),
3492 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3493 let mut parts = Vec::new();
3494 for f in &s.fields {
3495 let fname = match &f.member {
3496 syn::Member::Named(n) => n.to_string(),
3497 syn::Member::Unnamed(i) => format!("f{}", i.index),
3498 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3499 let want = self
3500 .structs
3501 .get(&name)
3502 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
3503 .map(|(_, t)| t.clone());
3504 let v = self.expr_at(&f.expr, want.as_ref())?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3505 parts.push(format!("{}: {}", ident(&fname), v.code));
3506 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago3507 // Nim cannot infer an object's generic parameters from a
3508 // constructor's field values, so they are written out.
3509 let gp = self.type_generics.get(&name).cloned().unwrap_or_default();
3510 let ty = if gp.is_empty() {
3511 Nim::Named(name.clone(), vec![])
3512 } else {
3513 match expect {
3514 Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => {
3515 Nim::Named(name.clone(), a.clone())
3516 }
3517 _ => {
3518 return Err(format!(
3519 "`{name} {{ .. }}` is generic, and Nim cannot infer \
3520 its parameters from the field values; annotate the \
3521 binding or the return type"
3522 ))
3523 }
3524 }
3525 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3526 Ok(Val::new(
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago3527 format!("{}({})", ty.render(), parts.join(", ")),
3528 Some(ty),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3529 ))
3530 }
3531 Expr::Array(a) => {
3532 let mut parts = Vec::new();
3533 let mut elem = match expect {
3534 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
3535 Some((**t).clone())
3536 }
3537 _ => None,
3538 };
3539 for e in &a.elems {
3540 let want = elem.clone();
3541 let v = self.expr_at(e, want.as_ref())?;
3542 elem = elem.or(v.ty.clone());
3543 parts.push(v.code);
3544 }
3545 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
3546 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
3547 }
3548 Expr::Repeat(r) => {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago3549 // `[0; 4]` is an array in Rust. Nim distinguishes a fixed-size
3550 // array from a `seq`, so the expected type decides which, and
3551 // an array needs its elements written out.
3552 let want_elem = match expect {
3553 Some(Nim::Array(_, e)) | Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => {
3554 Some((**e).clone())
3555 }
3556 _ => None,
3557 };
3558 let v = self.expr_at(&r.expr, want_elem.as_ref())?;
3559 if let Some(Nim::Array(n, _)) = expect {
3560 let elems: Vec<String> = (0..*n).map(|_| v.code.clone()).collect();
3561 let t = v.ty.clone().map(|t| Nim::Array(*n, Box::new(t)));
3562 return Ok(Val::new(format!("[{}]", elems.join(", ")), t));
3563 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3564 let n = self.expr(&r.len)?;
3565 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
3566 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
3567 }
3568 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
3569 Expr::Tuple(t) => {
3570 let mut parts = Vec::new();
3571 let mut tys = Vec::new();
3572 for e in &t.elems {
3573 let v = self.expr(e)?;
3574 tys.push(v.ty.clone());
3575 parts.push(v.code);
3576 }
3577 let ty = tys
3578 .iter()
3579 .cloned()
3580 .collect::<Option<Vec<_>>>()
3581 .map(Nim::Tuple);
3582 Ok(Val::new(format!("({})", parts.join(", ")), ty))
3583 }
3584 // `if` and `match` are expressions in both languages, but only
3585 // when every arm is itself a single expression.
3586 Expr::If(i) => self.if_expr(i, expect),
3587 Expr::Block(b) if b.block.stmts.len() == 1 => {
3588 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
3589 self.expr_at(e, expect)
3590 } else {
3591 Err("block expression with statements in value position is not implemented yet".into())
3592 }
3593 }
3594 other => Err(format!(
3595 "unsupported expression in value position: {}",
3596 expr_kind(other)
3597 )),
3598 }
3599 }
3600
3601 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
3602 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
3603 return Err(
3604 "an `if` used as a value must have an `else` and single-expression arms".into(),
3605 );
3606 };
3607 let c = self.expr(&i.cond)?;
3608 let t = self.expr_at(then, expect)?;
3609 let want = expect.cloned().or_else(|| t.ty.clone());
3610 let e = match &**els {
3611 Expr::Block(b) => match single_expr(&b.block) {
3612 Some(x) => self.expr_at(x, want.as_ref())?,
3613 None => return Err("an `if` used as a value must have single-expression arms".into()),
3614 },
3615 other => self.expr_at(other, want.as_ref())?,
3616 };
3617 let ty = t.ty.clone().or(e.ty.clone());
3618 Ok(Val::new(
3619 format!("(if {}: {} else: {})", c.code, t.code, e.code),
3620 ty,
3621 ))
3622 }
3623
3624 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
3625 match l {
3626 Lit::Int(i) => {
3627 let suffix = i.suffix();
3628 if let Some(why) = ty::rejected(suffix) {
3629 return Err(format!("integer literal `{}`: {}", i, why));
3630 }
3631 let digits = i.base10_digits().to_string();
3632 // Rust's default for an unconstrained integer literal is i32.
3633 // Nim's is `int` (64-bit). Making the width explicit is what
3634 // keeps overflow behaviour the same on both sides.
3635 let t = if suffix.is_empty() {
3636 match expect {
3637 Some(t) if t.is_integer() => t.clone(),
3638 // Rust's fallback for an otherwise-unconstrained
3639 // integer literal.
3640 _ => Nim::Prim("int32".into()),
3641 }
3642 } else {
3643 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
3644 };
3645 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
3646 }
3647 Lit::Float(f) => {
3648 let t = match f.suffix() {
3649 "" => match expect {
3650 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
3651 _ => Nim::Prim("float64".into()),
3652 },
3653 "f64" => Nim::Prim("float64".into()),
3654 "f32" => Nim::Prim("float32".into()),
3655 s => return Err(format!("unknown float suffix `{s}`")),
3656 };
3657 let d = f.base10_digits();
3658 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
3659 Ok(Val::new(d, Some(t)))
3660 }
3661 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
3662 Lit::Str(s) => Ok(Val::new(
3663 fmt::nim_str(&s.value()),
3664 Some(Nim::Prim("string".into())),
3665 )),
3666 Lit::Char(c) => Ok(Val::new(
3667 format!("Rune({})", c.value() as u32),
3668 Some(Nim::Prim("Rune".into())),
3669 )),
3670 Lit::Byte(b) => Ok(Val::new(
3671 format!("{}'u8", b.value()),
3672 Some(Nim::Prim("uint8".into())),
3673 )),
3674 Lit::ByteStr(b) => {
3675 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
3676 Ok(Val::new(
3677 format!("@[{}]", bytes.join(", ")),
3678 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
3679 ))
3680 }
3681 other => Err(format!("unsupported literal: {other:?}")),
3682 }
3683 }
3684
3685 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
3686 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
3687 // the positive half of the range before the negation runs. Folding the
3688 // sign into the literal keeps `i8::MIN` and friends expressible.
3689 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
3690 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
3691 let v = self.lit_at(&l.lit, expect)?;
3692 return Ok(Val::new(format!("-{}", v.code), v.ty));
3693 }
3694 }
3695 let v = self.expr_at(&u.expr, expect)?;
3696 match u.op {
3697 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
3698 // Rust's `!` is logical on bool and bitwise-complement on integers.
3699 // Nim spells those `not` and `not` as well, so one mapping covers
3700 // both — but only because Nim overloads `not` the same way.
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago3701 UnOp::Not(_) => {
3702 if let Some(f) = self.op_proc(&v.ty, "not") {
3703 return Ok(Val::new(format!("{}({})", f, v.code), v.ty));
3704 }
3705 Ok(Val::new(format!("(not {})", v.code), v.ty))
3706 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3707 UnOp::Deref(_) => Ok(v),
3708 _ => Err("unsupported unary operator".into()),
3709 }
3710 }
3711
3712 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
3713 // A comparison's operands are unrelated to the `bool` it produces, so
3714 // the outer expectation is not passed through to them.
3715 let down = match b.op {
3716 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3717 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
3718 _ => expect,
3719 };
3720 let mut l = self.expr_at(&b.left, down)?;
3721 // Rust unifies the two operand types; propagating whichever side is
3722 // known to the other reproduces that, and disagreement then surfaces
3723 // as a Nim type error rather than as a silent width change.
3724 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
3725 if l.ty.is_none() && r.ty.is_some() {
3726 l = self.expr_at(&b.left, r.ty.as_ref())?;
3727 }
3728 let r = std::mem::replace(&mut r, Val::untyped(""));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago3729 // A binary operator on a user type goes to that type's own impl.
3730 if let Some(f) = self.op_proc(&l.ty, binary_symbol(&b.op)) {
3731 let want = self.op_param(&l.ty, binary_symbol(&b.op));
3732 let r = self.expr_at(&b.right, want.as_ref())?;
3733 let ret = self
3734 .methods
3735 .get(&(
3736 type_name(l.ty.as_ref().unwrap()),
3737 op_method(binary_symbol(&b.op)).to_string(),
3738 ))
3739 .map(|s| s.ret.clone());
3740 return Ok(Val::new(format!("{}({}, {})", f, l.code, r.code), ret));
3741 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3742 let op = self.bin_op(&b.op, &l, &r)?;
3743 let ty = match b.op {
3744 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
3745 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
3746 // Rust's shift takes its result type from the *left* operand, and
3747 // the right may be a different width entirely.
3748 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
3749 _ => l.ty.clone().or(r.ty.clone()),
3750 };
3751 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
3752 }
3753
3754 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
3755 Ok(match op {
3756 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
3757 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
3758 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
3759 BinOp::Div(_) | BinOp::DivAssign(_) => {
3760 // Nim spells integer division `div`. Both languages truncate
3761 // toward zero, so once the right operator is chosen the
3762 // semantics match, including for negative operands.
3763 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3764 "cannot tell integer from float division here; annotate the operands",
3765 )?;
3766 if t.is_integer() { "div" } else { "/" }
3767 }
3768 BinOp::Rem(_) | BinOp::RemAssign(_) => {
3769 let t = l.ty.clone().or(r.ty.clone()).ok_or(
3770 "cannot tell integer from float remainder here; annotate the operands",
3771 )?;
3772 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
3773 }
3774 BinOp::And(_) => "and",
3775 BinOp::Or(_) => "or",
3776 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
3777 // bools, exactly as Rust's `&`/`|`/`^` are.
3778 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
3779 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
3780 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
3781 // Settled empirically: Nim's `shr` on a signed integer is
3782 // arithmetic, matching Rust. See DESIGN.md.
3783 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
3784 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
3785 BinOp::Eq(_) => "==",
3786 BinOp::Ne(_) => "!=",
3787 BinOp::Lt(_) => "<",
3788 BinOp::Le(_) => "<=",
3789 BinOp::Gt(_) => ">",
3790 BinOp::Ge(_) => ">=",
3791 other => return Err(format!("unsupported binary operator {other:?}")),
3792 })
3793 }
3794
3795 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
3796 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3797 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3798 let from = v.ty.clone().ok_or_else(|| {
3799 format!(
3800 "cannot lower `as {}`: the source type is unknown, and `as` \
3801 truncates, so the source width decides the result",
3802 to.render()
3803 )
3804 })?;
3805
3806 let code = match (&from, &to) {
3807 (f, t) if f.is_integer() && t.is_integer() => {
3808 // Rust's `as` between integers is a pure bit-width truncation
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 16h ago3809 // or sign-extension, never a range check. `cast` says exactly
3810 // that. (Nim's `T(x)` turns out to truncate here as well --
3811 // see DESIGN.md item 5 -- but `cast` is the spelling that
3812 // means it rather than the one that happens to agree.)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3813 format!("cast[{}]({})", t.render(), v.code)
3814 }
3815 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
3816 format!("{}({})", p, v.code)
3817 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago3818 // The facade's level enums carry their Rust discriminants, so
3819 // `Level::Info as usize` is the ordinal.
3820 (Nim::Prim(p), t)
3821 if t.is_integer() && (p == "RsLogLevel" || p == "RsLogFilter") =>
3822 {
3823 format!("{}(ord({}))", t.render(), v.code)
3824 }
3825 // A C-like enum's `as` yields its discriminant, which is its
3826 // ordinal in Nim.
3827 (Nim::Named(n, _), t) if t.is_integer() && self.enums.get(n).is_some_and(|d| d.simple) => {
3828 format!("{}(ord({}))", t.render(), v.code)
3829 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3830 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
3831 format!("{}(ord({}))", t.render(), v.code)
3832 }
3833 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
3834 format!("cast[{}](int32({}))", t.render(), v.code)
3835 }
3836 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
3837 format!("Rune(int32({}))", v.code)
3838 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago3839 // Pointer-to-pointer, and integer-to-pointer, are reinterpretations
3840 // in both languages.
3841 (Nim::Ptr(_) | Nim::ConstPtr(_), Nim::Ptr(_) | Nim::ConstPtr(_)) => {
3842 format!("cast[{}]({})", to.render(), v.code)
3843 }
3844 (f, Nim::Ptr(_) | Nim::ConstPtr(_)) if f.is_integer() => {
3845 format!("cast[{}]({})", to.render(), v.code)
3846 }
3847 (Nim::Ptr(_) | Nim::ConstPtr(_), t) if t.is_integer() => {
3848 format!("cast[{}]({})", t.render(), v.code)
3849 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago3850 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
3851 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
3852 // Rust saturates float->int casts; Nim rounds and range-errors.
3853 // Not the same operation, so it is refused rather than mapped.
3854 return Err(format!(
3855 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
3856 no faithful mapping is implemented",
3857 t.render()
3858 ));
3859 }
3860 (f, t) => {
3861 return Err(format!(
3862 "unsupported cast from `{}` to `{}`",
3863 f.render(),
3864 t.render()
3865 ))
3866 }
3867 };
3868 Ok(Val::new(code, Some(to)))
3869 }
3870
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago3871 /// Rust's `?`: return early on the error branch, otherwise yield the value.
3872 ///
3873 /// The early return is statements, not an expression, so they are emitted
3874 /// ahead of the line being built. Every caller lowers its sub-expressions
3875 /// 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 17h ago3876 /// The container, start offset, length and element type an expression
3877 /// denotes as a slice. A window alias contributes its own offset, so
3878 /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
3879 /// into the original buffer rather than through a rebuilt view.
3880 fn slice_parts(
3881 &mut self,
3882 e: &Expr,
3883 ) -> Result<(String, String, String, Option<Nim>), String> {
3884 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
3885 return Ok((code, off, len, elem));
3886 }
3887 let v = self.expr(e)?;
3888 let len = format!("{}.len", v.code);
3889 Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
3890 }
3891
3892 /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
3893 fn map_closure(
3894 &mut self,
3895 what: &str,
3896 recv: &Val,
3897 kind: &str,
3898 targs: &[Nim],
3899 c: &syn::ExprClosure,
3900 ) -> Result<Val, String> {
3901 if c.capture.is_some() {
3902 return Err("a `move` closure captures by value; Nim's closures \
3903 capture by reference, and the two are not the same"
3904 .into());
3905 }
3906 if c.inputs.len() != 1 {
3907 return Err(format!("`.{what}()` takes a one-argument closure"));
3908 }
3909 let pname = match &c.inputs[0] {
3910 Pat::Ident(i) => i.ident.to_string(),
3911 Pat::Wild(_) => "unused0".into(),
3912 _ => return Err("only plain identifier closure parameters are supported".into()),
3913 };
3914
3915 let is_opt = kind == "Option";
3916 let tmp = self.fresh("Map");
3917 let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
3918 self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
3919
3920 let body = match &*c.body {
3921 Expr::Block(b) => single_expr(&b.block)
3922 .ok_or("a closure body with statements is not implemented yet")?,
3923 other => other,
3924 };
3925 self.push_scope();
3926 // The parameter names the payload itself, so a view stays a view.
3927 self.bind_alias(
3928 &pname,
3929 Alias::Value {
3930 code: format!("{}.val", tmp),
3931 ty: Some(targs[0].clone()),
3932 },
3933 );
3934 let v = self.expr(body)?;
3935 self.pop_scope();
3936
3937 let inner = v
3938 .ty
3939 .clone()
3940 .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
3941 // `and_then`'s closure already returns the wrapped type; `map`'s does
3942 // not and has to be re-wrapped.
3943 let (test, some_branch, none_branch, out_ty) = if is_opt {
3944 let out = if what == "map" {
3945 Nim::Named("Option".into(), vec![inner.clone()])
3946 } else {
3947 inner.clone()
3948 };
3949 let body_code = if what == "map" {
3950 format!("rsSome[{}]({})", inner.render(), v.code)
3951 } else {
3952 v.code.clone()
3953 };
3954 (
3955 format!("{}.has", tmp),
3956 body_code,
3957 format!("rsNone[{}]()", elem_arg(&out).render()),
3958 out,
3959 )
3960 } else {
3961 let e = targs[1].clone();
3962 let out = if what == "map" {
3963 Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
3964 } else {
3965 inner.clone()
3966 };
3967 let ok_ty = elem_arg(&out);
3968 let body_code = if what == "map" {
3969 format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
3970 } else {
3971 v.code.clone()
3972 };
3973 (
3974 format!("{}.ok", tmp),
3975 body_code,
3976 format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
3977 out,
3978 )
3979 };
3980 Ok(Val::new(
3981 format!("(if {}: {} else: {})", test, some_branch, none_branch),
3982 Some(out_ty),
3983 ))
3984 }
3985
3986 /// `|x| x + 1` -> a Nim anonymous proc.
3987 ///
3988 /// Nim's closures capture by reference, as Rust's non-`move` closures do.
3989 /// A `move` closure captures by value, which is a different thing, so it
3990 /// is rejected rather than lowered to the same construct.
3991 fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
3992 if c.capture.is_some() {
3993 return Err("a `move` closure captures by value; Nim's closures \
3994 capture by reference, and the two are not the same"
3995 .into());
3996 }
3997 let want: Option<&Vec<Nim>> = match expect {
3998 Some(Nim::Proc(a, _)) => Some(a),
3999 _ => None,
4000 };
4001
4002 self.push_scope();
4003 let mut parts = Vec::new();
4004 let mut ptys = Vec::new();
4005 for (i, p) in c.inputs.iter().enumerate() {
4006 let (name, ann) = match p {
4007 Pat::Ident(id) => (id.ident.to_string(), None),
4008 Pat::Type(t) => match &*t.pat {
4009 Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
4010 _ => return Err("only plain identifier closure parameters are supported".into()),
4011 },
4012 Pat::Wild(_) => (format!("unused{i}"), None),
4013 _ => return Err("only plain identifier closure parameters are supported".into()),
4014 };
4015 let t = ann
4016 .or_else(|| want.and_then(|w| w.get(i).cloned()))
4017 .ok_or_else(|| {
4018 format!(
4019 "cannot infer the type of closure parameter `{name}`; \
4020 annotate it"
4021 )
4022 })?;
4023 parts.push(format!("{}: {}", ident(&name), t.render()));
4024 self.bind(&name, t.clone());
4025 ptys.push(t);
4026 }
4027
4028 let ret_ann = match &c.output {
4029 ReturnType::Default => None,
4030 ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
4031 };
4032 let body = match &*c.body {
4033 Expr::Block(b) => single_expr(&b.block)
4034 .ok_or("a closure body with statements is not implemented yet")?,
4035 other => other,
4036 };
4037 let v = self.expr_at(body, ret_ann.as_ref())?;
4038 self.pop_scope();
4039
4040 let ret = ret_ann
4041 .or_else(|| v.ty.clone())
4042 .ok_or("cannot infer a closure's return type; annotate it")?;
4043 Ok(Val::new(
4044 format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
4045 Some(Nim::Proc(ptys, Box::new(ret))),
4046 ))
4047 }
4048
4049 /// Lower a block's statements at the current indentation, without opening
4050 /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
4051 /// of its own in the generated code.
4052 fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
4053 self.push_scope();
4054 let tail = self.block_body(b)?;
4055 self.emit_tail(tail);
4056 self.pop_scope();
4057 Ok(())
4058 }
4059
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4060 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
4061 if self.in_loop_cond {
4062 return Err("`?` in a loop condition is not implemented yet: the \
4063 early-return it expands to would be evaluated once, \
4064 before the loop, rather than on each iteration"
4065 .into());
4066 }
4067 let v = self.expr(&t.expr)?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4068 if self.fmt_param.is_some() {
4069 // Writing into a string cannot fail, so `?` on a formatter write
4070 // is a no-op. `?` on anything else can fail, and `format!` panics
4071 // when a formatting impl returns an error -- so that is what the
4072 // error branch does here, with std's own message.
4073 if v.ty.as_ref() == Some(&Nim::Unit) {
4074 return Ok(v);
4075 }
4076 if let Some(Nim::Named(n, a)) = v.ty.clone() {
4077 if n == "Result" && a.len() == 2 {
4078 let tmp = self.fresh("Fmt");
4079 self.line(&format!(
4080 "let {}: {} = {}",
4081 tmp,
4082 Nim::Named(n, a.clone()).render(),
4083 v.code
4084 ));
4085 self.line(&format!("if not {}.ok:", tmp));
4086 self.line(
4087 " rsPanic(\"a formatting trait implementation returned an error\")",
4088 );
4089 return Ok(Val::new(format!("{}.val", tmp), Some(a[0].clone())));
4090 }
4091 }
4092 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4093 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
4094 // An `Option`/`Result` of a view: the check is emitted here and the
4095 // view itself survives as an alias, since it has no value form.
4096 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
4097 let err = v.guard_err.clone().ok_or(
4098 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
4099 )?;
4100 let Nim::Named(n, ra) = &ret else {
4101 return Err(format!("`?` in a function returning `{}`", ret.render()));
4102 };
4103 if n != "Result" || ra.len() != 2 {
4104 return Err(format!("`?` in a function returning `{}`", ret.render()));
4105 }
4106 self.line(&format!("if not {}:", guard));
4107 self.line(&format!(
4108 " return rsErr[{}, {}]({})",
4109 ra[0].render(),
4110 ra[1].render(),
4111 err
4112 ));
4113 let mut out = Val::new(String::new(), None);
4114 out.window = Some(w);
4115 return Ok(out);
4116 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4117 let vt = v.ty.clone().ok_or(
4118 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
4119 )?;
4120 let ret = self
4121 .ret
4122 .clone()
4123 .ok_or("`?` outside a function with a return type")?;
4124 let tmp = self.fresh("Try");
4125 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
4126
4127 match (&vt, &ret) {
4128 (Nim::Named(a, ai), Nim::Named(b, bi))
4129 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
4130 {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4131 // Rust inserts a `From::from` on the error here. Where the
4132 // types differ we call the crate's own `impl From`; we never
4133 // assume the conversion is the identity.
4134 let err = if ai[1] == bi[1] {
4135 format!("{}.err", tmp)
4136 } else {
4137 let key = (type_name(&ai[1]), type_name(&bi[1]));
4138 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4139 format!(
4140 "`?` needs `From<{}> for {}` to convert the error, and \
4141 no such `impl` is in scope; assuming the conversion is \
4142 the identity would be a guess",
4143 key.0, key.1
4144 )
4145 })?;
4146 format!("{}({}.err)", f, tmp)
4147 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4148 self.line(&format!("if not {}.ok:", tmp));
4149 self.line(&format!(
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4150 " return rsErr[{}, {}]({})",
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4151 bi[0].render(),
4152 bi[1].render(),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4153 err
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4154 ));
4155 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
4156 }
4157 (Nim::Named(a, ai), Nim::Named(b, bi))
4158 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
4159 {
4160 self.line(&format!("if not {}.has:", tmp));
4161 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
4162 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
4163 }
4164 _ => Err(format!(
4165 "`?` on `{}` in a function returning `{}` is not a supported \
4166 combination",
4167 vt.render(),
4168 ret.render()
4169 )),
4170 }
4171 }
4172
4173 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 18h ago4174 let Expr::Path(p) = &*c.func else {
4175 return Err("only calls to named functions are supported".into());
4176 };
4177 let name = path_name(&p.path);
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4178 let target = self.resolve_fn(&p.path);
4179 let ptys: Vec<Nim> = target
4180 .as_ref()
4181 .and_then(|k| self.fns.get(k))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4182 .map(|s| s.params.clone())
4183 .unwrap_or_default();
4184 let mut args = Vec::new();
4185 for (i, a) in c.args.iter().enumerate() {
4186 let want = ptys.get(i).cloned();
4187 args.push(self.expr_at(a, want.as_ref())?);
4188 }
4189 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
4190
4191 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4192 // `Ok`/`Err` must name the *whole* Result type, not just the half
4193 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
4194 match name.as_str() {
4195 "Some" => {
4196 let inner = match expect {
4197 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
4198 _ => {
4199 return Err("`Some(..)` needs a known `Option<T>` type here; \
4200 annotate the binding or the return type"
4201 .into())
4202 }
4203 };
4204 return Ok(Val::new(
4205 format!("rsSome[{}]({})", inner, codes.join(", ")),
4206 expect.cloned(),
4207 ));
4208 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4209 "Ok" if self.fmt_param.is_some()
4210 && matches!(c.args.first(), Some(Expr::Tuple(t)) if t.elems.is_empty()) =>
4211 {
4212 // `Ok(())` ends a `fmt` body: nothing more is written.
4213 return Ok(Val::new(String::new(), Some(Nim::Unit)));
4214 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4215 "Ok" | "Err" => {
4216 let (t, e) = match expect {
4217 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4218 (a[0].render(), a[1].render())
4219 }
4220 _ => {
4221 return Err(format!(
4222 "`{name}(..)` needs a known `Result<T, E>` type here; \
4223 annotate the binding or the return type"
4224 ))
4225 }
4226 };
4227 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
4228 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
4229 return Ok(Val::new(
4230 format!("{}[{}, {}]({})", ctor, t, e, arg),
4231 expect.cloned(),
4232 ));
4233 }
4234 _ => {}
4235 }
4236
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4237 // A tuple struct applied to arguments: `HexDisplay(bytes)`. Nim's
4238 // object constructor names its fields even when Rust's does not.
4239 if let Some(fields) = self.structs.get(&name).cloned() {
4240 if fields.len() == c.args.len() {
4241 let mut parts = Vec::new();
4242 for (i, a) in c.args.iter().enumerate() {
4243 let v = self.expr_at(a, Some(&fields[i].1))?;
4244 parts.push(format!("{}: {}", ident(&fields[i].0), v.code));
4245 }
4246 return Ok(Val::new(
4247 format!("{}({})", ident(&name), parts.join(", ")),
4248 Some(Nim::Named(name.clone(), vec![])),
4249 ));
4250 }
4251 }
4252
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago4253 // `log::set_max_level` / `log::max_level`.
4254 if name == "set_max_level" && codes.len() == 1 {
4255 return Ok(Val::new(
4256 format!("rsLogMaxLevel = int({})", codes[0]),
4257 Some(Nim::Unit),
4258 ));
4259 }
4260 if name == "max_level" && codes.is_empty() {
4261 return Ok(Val::new(
4262 "RsLogFilter(rsLogMaxLevel)",
4263 Some(Nim::Prim("RsLogFilter".into())),
4264 ));
4265 }
4266
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago4267 // `Spacing::from(d)`: a `From` impl called through its target type.
4268 // Rust picks the impl by the argument's type, and so do we -- Nim
4269 // cannot overload on return type, so each impl has its own proc name.
4270 if name == "from" && codes.len() == 1 {
4271 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
4272 let q = if q == "Self" {
4273 self.self_ty.as_ref().map(type_name).unwrap_or(q)
4274 } else {
4275 q
4276 };
4277 if let Some(src) = args[0].ty.as_ref().map(type_name) {
4278 if let Some(f) = self.from_impls.get(&(src, q.clone())).cloned() {
4279 return Ok(Val::new(
4280 format!("{}({})", f, codes[0]),
4281 Some(Nim::Named(q, vec![])),
4282 ));
4283 }
4284 }
4285 }
4286 }
4287
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago4288 // `u32::from(b)`: `From` between primitives is lossless by definition
4289 // -- it is the widening direction only -- so a plain Nim conversion is
4290 // exact. (The truncating direction is `as`, which is `cast`.)
4291 if name == "from" && codes.len() == 1 {
4292 if let Some(q) = p.path.segments.iter().rev().nth(1) {
4293 if let Some(Nim::Prim(t)) = ty::prim(&q.ident.to_string()) {
4294 return Ok(Val::new(
4295 format!("{}({})", t, codes[0]),
4296 Some(Nim::Prim(t)),
4297 ));
4298 }
4299 }
4300 }
4301
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4302 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
4303 // string view; no copy, no validation, same memory.
4304 if name == "from_utf8_unchecked" && codes.len() == 1 {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4305 // `String::from_utf8_unchecked(v)` takes ownership and yields an
4306 // owned `String`; `str::from_utf8_unchecked(b)` borrows and yields
4307 // a view. Same name, different operations -- the qualifier says
4308 // which, and an unqualified call is ambiguous.
4309 let q = p
4310 .path
4311 .segments
4312 .iter()
4313 .rev()
4314 .nth(1)
4315 .map(|s| s.ident.to_string());
4316 return match q.as_deref() {
4317 Some("String") => Ok(Val::new(
4318 format!("rsStringOf({})", codes[0]),
4319 Some(Nim::Prim("string".into())),
4320 )),
4321 Some("str") => Ok(Val::new(
4322 format!("rsStrView({})", codes[0]),
4323 Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
4324 )),
4325 _ => Err(
4326 "`from_utf8_unchecked` must be written as `str::..` (a \
4327 borrowed view) or `String::..` (an owned string); the two \
4328 are different operations"
4329 .into(),
4330 ),
4331 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4332 }
4333
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4334 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
4335 if let Some((def, v)) = self.resolve_variant(&p.path) {
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago4336 let (ty, _) = self.variant_type(&def, expect)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4337 return Ok(Val::new(
4338 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago4339 Some(ty),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4340 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4341 }
4342
4343 // A bare path that names a primitive type is Rust's tuple-struct-like
4344 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4345 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
4346 // is invoked.
4347 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
4348 return Ok(Val::new(
4349 format!("{}({})", ident(&name), codes.join(", ")),
4350 Some((*ret).clone()),
4351 ));
4352 }
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago4353 // `Adler32::new()` / `Adler32::default()`: a method called through
4354 // its type rather than through a receiver.
4355 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
4356 // `Self::new()` inside an `impl` names the type being implemented.
4357 let q = if q == "Self" {
4358 self.self_ty.as_ref().map(type_name).unwrap_or(q)
4359 } else {
4360 q
4361 };
4362 if let Some(sig) = self.methods.get(&(q.clone(), name.clone())) {
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago4363 // Re-lower the arguments with the declared parameter types, so
4364 // a literal takes the width the signature asks for.
4365 let declared = sig.params.clone();
4366 let mut args = args.clone();
4367 let mut codes = codes.clone();
4368 for (i, a) in c.args.iter().enumerate() {
4369 if let Some(want) = declared.get(i) {
4370 let want = want.clone().unvar();
4371 args[i] = self.expr_at(a, Some(&want))?;
4372 codes[i] = args[i].code.clone();
4373 }
4374 }
4375 let sig = &self.methods[&(q.clone(), name.clone())];
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago4376 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
4377 let ret = Self::instantiate(sig, &arg_tys);
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago4378 let nim = self
4379 .statics
4380 .get(&(q.clone(), name.clone()))
4381 .cloned()
4382 .unwrap_or_else(|| ident(&name));
4383 return Ok(Val::new(format!("{}({})", nim, codes.join(", ")), Some(ret)));
4384 }
4385 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago4386 let arg_tys: Vec<Option<Nim>> = args.iter().map(|a| a.ty.clone()).collect();
4387 let ret = target
4388 .as_ref()
4389 .and_then(|k| self.fns.get(k))
4390 .map(|sig| Self::instantiate(sig, &arg_tys));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4391 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 18h ago4392 return Err(format!(
4393 "call to unknown function `{name}`; only functions defined in \
4394 this file and the supported standard-library subset can be lowered"
4395 ));
4396 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4397 let nim = match &target {
4398 Some((m, n)) => self.fn_name(m, n),
4399 None => ident(&name),
4400 };
4401 Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4402 }
4403
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4404 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 18h ago4405 let name = m.method.to_string();
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago4406 // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield.
4407 if name == "remainder" && m.args.is_empty() {
4408 if let Expr::Path(p) = &*m.receiver {
4409 if let Some(Alias::Iterator(it)) = self.lookup_alias(&path_name(&p.path)) {
4410 if let Iter::Chunks { code, base, len, k, elem, .. } = &*it {
4411 let kept = format!("(({} div int({})) * int({}))", len, k, k);
4412 let mut v = Val::new(
4413 String::new(),
4414 elem.clone().map(|e| Nim::OpenArray(Box::new(e))),
4415 );
4416 v.window = Some(Alias::Window {
4417 code: code.clone(),
4418 off: format!("({} + {})", base, kept),
4419 len: format!("({} - {})", len, kept),
4420 elem: elem.clone(),
4421 });
4422 return Ok(v);
4423 }
4424 return Err(
4425 "`.remainder()` is only defined for a `chunks_exact` iterator".into(),
4426 );
4427 }
4428 }
4429 return Err("`.remainder()` needs an iterator bound by `let`".into());
4430 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4431 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
4432 match name.as_str() {
4433 "len" => {
4434 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
4435 }
4436 "is_empty" => {
4437 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
4438 }
4439 other => {
4440 return Err(format!(
4441 "`.{other}()` on a slice window from `chunks_exact`/\
4442 `windows` is not implemented; only indexing and \
4443 `len()` are"
4444 ))
4445 }
4446 }
4447 }
4448 let recv = self.expr(&m.receiver)?;
4449 let rt0 = recv.ty.clone();
4450
4451// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
4452 // way to put a view in an object, so instead of materialising an
4453 // Option the view and its validity condition travel together until
4454 // an `ok_or`/`?`/`unwrap` resolves them.
4455 if matches!(name.as_str(), "get" | "get_mut")
4456 && matches!(m.args.first(), Some(Expr::Range(_)))
4457 {
4458 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 17h ago4459 let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4460 let lo = match &r.start {
4461 Some(e) => format!("int({})", self.expr(e)?.code),
4462 None => "0".into(),
4463 };
4464 let len = match (&r.end, r.limits) {
4465 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
4466 format!("(int({}) - {})", self.expr(e)?.code, lo)
4467 }
4468 (Some(e), syn::RangeLimits::Closed(_)) => {
4469 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
4470 }
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4471 (None, _) => format!("({} - {})", blen, lo),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4472 };
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4473 // Hoisted, so the bounds are computed once -- as Rust computes
4474 // them once -- and cannot be re-evaluated later in a scope where
4475 // the names they mention have been shadowed by a loop pattern.
4476 let off_t = self.fresh("Off");
4477 let len_t = self.fresh("Len");
4478 self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
4479 self.line(&format!("let {}: int = {}", len_t, len));
4480 let elem = belem
4481 .or_else(|| elem_of(&rt0))
4482 .ok_or("cannot infer the element type of this slice")?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4483 let mut v = Val::new(
4484 String::new(),
4485 Some(Nim::Named(
4486 "Option".into(),
4487 vec![Nim::OpenArray(Box::new(elem.clone()))],
4488 )),
4489 );
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4490 v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4491 v.window = Some(Alias::Window {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4492 code,
4493 off: off_t,
4494 len: len_t,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4495 elem: Some(elem),
4496 });
4497 return Ok(v);
4498 }
4499
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4500 // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
4501 // parameter type comes from the receiver, so they are handled before
4502 // the arguments are lowered. The closure is expanded inline, with its
4503 // parameter aliased to the payload: that keeps the whole thing an
4504 // expression and avoids handing a view to a generic proc.
4505 if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
4506 if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
4507 (recv.ty.clone(), &m.args[0])
4508 {
4509 if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
4510 {
4511 return self.map_closure(&name, &recv, &kind, &targs, c);
4512 }
4513 }
4514 }
4515
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4516 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
4517 // own type; `v.push(e)` takes the element type.
4518 let arg_want = match (name.as_str(), &recv.ty) {
4519 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
4520 (_, t) => t.clone(),
4521 };
4522 let mut args = Vec::new();
4523 for a in &m.args {
4524 args.push(self.expr_at(a, arg_want.as_ref())?);
4525 }
4526 let a0 = args.first().map(|a| a.code.clone());
4527 let rt = recv.ty.clone();
4528
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago4529 // A method the input defines wins over our model of the standard
4530 // library: `is_empty` on a `bitflags!` type is that type's, not the
4531 // sequence one. Rust resolves inherent methods the same way.
4532 if let Some(t) = &rt {
4533 let key = (type_name(t), name.clone());
4534 if self.methods.contains_key(&key) {
4535 let declared = self.methods[&key].params.clone();
4536 let skip = usize::from(declared.len() == m.args.len() + 1);
4537 for (i, a) in m.args.iter().enumerate() {
4538 if let Some(want) = declared.get(i + skip) {
4539 let want = want.clone().unvar();
4540 args[i] = self.expr_at(a, Some(&want))?;
4541 }
4542 }
4543 let mut arg_tys: Vec<Option<Nim>> = vec![rt.clone()];
4544 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
4545 let ret = Self::instantiate(&self.methods[&key], &arg_tys);
4546 let nim = self
4547 .statics
4548 .get(&key)
4549 .cloned()
4550 .unwrap_or_else(|| ident(&name));
4551 let mut all = vec![recv.code.clone()];
4552 all.extend(args.iter().map(|a| a.code.clone()));
4553 return Ok(Val::new(format!("{}({})", nim, all.join(", ")), Some(ret)));
4554 }
4555 }
4556
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4557 let (code, ty) = match name.as_str() {
4558 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
4559 // explicit so that a `usize` binding type-checks on the Nim side.
4560 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
4561 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
4562 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
4563 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
4564 | "into_iter" => (recv.code.clone(), rt.clone()),
4565 "unwrap" | "expect" => {
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4566 // Expanded inline rather than called as a generic proc: when
4567 // the payload is a view, Nim can only borrow from a path
4568 // expression, which a proc body containing the panic is not.
4569 let (kind, inner) = match &rt {
4570 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
4571 ("Option", a[0].clone())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4572 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4573 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
4574 ("Result", a[0].clone())
4575 }
4576 _ => {
4577 return Err(format!(
4578 "`.{name}()` needs a known `Option`/`Result` receiver type"
4579 ))
4580 }
4581 };
4582 if self.in_loop_cond {
4583 return Err(format!(
4584 "`.{name}()` in a loop condition is not implemented yet: the \
4585 check it expands to would run once, before the loop"
4586 ));
4587 }
4588 let tmp = self.fresh("Unwrap");
4589 let rty = rt.clone().unwrap();
4590 self.line(&format!("let {}: {} = {}", tmp, rty.render(), recv.code));
4591 let (test, msg) = if kind == "Option" {
4592 (format!("{}.has", tmp), "called `Option::unwrap()` on a `None` value")
4593 } else {
4594 (format!("{}.ok", tmp), "called `Result::unwrap()` on an `Err` value")
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4595 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4596 let msg = if name == "expect" {
4597 args.first().map(|a| a.code.clone()).unwrap_or_else(|| fmt::nim_str(msg))
4598 } else {
4599 fmt::nim_str(msg)
4600 };
4601 self.line(&format!("if not {}:", test));
4602 self.line(&format!(" rsPanic({})", msg));
4603 // If the payload is a view, hand back an alias rather than a
4604 // value: Nim will not let a `let` borrow out of a local, and a
4605 // view is a reference anyway, so there is nothing to bind.
4606 // `{tmp}.val` is a plain field access, so substituting it at
4607 // each use re-evaluates nothing.
4608 if matches!(inner, Nim::OpenArray(_)) {
4609 let mut v = Val::new(format!("{}.val", tmp), Some(inner.clone()));
4610 v.window = Some(Alias::Value {
4611 code: format!("{}.val", tmp),
4612 ty: Some(inner),
4613 });
4614 return Ok(v);
4615 }
4616 (format!("{}.val", tmp), Some(inner))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4617 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4618 "ok_or" if recv.guard.is_some() => {
4619 let e = args.first().ok_or("`ok_or` takes one argument")?;
4620 let ety = e.ty.clone();
4621 let mut v = recv.clone();
4622 v.guard_err = Some(e.code.clone());
4623 v.ty = match (&recv.ty, ety) {
4624 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
4625 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
4626 }
4627 _ => None,
4628 };
4629 return Ok(v);
4630 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4631 "ok_or" => {
4632 let inner = match &rt {
4633 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
4634 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
4635 };
4636 let e = args.first().ok_or("`ok_or` takes one argument")?;
4637 let ety = e
4638 .ty
4639 .clone()
4640 .ok_or("`ok_or` needs a known error type for its argument")?;
4641 (
4642 format!(
4643 "rsOkOr[{}, {}]({}, {})",
4644 inner.render(),
4645 ety.render(),
4646 recv.code,
4647 e.code
4648 ),
4649 Some(Nim::Named("Result".into(), vec![inner, ety])),
4650 )
4651 }
4652 "unwrap_or" => {
4653 let inner = match &rt {
4654 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
4655 Some(a[0].clone())
4656 }
4657 _ => None,
4658 };
4659 (
4660 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
4661 inner,
4662 )
4663 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4664 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
4665 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
4666 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
4667 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
4668
4669 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
4670 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
4671 // Nim raises OverflowDefect, so the operation is routed through
4672 // the unsigned view of the same width, which is what Rust's
4673 // wrapping_* is defined to compute.
4674 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
4675 let op = match name.as_str() {
4676 "wrapping_add" => "+",
4677 "wrapping_sub" => "-",
4678 _ => "*",
4679 };
4680 let t = rt.clone().ok_or_else(|| {
4681 format!("`{name}` needs a known receiver type to pick the wrapping width")
4682 })?;
4683 if !t.is_integer() {
4684 return Err(format!("`{name}` on a non-integer type"));
4685 }
4686 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
4687 if t.is_unsigned() {
4688 (format!("({} {} {})", recv.code, op, arg), Some(t))
4689 } else {
4690 let u = unsigned_peer(&t)?;
4691 (
4692 format!(
4693 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
4694 t.render(), u, recv.code, op, u, arg
4695 ),
4696 Some(t),
4697 )
4698 }
4699 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4700 // Inside a formatting impl, a write through the `Formatter` *is*
4701 // 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 16h ago4702 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => {
4703 let a = args.first().ok_or("`write_str` takes one argument")?;
4704 // A `&str` argument is a character view, not a Nim string.
4705 let text = match &a.ty {
4706 Some(Nim::Prim(p)) if p == "string" => a.code.clone(),
4707 _ => format!("rsDisplay({})", a.code),
4708 };
4709 (format!("result.add({})", text), Some(Nim::Unit))
4710 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago4711 "saturating_add" | "saturating_sub" | "saturating_mul" | "checked_add"
4712 | "checked_sub" | "checked_mul" => {
4713 let t = rt
4714 .clone()
4715 .filter(|t| t.is_integer())
4716 .ok_or_else(|| format!("`{name}` needs a known integer receiver"))?;
4717 let arg = args
4718 .first()
4719 .ok_or_else(|| format!("`{name}` takes one argument"))?;
4720 let f = match name.as_str() {
4721 "saturating_add" => "rsSatAdd",
4722 "saturating_sub" => "rsSatSub",
4723 "saturating_mul" => "rsSatMul",
4724 "checked_add" => "rsChkAdd",
4725 "checked_sub" => "rsChkSub",
4726 _ => "rsChkMul",
4727 };
4728 let out = if name.starts_with("checked") {
4729 Nim::Named("Option".into(), vec![t])
4730 } else {
4731 t
4732 };
4733 (format!("{}({}, {})", f, recv.code, arg.code), Some(out))
4734 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago4735 // `as_ptr` hands a C function the address of the first element,
4736 // which is what Rust's does. An empty slice has no first element
4737 // in either language, and reading through the pointer would be
4738 // undefined in both.
4739 "as_ptr" | "as_mut_ptr" => {
4740 let elem = elem_of(&rt)
4741 .ok_or("`as_ptr` needs a known element type")?;
4742 (
4743 format!(
4744 "(if {r}.len == 0: nil else: cast[ptr {e}](addr {r}[0]))",
4745 r = recv.code,
4746 e = elem.render()
4747 ),
4748 Some(Nim::Ptr(Box::new(elem))),
4749 )
4750 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4751 "abs" => (format!("abs({})", recv.code), rt.clone()),
4752 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4753 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
4754 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
4755 "as_bytes" | "into_bytes" => (
4756 format!("rsBytes({})", recv.code),
4757 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
4758 ),
4759
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4760 "into" => {
4761 // `.into()` resolves through the `impl From` declarations, and
4762 // needs the target type to pick one.
4763 let from = rt
4764 .clone()
4765 .ok_or("`.into()` needs a known receiver type")?;
4766 let to = expect
4767 .ok_or("`.into()` needs a known target type; annotate the binding")?;
4768 let key = (type_name(&from), type_name(to));
4769 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
4770 format!(
4771 "no `impl From<{}> for {}` in this file, so `.into()` has \
4772 no conversion to call",
4773 key.0, key.1
4774 )
4775 })?;
4776 (format!("{}({})", f, recv.code), Some(to.clone()))
4777 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4778 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4779 // A method defined in this file via `impl`, found by the
4780 // receiver's type rather than by name alone.
4781 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 15h ago4782 // Re-lower the arguments with the declared parameter types:
4783 // a method's own signature says what width its literals are,
4784 // which the receiver's type does not.
4785 let declared: Option<Vec<Nim>> = key
4786 .as_ref()
4787 .and_then(|k| self.methods.get(k))
4788 .map(|s| s.params.clone());
4789 if let Some(d) = &declared {
4790 // params[0] is the receiver for a method with `self`.
4791 let skip = usize::from(d.len() == m.args.len() + 1);
4792 for (i, a) in m.args.iter().enumerate() {
4793 if let Some(want) = d.get(i + skip) {
4794 let want = want.clone().unvar();
4795 args[i] = self.expr_at(a, Some(&want))?;
4796 }
4797 }
4798 }
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago4799 let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
4800 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago4801 let sig = key
4802 .as_ref()
4803 .and_then(|k| self.methods.get(k))
Add generics; transpile the part of cosmic-theme that is reachable ff34e1b nandithebull 15h ago4804 .map(|s| Self::instantiate(s, &arg_tys));
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4805 if let Some(ret) = sig {
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago4806 // Use the name the proc was actually emitted under: an
4807 // inherent method is qualified by its module, a trait
4808 // method by its trait.
4809 let nim = key
4810 .and_then(|k| self.statics.get(&k).cloned())
4811 .unwrap_or_else(|| ident(&name));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4812 let mut all = vec![recv.code.clone()];
4813 all.extend(args.iter().map(|a| a.code.clone()));
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago4814 (format!("{}({})", nim, all.join(", ")), Some(ret))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4815 } else {
4816 return Err(format!(
4817 "unsupported method `.{name}()`; it is neither defined in \
4818 this file nor part of the standard-library subset that \
4819 has a verified Nim equivalent"
4820 ));
4821 }
4822 }
4823 };
4824 Ok(Val::new(code, ty))
4825 }
4826
4827 // -------------------------------------------------------------- macros
4828
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4829 /// The element type of a `vec![..]`, from its first element.
4830 fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
4831 let body = mac.tokens.to_string();
4832 if body.trim().is_empty() {
4833 return Ok(None);
4834 }
4835 let first: Option<Expr> = if body.contains(';') {
4836 // The whole body must be consumed or the parse fails, so the
4837 // length is parsed too even though only the element is wanted.
4838 mac.parse_body_with(|input: syn::parse::ParseStream| {
4839 let v: Expr = input.parse()?;
4840 input.parse::<syn::Token![;]>()?;
4841 let _len: Expr = input.parse()?;
4842 Ok(v)
4843 })
4844 .ok()
4845 } else {
4846 mac.parse_body_with(
4847 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4848 )
4849 .ok()
4850 .and_then(|p| p.into_iter().next())
4851 };
4852 match first {
4853 Some(e) => Ok(self.expr(&e)?.ty),
4854 None => Ok(None),
4855 }
4856 }
4857
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4858 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
4859 let name = path_name(&mac.path);
4860 match name.as_str() {
4861 "println" | "print" | "eprintln" | "eprint" => {
4862 let s = self.format_args(mac)?;
4863 let nl = name.ends_with("ln");
4864 Ok(match (name.starts_with('e'), nl) {
4865 (false, true) => format!("echo {s}"),
4866 (false, false) => format!("stdout.write({s})"),
4867 (true, true) => format!("stderr.writeLine({s})"),
4868 (true, false) => format!("stderr.write({s})"),
4869 })
4870 }
4871 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4872 "write" | "writeln" => {
4873 // `write!(f, "..", ..)` inside a formatting impl: the first
4874 // argument is the sink, the rest is an ordinary format call.
4875 let args: Vec<Expr> = mac
4876 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
4877 .map_err(|e| format!("write!: {e}"))?
4878 .into_iter()
4879 .collect();
4880 let sink = args.first().ok_or("`write!` needs a sink")?;
4881 if !self.is_fmt_param(sink) {
4882 return Err("`write!` to anything but the `Formatter` of the \
4883 enclosing formatting impl is not implemented"
4884 .into());
4885 }
4886 let s = self.format_pieces(&args[1..])?;
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4887 let s = if name == "writeln" {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4888 format!("({} & \"\\n\")", s)
4889 } else {
4890 s
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4891 };
4892 Ok(format!("result.add({})", s))
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago4893 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4894 "panic" => {
4895 let s = self.format_args(mac)?;
4896 Ok(format!("rsPanic({s})"))
4897 }
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4898 // `debug_assert*` fires in debug builds, which is the profile
4899 // this project models, so it lowers the same as `assert*`.
4900 "assert" | "debug_assert" => {
4901 let args: Vec<Expr> = mac
4902 .parse_body_with(
4903 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4904 )
4905 .map_err(|e| format!("{name}!: {e}"))?
4906 .into_iter()
4907 .collect();
4908 let cond = args.first().ok_or("`assert!` needs a condition")?;
4909 let v = self.expr(cond)?;
4910 let msg = if args.len() > 1 {
4911 self.format_pieces(&args[1..])?
4912 } else {
4913 fmt::nim_str("assertion failed")
4914 };
4915 Ok(format!("(if not ({}): rsPanic({}))", v.code, msg))
4916 }
4917 "assert_eq" | "assert_ne" | "debug_assert_eq" | "debug_assert_ne" => {
4918 let args: Vec<Expr> = mac
4919 .parse_body_with(
4920 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4921 )
4922 .map_err(|e| format!("{name}!: {e}"))?
4923 .into_iter()
4924 .collect();
4925 if args.len() < 2 {
4926 return Err(format!("`{name}!` takes two operands"));
4927 }
4928 let a = self.expr(&args[0])?;
4929 let b = self.expr_at(&args[1], a.ty.as_ref())?;
4930 let ne = name.ends_with("_ne");
4931 let op = if ne { "!=" } else { "==" };
4932 // Rust's message shows both sides; reproducing it keeps a
4933 // failing assertion as informative as the original.
4934 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 18h ago4935 Ok(format!(
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago4936 "(if not (({}) {} ({})): rsPanic({} & \"\\n left: \" & rsDebug({}) & \"\\n right: \" & rsDebug({})))",
4937 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 18h ago4938 ))
4939 }
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago4940 // The `log` facade. See `src/prelude.nim` for why these are
4941 // lowered directly rather than expanded. The enabled check wraps
4942 // the whole thing because Rust does not evaluate a log record's
4943 // arguments when the level is disabled.
4944 "error" | "warn" | "info" | "debug" | "trace" | "log" => {
4945 let args: Vec<Expr> = mac
4946 .parse_body_with(
4947 syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
4948 )
4949 .map_err(|e| format!("`{name}!`: {e}"))?
4950 .into_iter()
4951 .collect();
4952 let (level, rest) = if name == "log" {
4953 let first = args.first().ok_or("`log!` needs a level")?;
4954 (self.log_level_of(first)?, &args[1..])
4955 } else {
4956 (
4957 match name.as_str() {
4958 "error" => "rsLvlError",
4959 "warn" => "rsLvlWarn",
4960 "info" => "rsLvlInfo",
4961 "debug" => "rsLvlDebug",
4962 _ => "rsLvlTrace",
4963 }
4964 .to_string(),
4965 &args[..],
4966 )
4967 };
4968 let msg = self.format_pieces(rest)?;
4969 let target = fmt::nim_str(&self.cur_mod.clone());
4970 Ok(format!(
4971 "(if rsLogEnabled({lvl}): rsLog({lvl}, {target}, {msg}))",
4972 lvl = level
4973 ))
4974 }
4975 "log_enabled" => {
4976 let e: Expr = mac
4977 .parse_body()
4978 .map_err(|e| format!("`log_enabled!`: {e}"))?;
4979 let l = self.log_level_of(&e)?;
4980 Ok(format!("rsLogEnabled({l})"))
4981 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago4982 "vec" => {
4983 let body = mac.tokens.to_string();
4984 if body.trim().is_empty() {
4985 return Ok("@[]".into());
4986 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago4987 // `vec![elem; n]` is the repeat form, not a list. The macro
4988 // body has no brackets, so it is parsed directly.
4989 if body.contains(';') {
4990 let (v, n) = mac
4991 .parse_body_with(|input: syn::parse::ParseStream| {
4992 let v: Expr = input.parse()?;
4993 input.parse::<syn::Token![;]>()?;
4994 let n: Expr = input.parse()?;
4995 Ok((v, n))
4996 })
4997 .map_err(|e| format!("vec![elem; n]: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago4998 let want = self.vec_expect.clone();
4999 let v = self.expr_at(&v, want.as_ref())?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago5000 let n = self.expr(&n)?;
5001 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
5002 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5003 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
5004 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
5005 .map_err(|e| format!("vec!: {e}"))?;
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago5006 let want = self.vec_expect.clone();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5007 let mut parts = Vec::new();
5008 for e in &elems {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago5009 parts.push(self.expr_at(e, want.as_ref())?.code);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5010 }
5011 Ok(format!("@[{}]", parts.join(", ")))
5012 }
5013 other => Err(format!(
5014 "unsupported macro `{other}!`; a macro whose expansion is not \
5015 known cannot be lowered faithfully"
5016 )),
5017 }
5018 }
5019
5020 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
5021 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 17h ago5022 let args: Vec<Expr> = mac
5023 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
5024 .map_err(|e| format!("format arguments: {e}"))?
5025 .into_iter()
5026 .collect();
5027 self.format_pieces(&args)
5028 }
5029
5030 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
5031 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
5032 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 18h ago5033 if args.is_empty() {
5034 return Ok("\"\"".into());
5035 }
5036 return Err("the first argument must be a literal format string".into());
5037 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago5038 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5039
5040 let pieces = fmt::parse(&s.value())?;
5041 let mut parts: Vec<String> = Vec::new();
5042 let mut next = 0usize;
5043 let mut used = vec![false; rest.len()];
5044 for p in &pieces {
5045 match p {
5046 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
5047 fmt::Piece::Arg { r#ref, spec } => {
5048 let v = match r#ref {
5049 fmt::Ref::Next => {
5050 let e = rest.get(next).ok_or("too few arguments for format string")?;
5051 used[next] = true;
5052 next += 1;
5053 self.expr(e)?
5054 }
5055 fmt::Ref::Index(i) => {
5056 let e = rest.get(*i).ok_or("format index out of range")?;
5057 used[*i] = true;
5058 self.expr(e)?
5059 }
5060 fmt::Ref::Named(n) => {
5061 let t = self.lookup(n).ok_or_else(|| {
5062 format!("`{{{n}}}` captures `{n}`, which is not in scope")
5063 })?;
5064 Val::new(ident(n), Some(t))
5065 }
5066 };
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 16h ago5067 let integer = v.ty.as_ref().is_some_and(|t| t.is_integer());
5068 if spec.radix.is_some() && !integer && v.ty.is_none() {
5069 return Err(
5070 "a radix format (`{:x}`, `{:b}`, ...) needs a known \
5071 argument type: on an integer it formats the bit \
5072 pattern, on anything else it calls that type's own \
5073 impl"
5074 .into(),
5075 );
5076 }
5077 parts.push(fmt::render_arg(&v.code, spec, integer));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5078 }
5079 }
5080 }
5081 // Rust rejects an argument that no `{}` consumes; so do we, rather
5082 // than dropping it from the output.
5083 if let Some(i) = used.iter().position(|u| !u) {
5084 return Err(format!(
5085 "argument {} is never used by the format string",
5086 i + 1
5087 ));
5088 }
5089 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
5090 }
5091}
5092
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago5093/// Whether a pattern introduces a binding.
5094fn binds(p: &Pat) -> bool {
5095 match p {
5096 Pat::Ident(_) => true,
5097 Pat::Guard(g) => binds(&g.pat),
5098 Pat::Paren(x) => binds(&x.pat),
5099 Pat::Reference(r) => binds(&r.pat),
5100 Pat::Or(o) => o.cases.iter().any(binds),
5101 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
5102 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
5103 _ => false,
5104 }
5105}
5106
5107/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
5108fn destructures(p: &Pat) -> bool {
5109 matches!(
5110 p,
5111 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
5112 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
5113 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
5114 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
5115}
5116
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5117/// Whether an expression has a direct Nim expression form.
5118///
5119/// Nim's `if` is an expression only when every arm is a single expression, and
5120/// its `case` is never one here. Anything else has to be lowered as statements
5121/// that assign into a target.
5122fn expressible(e: &Expr) -> bool {
5123 match e {
5124 Expr::If(i) => {
5125 let Some(then) = single_expr(&i.then_branch) else { return false };
5126 if !expressible(then) {
5127 return false;
5128 }
5129 match &i.else_branch {
5130 None => false,
5131 Some((_, els)) => match &**els {
5132 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
5133 other => expressible(other),
5134 },
5135 }
5136 }
Lower `extern "C"` blocks to Nim importc; do not transpile libc 7db7991 nandithebull 14h ago5137 // `unsafe { .. }` is transparent, so it is an expression exactly when
5138 // its block is one.
5139 Expr::Unsafe(u) => single_expr(&u.block).is_some_and(expressible),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5140 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
5141 _ => true,
5142 }
5143}
5144
5145/// The single expression a block consists of, if that is all it is. An `if`
5146/// can only be lowered as a Nim `if`-expression when both arms are this shape.
5147fn single_expr(b: &syn::Block) -> Option<&Expr> {
5148 match (b.stmts.len(), b.stmts.first()) {
5149 (1, Some(Stmt::Expr(e, None))) => Some(e),
5150 _ => None,
5151 }
5152}
5153
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago5154/// Substitute `params[i] -> args[i]` through a type. Enough of the type
5155/// grammar is covered to expand the aliases we accept; anything else is left
5156/// alone and will be reported by `ty::map` if it is unsupported.
5157fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
5158 use syn::Type;
5159 match t {
5160 Type::Path(p) => {
5161 if p.qself.is_none() && p.path.segments.len() == 1 {
5162 let seg = &p.path.segments[0];
5163 if seg.arguments.is_empty() {
5164 let name = seg.ident.to_string();
5165 if let Some(i) = params.iter().position(|x| *x == name) {
5166 return args[i].clone();
5167 }
5168 }
5169 }
5170 let mut p = p.clone();
5171 for seg in &mut p.path.segments {
5172 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
5173 for g in &mut a.args {
5174 if let syn::GenericArgument::Type(t) = g {
5175 *t = substitute(t, params, args);
5176 }
5177 }
5178 }
5179 }
5180 Type::Path(p)
5181 }
5182 Type::Reference(r) => {
5183 let mut r = r.clone();
5184 r.elem = Box::new(substitute(&r.elem, params, args));
5185 Type::Reference(r)
5186 }
5187 Type::Slice(sl) => {
5188 let mut sl = sl.clone();
5189 sl.elem = Box::new(substitute(&sl.elem, params, args));
5190 Type::Slice(sl)
5191 }
5192 Type::Array(a) => {
5193 let mut a = a.clone();
5194 a.elem = Box::new(substitute(&a.elem, params, args));
5195 Type::Array(a)
5196 }
5197 Type::Tuple(tp) => {
5198 let mut tp = tp.clone();
5199 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
5200 Type::Tuple(tp)
5201 }
5202 Type::Paren(p) => substitute(&p.elem, params, args),
5203 Type::Group(g) => substitute(&g.elem, params, args),
5204 other => other.clone(),
5205 }
5206}
5207
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5208// --------------------------------------------------------------- utilities
5209
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago5210/// Whether a return type is a borrow of one of the arguments, which Nim
5211/// models with a view rather than with an owned copy.
5212fn returns_borrow(t: &syn::Type) -> bool {
5213 match t {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago5214 syn::Type::Reference(r) => match &*r.elem {
5215 syn::Type::Slice(_) => true,
5216 // `&str` is a borrow of someone else's bytes too, and returning it
5217 // means returning a view, not an owned string.
5218 syn::Type::Path(p) => p.path.is_ident("str"),
5219 _ => false,
5220 },
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago5221 syn::Type::Paren(p) => returns_borrow(&p.elem),
5222 syn::Type::Group(g) => returns_borrow(&g.elem),
5223 _ => false,
5224 }
5225}
5226
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago5227/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
5228/// to the crate root, which is where a flattened module's items live unless
5229/// they came from one of the extra input files.
5230fn module_of(prefix: &[String]) -> String {
5231 match prefix.last() {
5232 Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
5233 _ => String::new(),
5234 }
5235}
5236
5237/// The first type argument of an `Option[T]` / `Result[T, E]`.
5238fn elem_arg(t: &Nim) -> Nim {
5239 match t {
5240 Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
5241 other => other.clone(),
5242 }
5243}
5244
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago5245/// The element type of a sequence-like Nim type.
5246fn elem_of(t: &Option<Nim>) -> Option<Nim> {
5247 match t {
5248 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
5249 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
5250 _ => None,
5251 }
5252}
5253
5254/// The short name a Nim type is known by, for keying method tables.
5255fn type_name(t: &Nim) -> String {
5256 match t {
5257 Nim::Named(n, _) => n.clone(),
5258 Nim::Prim(p) => p.clone(),
5259 other => other.render(),
5260 }
5261}
5262
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago5263/// `(trait, operator)` for every operator trait we dispatch.
5264const OPERATOR_TRAITS: &[(&str, &str)] = &[
5265 ("Add", "+"), ("Sub", "-"), ("Mul", "*"), ("Div", "/"), ("Rem", "%"),
5266 ("BitAnd", "&"), ("BitOr", "|"), ("BitXor", "^"), ("Shl", "<<"), ("Shr", ">>"),
5267 ("AddAssign", "+="), ("SubAssign", "-="), ("MulAssign", "*="), ("DivAssign", "/="),
5268 ("RemAssign", "%="), ("BitAndAssign", "&="), ("BitOrAssign", "|="),
5269 ("BitXorAssign", "^="), ("ShlAssign", "<<="), ("ShrAssign", ">>="),
5270 ("Neg", "neg"), ("Not", "not"),
5271];
5272
5273/// `(operator, trait method name)`.
5274const OP_METHOD: &[(&str, &str)] = &[
5275 ("+", "add"), ("-", "sub"), ("*", "mul"), ("/", "div"), ("%", "rem"),
5276 ("&", "bitand"), ("|", "bitor"), ("^", "bitxor"), ("<<", "shl"), (">>", "shr"),
5277 ("+=", "add_assign"), ("-=", "sub_assign"), ("*=", "mul_assign"),
5278 ("/=", "div_assign"), ("%=", "rem_assign"), ("&=", "bitand_assign"),
5279 ("|=", "bitor_assign"), ("^=", "bitxor_assign"), ("<<=", "shl_assign"),
5280 (">>=", "shr_assign"), ("neg", "neg"), ("not", "not"),
5281];
5282
5283fn op_method(op: &str) -> &'static str {
5284 OP_METHOD.iter().find(|(o, _)| *o == op).map(|(_, m)| *m).unwrap_or("")
5285}
5286
5287/// The operator symbol a compound assignment applies.
5288fn compound_symbol(op: &BinOp) -> &'static str {
5289 match op {
5290 BinOp::AddAssign(_) => "+=",
5291 BinOp::SubAssign(_) => "-=",
5292 BinOp::MulAssign(_) => "*=",
5293 BinOp::DivAssign(_) => "/=",
5294 BinOp::RemAssign(_) => "%=",
5295 BinOp::BitAndAssign(_) => "&=",
5296 BinOp::BitOrAssign(_) => "|=",
5297 BinOp::BitXorAssign(_) => "^=",
5298 BinOp::ShlAssign(_) => "<<=",
5299 BinOp::ShrAssign(_) => ">>=",
5300 _ => "",
5301 }
5302}
5303
5304fn binary_symbol(op: &BinOp) -> &'static str {
5305 match op {
5306 BinOp::Add(_) => "+",
5307 BinOp::Sub(_) => "-",
5308 BinOp::Mul(_) => "*",
5309 BinOp::Div(_) => "/",
5310 BinOp::Rem(_) => "%",
5311 BinOp::BitAnd(_) => "&",
5312 BinOp::BitOr(_) => "|",
5313 BinOp::BitXor(_) => "^",
5314 BinOp::Shl(_) => "<<",
5315 BinOp::Shr(_) => ">>",
5316 _ => "",
5317 }
5318}
5319
5320/// The operator a trait overloads, if it is one of the operator traits.
5321fn operator_trait(t: &str) -> Option<&'static str> {
5322 Some(match t {
5323 "Add" => "+",
5324 "Sub" => "-",
5325 "Mul" => "*",
5326 "Div" => "/",
5327 "Rem" => "%",
5328 "BitAnd" => "&",
5329 "BitOr" => "|",
5330 "BitXor" => "^",
5331 "Shl" => "<<",
5332 "Shr" => ">>",
5333 "AddAssign" => "+=",
5334 "SubAssign" => "-=",
5335 "MulAssign" => "*=",
5336 "DivAssign" => "/=",
5337 "RemAssign" => "%=",
5338 "BitAndAssign" => "&=",
5339 "BitOrAssign" => "|=",
5340 "BitXorAssign" => "^=",
5341 "ShlAssign" => "<<=",
5342 "ShrAssign" => ">>=",
5343 "Neg" => "neg",
5344 "Not" => "not",
5345 _ => return None,
5346 })
5347}
5348
5349/// The Nim proc name for a trait method, qualified by trait and type so that
5350/// two traits declaring the same method name cannot collide.
5351fn trait_method_name(ty: &str, tr: &str, m: &str) -> String {
5352 format!("rs{}_{}_{}", tr, ty, m)
5353}
5354
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago5355fn is_fmt_trait(t: &str) -> bool {
5356 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
5357}
5358
5359/// The prelude proc a formatting trait's output is produced by.
5360fn fmt_proc(t: &str) -> &'static str {
5361 match t {
5362 "Display" => "rsDisplay",
5363 "Debug" => "rsDebug",
5364 "LowerHex" => "rsLowerHex",
5365 "UpperHex" => "rsUpperHex",
5366 "Binary" => "rsBinary",
5367 _ => "rsOctal",
5368 }
5369}
5370
Transpile a second crate, adler2, to test whether any of this generalises 5fdd5be nandithebull 16h ago5371/// Whether an expression is an iterator-producing chain rather than a value.
5372fn is_iterator_expr(e: &Expr) -> bool {
5373 match e {
5374 Expr::MethodCall(m) => matches!(
5375 m.method.to_string().as_str(),
5376 "iter" | "iter_mut" | "into_iter" | "enumerate" | "zip" | "chunks_exact"
5377 | "chunks_exact_mut" | "windows"
5378 ),
5379 Expr::Paren(p) => is_iterator_expr(&p.expr),
5380 _ => false,
5381 }
5382}
5383
Prove byte-identity for base16ct by enumerating whole input domains 99b8376 nandithebull 16h ago5384/// Whether an expression denotes a place -- a variable, a field, or an index
5385/// or slice of one -- and so may be re-evaluated with no side effect.
5386fn is_pure_place(e: &Expr) -> bool {
5387 match e {
5388 Expr::Path(_) => true,
5389 Expr::Field(f) => is_pure_place(&f.base),
5390 Expr::Index(i) => {
5391 is_pure_place(&i.expr)
5392 && match &*i.index {
5393 Expr::Range(r) => {
5394 r.start.as_deref().map_or(true, is_pure_place)
5395 && r.end.as_deref().map_or(true, is_pure_place)
5396 }
5397 other => is_pure_place(other),
5398 }
5399 }
5400 Expr::Lit(_) => true,
5401 Expr::Reference(r) => is_pure_place(&r.expr),
5402 Expr::Paren(p) => is_pure_place(&p.expr),
5403 Expr::Group(g) => is_pure_place(&g.expr),
5404 // Arithmetic on places is still side-effect free, so a bound like
5405 // `..want - 1` does not stop the binding being an alias.
5406 Expr::Binary(b) if !is_compound(&b.op) => {
5407 is_pure_place(&b.left) && is_pure_place(&b.right)
5408 }
5409 Expr::Unary(u) => is_pure_place(&u.expr),
5410 Expr::Cast(c) => is_pure_place(&c.expr),
5411 _ => false,
5412 }
5413}
5414
5415/// Whether an expression is a `&mut` borrow, directly or through parens.
5416fn is_mut_borrow(e: &Expr) -> bool {
5417 match e {
5418 Expr::Reference(r) => r.mutability.is_some(),
5419 Expr::Paren(p) => is_mut_borrow(&p.expr),
5420 Expr::Group(g) => is_mut_borrow(&g.expr),
5421 _ => false,
5422 }
5423}
5424
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5425fn takes_self(sig: &syn::Signature) -> bool {
5426 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
5427}
5428
5429fn path_name(p: &syn::Path) -> String {
5430 p.segments
5431 .last()
5432 .map(|s| s.ident.to_string())
5433 .unwrap_or_default()
5434}
5435
5436fn is_compound(op: &BinOp) -> bool {
5437 matches!(
5438 op,
5439 BinOp::AddAssign(_)
5440 | BinOp::SubAssign(_)
5441 | BinOp::MulAssign(_)
5442 | BinOp::DivAssign(_)
5443 | BinOp::RemAssign(_)
5444 | BinOp::BitAndAssign(_)
5445 | BinOp::BitOrAssign(_)
5446 | BinOp::BitXorAssign(_)
5447 | BinOp::ShlAssign(_)
5448 | BinOp::ShrAssign(_)
5449 )
5450}
5451
5452/// The Nim literal suffix for an integer type (`5'i32`).
5453fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
5454 let Nim::Prim(p) = t else {
5455 return Err("not a primitive integer".into());
5456 };
5457 Ok(match p.as_str() {
5458 "int8" => "i8",
5459 "int16" => "i16",
5460 "int32" => "i32",
5461 "int64" => "i64",
5462 "int" => "i",
5463 "uint8" => "u8",
5464 "uint16" => "u16",
5465 "uint32" => "u32",
5466 "uint64" => "u64",
5467 "uint" => "u",
5468 other => return Err(format!("no Nim literal suffix for `{other}`")),
5469 })
5470}
5471
5472/// The unsigned integer type of the same width, used to spell `wrapping_*`.
5473fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
5474 let Nim::Prim(p) = t else {
5475 return Err("not a primitive integer".into());
5476 };
5477 Ok(match p.as_str() {
5478 "int8" => "uint8",
5479 "int16" => "uint16",
5480 "int32" => "uint32",
5481 "int64" => "uint64",
5482 "int" => "uint",
5483 other => return Err(format!("`{other}` has no unsigned peer")),
5484 })
5485}
5486
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago5487fn quote_meta(m: &syn::Meta) -> String {
5488 match m {
5489 syn::Meta::Path(p) => path_name(p),
5490 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
5491 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
5492 }
5493}
5494
5495fn item_attrs(i: &Item) -> &[syn::Attribute] {
5496 match i {
5497 Item::Fn(f) => &f.attrs,
5498 Item::Struct(s) => &s.attrs,
5499 Item::Enum(e) => &e.attrs,
5500 Item::Impl(x) => &x.attrs,
5501 Item::Const(c) => &c.attrs,
5502 Item::Type(t) => &t.attrs,
5503 Item::Mod(m) => &m.attrs,
5504 Item::Use(u) => &u.attrs,
5505 Item::ExternCrate(e) => &e.attrs,
5506 Item::Static(s) => &s.attrs,
5507 _ => &[],
5508 }
5509}
5510
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 18h ago5511fn item_kind(i: &Item) -> &'static str {
5512 match i {
5513 Item::Trait(_) => "`trait`",
5514 Item::Static(_) => "`static`",
5515 Item::Macro(_) => "macro definition",
5516 Item::Union(_) => "`union`",
5517 _ => "item",
5518 }
5519}
5520
5521fn expr_kind(e: &Expr) -> &'static str {
5522 match e {
5523 Expr::Async(_) => "`async` block",
5524 Expr::Await(_) => "`.await`",
5525 Expr::Try(_) => "`?`",
5526 Expr::Range(_) => "range",
5527 Expr::Match(_) => "`match` (only statement position is implemented)",
5528 Expr::Let(_) => "`let` expression",
5529 Expr::Unsafe(_) => "`unsafe` block",
5530 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
5531 _ => "expression",
5532 }
5533}