nandi/rustnimpublic Fork 0
ae9f986fc43d0fc9ab029946e2c7a25cdc72b4ed
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 · 3385 lines · 137.9 KBRust Blame HistoryRaw
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1//! Rust AST -> Nim source.
2//!
3//! The governing rule is in DESIGN.md and it shapes every function here:
4//! anything whose Rust semantics cannot be reproduced exactly in Nim returns
5//! `Err` with a reason. Nothing is emitted on a guess. Where a construct maps
6//! one-to-one (signed `shr`, unsigned wrapping, truncating `div`/`mod`) the
7//! mapping is direct and there is a comment saying why that is safe.
8
9use crate::fmt;
10use crate::ty::{self, Nim};
11use std::collections::HashMap;
12use syn::{
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago13 BinOp, Expr, FnArg, GenericArgument, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago14};
15
16// --------------------------------------------------------------- vocabulary
17
18/// Nim keywords. Rust code may legally use any of these as an identifier.
19const NIM_KEYWORDS: &[&str] = &[
20 "addr", "and", "as", "asm", "bind", "block", "break", "case", "cast",
21 "concept", "const", "continue", "converter", "defer", "discard", "distinct",
22 "div", "do", "elif", "else", "end", "enum", "except", "export", "finally",
23 "for", "from", "func", "if", "import", "in", "include", "interface", "is",
24 "isnot", "iterator", "let", "macro", "method", "mixin", "mod", "nil", "not",
25 "notin", "object", "of", "or", "out", "proc", "ptr", "raise", "ref",
26 "return", "shl", "shr", "static", "template", "try", "tuple", "type",
27 "using", "var", "when", "while", "xor", "result", "echo",
28];
29
30fn ident(name: &str) -> String {
31 if NIM_KEYWORDS.contains(&name) {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago32 return format!("{name}_r");
33 }
34 // Nim identifiers may not begin with an underscore, and may not contain
35 // two in a row. Rust uses both freely (`_unused`, `__private`).
36 let mut out = String::new();
37 let mut last_us = false;
38 for (i, c) in name.chars().enumerate() {
39 if c == '_' {
40 if i == 0 {
41 out.push('u');
42 out.push('_');
43 last_us = true;
44 continue;
45 }
46 if last_us {
47 continue;
48 }
49 last_us = true;
50 out.push('_');
51 } else {
52 last_us = false;
53 out.push(c);
54 }
55 }
56 if out.ends_with('_') {
57 out.push('x');
58 }
59 out
60}
61
62/// A `for`-loop source, resolved from a chain of iterator adaptors.
63///
64/// Rust's slice iterators are lazy and compose; Nim's `for` is over one
65/// sequence. So a chain is resolved into this shape and then emitted as a
66/// single index loop, with each binding becoming an *lvalue* into the original
67/// container. That is what makes `*dst = v` through `iter_mut()` write back to
68/// the caller's slice rather than to a copy.
69#[derive(Clone, Debug)]
70enum Iter {
71 /// `a..b` / `a..=b`.
72 Range { lo: String, hi: String, closed: bool, ty: Option<Nim> },
73 /// `for x in a`, `a.iter()`, `a.iter_mut()`. `off`/`len` let the same
74 /// shape cover a subslice view. `mutable` only affects whether the binding
75 /// may be assigned through.
76 Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool },
77 /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of
78 /// `k` elements starting at `k * i`.
79 Chunks { code: String, k: String, elem: Option<Nim>, mutable: bool },
80 /// `a.windows(k)`: like `Chunks` but advancing one element at a time.
81 Windows { code: String, k: String, elem: Option<Nim> },
82 /// `.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(),
98 Iter::Chunks { code, k, .. } => format!("({}.len div int({}))", code, k),
99 Iter::Windows { code, k, .. } => {
100 format!("(max(0, {}.len - int({}) + 1))", code, k)
101 }
102 Iter::Enumerate(i) => i.len(),
103 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
104 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago105 }
106}
107
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago108/// How a `for`-loop pattern name refers back into the container it came from.
109#[derive(Clone, Debug)]
110enum Alias {
111 /// The name stands for this Nim lvalue expression.
112 Value { code: String, ty: Option<Nim> },
113 /// The name stands for a window: `code[off .. off + len - 1]`.
114 Window { code: String, off: String, len: String, elem: Option<Nim> },
115}
116
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago117/// A lowered expression: its Nim text, and its type where we know it.
118///
119/// The type is not decoration. Nim needs it to pick `div` over `/`, to size a
120/// `cast`, and to annotate every binding so that Nim's own type checker
121/// catches a mistake in this file rather than letting it through as output
122/// that runs and is wrong.
123#[derive(Clone, Debug)]
124struct Val {
125 code: String,
126 ty: Option<Nim>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago127 /// Set when the value *is* a slice view rather than a Nim value: binding
128 /// it introduces an alias, not a copy.
129 window: Option<Alias>,
130 /// For `get`/`get_mut`: the condition under which the `Option` is `Some`,
131 /// carried until an `ok_or`/`?` or an `unwrap` consumes it. Nim's view
132 /// types cannot live inside an object, so an `Option` of a view has no
133 /// runtime representation -- it is tracked here instead.
134 guard: Option<String>,
135 /// The error an `ok_or` attached to that guard.
136 guard_err: Option<String>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago137}
138
139impl Val {
140 fn new(code: impl Into<String>, ty: Option<Nim>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago141 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 8h ago142 }
143 fn untyped(code: impl Into<String>) -> Self {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago144 Val::new(code, None)
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago145 }
146}
147
148struct Sig {
149 params: Vec<Nim>,
150 ret: Nim,
151}
152
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago153/// One variant of a Rust enum.
154#[derive(Clone)]
155struct Variant {
156 name: String,
157 /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
158 /// `f0`, `f1`, ...; every field is prefixed with the variant name because
159 /// Nim requires the branches of a variant object to have distinct fields.
160 fields: Vec<(String, Nim)>,
161}
162
163#[derive(Clone)]
164struct EnumDef {
165 name: String,
166 /// True when every variant is a unit variant, which Nim represents as a
167 /// plain `enum` rather than an object variant.
168 simple: bool,
169 variants: Vec<Variant>,
170}
171
172impl EnumDef {
173 fn kind_ident(&self, v: &str) -> String {
174 format!("k{}{}", self.name, v)
175 }
176 fn ctor_ident(&self, v: &str) -> String {
177 format!("{}{}", self.name, v)
178 }
179 fn get(&self, v: &str) -> Option<&Variant> {
180 self.variants.iter().find(|x| x.name == v)
181 }
182}
183
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago184pub struct Lowerer {
185 out: String,
186 indent: usize,
187 scopes: Vec<HashMap<String, Nim>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago188 /// Names introduced by a `for` pattern that stand for an lvalue or a
189 /// window into a container, rather than for a variable of their own.
190 alias_scopes: Vec<HashMap<String, Alias>>,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago191 fns: HashMap<String, Sig>,
192 /// struct name -> (field, type)
193 structs: HashMap<String, Vec<(String, Nim)>>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago194 enums: HashMap<String, EnumDef>,
195 /// variant name -> enums declaring it. A variant named by more than one
196 /// enum must be written qualified, or it is rejected as ambiguous.
197 variant_owner: HashMap<String, Vec<String>>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago198 /// `(receiver type, method) -> signature`. Keyed by type because two
199 /// types may define the same method name, and Nim tells them apart by
200 /// overload resolution on the first parameter.
201 methods: HashMap<(String, String), Sig>,
202 /// The formatting traits implemented for each type, so `{}`/`{:?}`/`{:x}`
203 /// on a user type can be checked rather than assumed.
204 fmt_impls: HashMap<(String, String), ()>,
205 /// `(from, to)` conversions declared by `impl From<A> for B`.
206 from_impls: HashMap<(String, String), String>,
207 /// Forward declarations, emitted between the type definitions and the
208 /// bodies. Rust has no declaration-before-use rule and Nim does, so every
209 /// proc is declared up front rather than the input being reordered --
210 /// which would not work for mutual recursion anyway.
211 forwards: Vec<String>,
212 /// While lowering a formatting impl: the `Formatter` parameter's name.
213 /// Writes through it produce the proc's string result.
214 fmt_param: Option<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago215 /// `type X<T> = ...`, expanded before any type is mapped.
216 aliases: HashMap<String, (Vec<String>, syn::Type)>,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago217 /// Module names supplied as separate input files. A `mod x;` naming one
218 /// of these is satisfied by that file having been passed in.
219 pub modules: Vec<String>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago220 /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
221 /// evaluated against these exactly as rustc would, so an item that is
222 /// dropped here is genuinely not part of the program being compiled.
223 pub features: Vec<String>,
224 dropped_by_cfg: usize,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago225 /// Return type of the proc being lowered, so `return e` and a trailing
226 /// expression can type their literals the way Rust's inference would.
227 ret: Option<Nim>,
228 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
229 /// statement must assign their value to.
230 target: Option<(String, Option<Nim>)>,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago231 /// Set while lowering a `while` condition, which Nim re-evaluates each
232 /// iteration and so cannot have statements hoisted out of it.
233 in_loop_cond: bool,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago234 tmp: usize,
235}
236
237impl Lowerer {
238 pub fn new() -> Self {
239 Lowerer {
240 out: String::new(),
241 indent: 0,
242 scopes: vec![HashMap::new()],
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago243 alias_scopes: vec![HashMap::new()],
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago244 fns: HashMap::new(),
245 structs: HashMap::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago246 enums: HashMap::new(),
247 variant_owner: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago248 methods: HashMap::new(),
249 fmt_impls: HashMap::new(),
250 from_impls: HashMap::new(),
251 fmt_param: None,
252 forwards: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago253 aliases: HashMap::new(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago254 modules: Vec::new(),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago255 features: Vec::new(),
256 dropped_by_cfg: 0,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago257 ret: None,
258 target: None,
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago259 in_loop_cond: false,
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago260 tmp: 0,
261 }
262 }
263
264 // ------------------------------------------------------------ emission
265
266 fn line(&mut self, s: &str) {
267 for _ in 0..self.indent {
268 self.out.push_str(" ");
269 }
270 self.out.push_str(s);
271 self.out.push('\n');
272 }
273
274 fn blank(&mut self) {
275 self.out.push('\n');
276 }
277
278 fn fresh(&mut self, hint: &str) -> String {
279 self.tmp += 1;
280 format!("rsTmp{}{}", hint, self.tmp)
281 }
282
283 // --------------------------------------------------------------- scope
284
285 fn push_scope(&mut self) {
286 self.scopes.push(HashMap::new());
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago287 self.alias_scopes.push(HashMap::new());
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago288 }
289 fn pop_scope(&mut self) {
290 self.scopes.pop();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago291 self.alias_scopes.pop();
292 }
293 fn bind_alias(&mut self, name: &str, a: Alias) {
294 self.alias_scopes
295 .last_mut()
296 .unwrap()
297 .insert(name.to_string(), a);
298 }
299 fn lookup_alias(&self, name: &str) -> Option<Alias> {
300 self.alias_scopes
301 .iter()
302 .rev()
303 .find_map(|s| s.get(name).cloned())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago304 }
305 fn bind(&mut self, name: &str, t: Nim) {
306 self.scopes.last_mut().unwrap().insert(name.to_string(), t);
307 }
308 fn lookup(&self, name: &str) -> Option<Nim> {
309 self.scopes.iter().rev().find_map(|s| s.get(name).cloned())
310 }
311
312 // ---------------------------------------------------------------- file
313
314 pub fn lower_file(&mut self, file: &syn::File) -> Result<String, String> {
315 self.out.push_str(include_str!("prelude.nim"));
316 self.blank();
317
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago318 // Pass 0: type aliases. A signature in one file may use an alias
319 // declared in another, and inputs are given in whatever order suits
320 // the caller, so aliases are registered before anything is mapped.
321 for item in &file.items {
322 self.collect_aliases(item)?;
323 }
324
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago325 // Pass 1: signatures and struct shapes, so that a call can be typed
326 // regardless of declaration order (Rust has no forward declarations).
327 for item in &file.items {
328 self.collect(item)?;
329 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago330 // Pass 2: type definitions, which every signature may mention.
331 for item in &file.items {
332 self.item_types(item)?;
333 }
334
335 // Pass 3: forward declarations. Rust imposes no declaration order and
336 // Nim does, so everything is declared before any body is emitted;
337 // reordering the input would not handle mutual recursion anyway.
338 if !self.forwards.is_empty() {
339 for f in self.forwards.clone() {
340 self.line(&f);
341 }
342 self.blank();
343 }
344
345 // Pass 4: bodies.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago346 for item in &file.items {
347 self.item(item)?;
348 }
349
350 if self.fns.contains_key("main") {
351 self.blank();
352 self.line("when isMainModule:");
353 self.indent += 1;
354 self.line("try:");
355 self.line(" main()");
356 // Rust's panic exits 101 with a message on stderr. Nim's Defects
357 // exit 1. Mapping them here is what keeps the differential runner's
358 // exit-status comparison meaningful for panicking programs.
359 self.line("except RustPanic as e:");
360 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
361 self.line(" quit(101)");
362 self.line("except Defect as e:");
363 self.line(" stderr.writeLine(\"thread 'main' panicked: \" & e.msg)");
364 self.line(" quit(101)");
365 self.indent -= 1;
366 }
367 Ok(std::mem::take(&mut self.out))
368 }
369
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago370 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
371 if !self.cfg_keeps(item_attrs(item))? {
372 return Ok(());
373 }
374 match item {
375 Item::Type(t) => {
376 let params: Vec<String> = t
377 .generics
378 .params
379 .iter()
380 .filter_map(|g| match g {
381 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
382 _ => None,
383 })
384 .collect();
385 self.aliases
386 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
387 }
388 Item::Mod(m) if m.content.is_some() => {
389 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
390 for i in &items {
391 self.collect_aliases(i)?;
392 }
393 }
394 _ => {}
395 }
396 Ok(())
397 }
398
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago399 fn collect(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago400 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
401 // silently would change what the program does; picking a feature set
402 // on the user's behalf would be a guess. So it is reported, except on
403 // items that carry no runtime meaning here anyway.
404 if !self.cfg_keeps(item_attrs(item))? {
405 self.dropped_by_cfg += 1;
406 return Ok(());
407 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago408 match item {
409 Item::Fn(f) => {
410 let (params, ret) = self.signature(&f.sig)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago411 let head = self.head_of(&f.sig.ident.to_string(), &f.sig, None)?;
412 self.forwards.push(head);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago413 self.fns.insert(f.sig.ident.to_string(), Sig { params, ret });
414 }
415 Item::Struct(s) => {
416 let mut fields = Vec::new();
417 for (i, f) in s.fields.iter().enumerate() {
418 let name = match &f.ident {
419 Some(id) => id.to_string(),
420 None => format!("f{i}"), // tuple struct
421 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago422 fields.push((name, self.map_ty(&f.ty)?.owned()));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago423 }
424 self.structs.insert(s.ident.to_string(), fields);
425 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago426 Item::Mod(m) if m.content.is_some() => {
427 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
428 for i in &items {
429 self.collect(i)?;
430 }
431 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago432 Item::Type(t) => {
433 let params: Vec<String> = t
434 .generics
435 .params
436 .iter()
437 .filter_map(|g| match g {
438 syn::GenericParam::Type(t) => Some(t.ident.to_string()),
439 _ => None,
440 })
441 .collect();
442 self.aliases
443 .insert(t.ident.to_string(), (params, (*t.ty).clone()));
444 }
445 Item::Enum(e) => {
446 let name = e.ident.to_string();
447 if e.generics.params.iter().any(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
448 return Err(format!("`enum {name}` is generic: not implemented yet"));
449 }
450 let mut variants = Vec::new();
451 for v in &e.variants {
452 let vname = v.ident.to_string();
453 if v.discriminant.is_some() {
454 return Err(format!(
455 "`{name}::{vname}` has an explicit discriminant; Rust's \
456 `as` on such an enum has a value this lowering does not \
457 yet preserve"
458 ));
459 }
460 let mut fields = Vec::new();
461 for (i, f) in v.fields.iter().enumerate() {
462 // Nim requires the branches of a variant object to have
463 // distinct field names, so each is prefixed.
464 let fname = match &f.ident {
465 Some(id) => format!("{vname}_{id}"),
466 None => format!("{vname}_f{i}"),
467 };
468 fields.push((fname, self.map_ty(&f.ty)?.owned()));
469 }
470 variants.push(Variant { name: vname, fields });
471 }
472 let simple = variants.iter().all(|v| v.fields.is_empty());
473 for v in &variants {
474 self.variant_owner
475 .entry(v.name.clone())
476 .or_default()
477 .push(name.clone());
478 }
479 self.enums.insert(
480 name.clone(),
481 EnumDef { name, simple, variants },
482 );
483 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago484 Item::Impl(im) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago485 let self_ty = self.map_ty(&im.self_ty)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago486 let tyname = type_name(&self_ty);
487 if let Some((path, _)) = &im.trait_ {
488 let tr = path_name(path);
489 if im.items.is_empty() {
490 // A marker trait with no items. We do not model trait
491 // resolution at all, so it generates nothing; any use
492 // that actually needed the trait (a `dyn`, a bound) is
493 // rejected where it appears.
494 return Ok(());
495 }
496 if is_fmt_trait(&tr) {
497 self.forwards.push(format!(
498 "proc {}*(self: {}): string",
499 fmt_proc(&tr),
500 self_ty.render()
501 ));
502 self.fmt_impls.insert((tyname, tr), ());
503 return Ok(());
504 }
505 if tr == "From" {
506 let syn::ImplItem::Fn(m) = &im.items[0] else {
507 return Err("`impl From` must contain `fn from`".into());
508 };
509 let (params, _) = self.signature(&m.sig)?;
510 let src = params
511 .first()
512 .ok_or("`fn from` takes one argument")?
513 .clone();
514 let name = format!("rsFrom{}{}", tyname, type_name(&src));
515 self.forwards.push(self.head_of(&name, &m.sig, None)?);
516 self.from_impls
517 .insert((type_name(&src), tyname), name);
518 return Ok(());
519 }
520 return Err(format!(
521 "`impl {tr} for {tyname}`: only formatting traits \
522 (Display, Debug, LowerHex, UpperHex, Binary, Octal), \
523 `From`, and marker traits with no items are implemented"
524 ));
525 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago526 for it in &im.items {
527 if let syn::ImplItem::Fn(m) = it {
528 let (mut params, ret) = self.signature(&m.sig)?;
529 if takes_self(&m.sig) {
530 params.insert(0, self_ty.clone());
531 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago532 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
533 let head = self.head_of(&m.sig.ident.to_string(), &m.sig, recv.as_ref())?;
534 self.forwards.push(head);
535 self.methods
536 .insert((tyname.clone(), m.sig.ident.to_string()), Sig { params, ret });
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago537 }
538 }
539 }
540 _ => {}
541 }
542 Ok(())
543 }
544
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago545 /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
546 ///
547 /// This is evaluation, not approximation: rustc does the same thing, and
548 /// an item whose predicate is false is not part of the compiled program.
549 /// A predicate that cannot be evaluated is reported rather than assumed.
550 fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
551 for a in attrs {
552 if a.path().is_ident("cfg") {
553 let pred: syn::Meta = a
554 .parse_args()
555 .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
556 if !self.cfg_eval(&pred)? {
557 return Ok(false);
558 }
559 }
560 }
561 Ok(true)
562 }
563
564 fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
565 match m {
566 syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
567 let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
568 return Err("`feature = ..` expects a string".into());
569 };
570 Ok(self.features.iter().any(|f| *f == s.value()))
571 }
572 syn::Meta::List(l) if l.path.is_ident("not") => {
573 let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
574 Ok(!self.cfg_eval(&inner)?)
575 }
576 syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
577 let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
578 .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
579 .map_err(|e| e.to_string())?;
580 let all = l.path.is_ident("all");
581 let mut acc = all;
582 for i in &items {
583 let v = self.cfg_eval(i)?;
584 acc = if all { acc && v } else { acc || v };
585 }
586 Ok(acc)
587 }
588 other => Err(format!(
589 "`#[cfg({})]` is not a predicate rustnim can evaluate; only \
590 `feature = \"..\"`, `not`, `all` and `any` are implemented",
591 quote_meta(other)
592 )),
593 }
594 }
595
596 /// Map a Rust type, expanding any `type` alias first. Every type in the
597 /// lowering goes through here rather than calling `ty::map` directly, so
598 /// an alias cannot be missed in one position and honoured in another.
599 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
600 ty::map(&self.expand(t, 0)?)
601 }
602
603 fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
604 if depth > 16 {
605 return Err("type alias expansion did not terminate; is it cyclic?".into());
606 }
607 let syn::Type::Path(p) = t else { return Ok(t.clone()) };
608 // Only an unqualified name can be one of this file's aliases.
609 // `fmt::Result` and `core::result::Result` are different types that
610 // merely end in the same segment.
611 if p.path.segments.len() != 1 {
612 return Ok(t.clone());
613 }
614 let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
615 let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
616 return Ok(t.clone());
617 };
618 let args: Vec<syn::Type> = match &seg.arguments {
619 syn::PathArguments::AngleBracketed(a) => a
620 .args
621 .iter()
622 .filter_map(|g| match g {
623 GenericArgument::Type(t) => Some(t.clone()),
624 _ => None,
625 })
626 .collect(),
627 _ => vec![],
628 };
629 if args.len() != params.len() {
630 // Flattening several files into one module can bring a crate's own
631 // alias (`type Result<T> = Result<T, Error>`) into scope at a site
632 // that meant the builtin (`Result<T, E>`). Rust kept them apart by
633 // module; here they are told apart by arity, and a use that fits
634 // neither is left for `ty::map` to report.
635 return Ok(t.clone());
636 }
637 self.expand(&substitute(target, params, &args), depth + 1)
638 }
639
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago640 /// The Nim `proc` head for a Rust signature, used both for the forward
641 /// declaration and for the definition, so the two cannot drift apart.
642 fn head_of(
643 &self,
644 name: &str,
645 sig: &syn::Signature,
646 recv: Option<&Nim>,
647 ) -> Result<String, String> {
648 let (ptys, ret) = self.signature(sig)?;
649 let mut parts = Vec::new();
650 if let Some(self_ty) = recv {
651 let mutable = matches!(
652 sig.inputs.first(),
653 Some(FnArg::Receiver(r))
654 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
655 );
656 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
657 parts.push(format!("self: {}", t.render()));
658 }
659 let typed: Vec<&syn::PatType> = sig
660 .inputs
661 .iter()
662 .filter_map(|a| match a {
663 FnArg::Typed(t) => Some(t),
664 _ => None,
665 })
666 .collect();
667 for (i, (p, t)) in typed.iter().zip(ptys.iter()).enumerate() {
668 let pname = match &*p.pat {
669 Pat::Ident(id) => id.ident.to_string(),
670 Pat::Wild(_) => format!("unused{}", parts.len()),
671 _ => return Err("only plain identifier parameters are supported".into()),
672 };
673 let _ = i;
674 parts.push(format!("{}: {}", ident(&pname), t.render()));
675 }
676 Ok(if ret == Nim::Unit {
677 format!("proc {}*({})", ident(name), parts.join(", "))
678 } else {
679 format!("proc {}*({}): {}", ident(name), parts.join(", "), ret.render())
680 })
681 }
682
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago683 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
684 if sig.asyncness.is_some() {
685 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
686 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago687 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
688 // `fn encode<'a>(..)` is not generic for our purposes. Type and const
689 // parameters genuinely are, and are rejected.
690 if let Some(p) = sig.generics.params.iter().find(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
691 let what = match p {
692 syn::GenericParam::Const(_) => "const",
693 _ => "type",
694 };
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago695 return Err(format!(
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago696 "`fn {}` has a {what} parameter: generics are not implemented yet",
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago697 sig.ident
698 ));
699 }
700 let mut params = Vec::new();
701 for a in &sig.inputs {
702 if let FnArg::Typed(t) = a {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago703 params.push(self.map_ty(&t.ty)?);
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago704 }
705 }
706 let ret = match &sig.output {
707 ReturnType::Default => Nim::Unit,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago708 // A returned `&[T]` is a borrow of the caller's buffer, so it
709 // stays an `openArray` view. Only an owned type (`Vec<T>`) becomes
710 // a `seq`, which `owned()` would do to both.
711 ReturnType::Type(_, t) => {
712 let n = self.map_ty(t)?;
713 if returns_borrow(t) { n } else { n.owned() }
714 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago715 };
716 Ok((params, ret))
717 }
718
719 // --------------------------------------------------------------- items
720
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago721 /// Emit the type definitions only: they must precede every signature.
722 fn item_types(&mut self, item: &Item) -> Result<(), String> {
723 if !self.cfg_keeps(item_attrs(item))? {
724 return Ok(());
725 }
726 match item {
727 Item::Struct(_) | Item::Enum(_) | Item::Const(_) => self.item_inner(item),
728 Item::Mod(m) if m.content.is_some() => {
729 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
730 for i in &items {
731 self.item_types(i)?;
732 }
733 Ok(())
734 }
735 _ => Ok(()),
736 }
737 }
738
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago739 fn item(&mut self, item: &Item) -> Result<(), String> {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago740 if !self.cfg_keeps(item_attrs(item))? {
741 return Ok(());
742 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago743 // Types were emitted in their own pass.
744 if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) {
745 return Ok(());
746 }
747 self.item_inner(item)
748 }
749
750 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago751 match item {
752 Item::Fn(f) => self.func(&f.sig, &f.block, None),
753 Item::Struct(s) => {
754 let name = s.ident.to_string();
755 let fields = self.structs[&name].clone();
756 self.line(&format!("type {}* = object", ident(&name)));
757 self.indent += 1;
758 if fields.is_empty() {
759 self.line("discard");
760 }
761 for (fname, fty) in &fields {
762 self.line(&format!("{}*: {}", ident(fname), fty.render()));
763 }
764 self.indent -= 1;
765 self.blank();
766 Ok(())
767 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago768 Item::Type(_) => Ok(()), // expanded at every use site
769 Item::Enum(e) => {
770 let def = self.enums[&e.ident.to_string()].clone();
771 self.emit_enum(&def);
772 Ok(())
773 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago774 Item::Const(c) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago775 let t = self.map_ty(&c.ty)?.owned();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago776 let v = self.expr(&c.expr)?;
777 self.bind(&c.ident.to_string(), t.clone());
778 let line = format!("const {}*: {} = {}", ident(&c.ident.to_string()), t.render(), v.code);
779 self.line(&line);
780 self.blank();
781 Ok(())
782 }
783 Item::Impl(im) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago784 let self_ty = self.map_ty(&im.self_ty)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago785 if let Some((path, _)) = &im.trait_ {
786 let tr = path_name(path);
787 if im.items.is_empty() {
788 return Ok(());
789 }
790 let syn::ImplItem::Fn(m) = &im.items[0] else {
791 return Err(format!("unsupported item in `impl {tr}`"));
792 };
793 if is_fmt_trait(&tr) {
794 return self.fmt_impl(&tr, &self_ty, &m.sig, &m.block);
795 }
796 if tr == "From" {
797 let name = {
798 let (params, _) = self.signature(&m.sig)?;
799 let src = params.first().cloned().ok_or("`fn from` takes one argument")?;
800 self.from_impls[&(type_name(&src), type_name(&self_ty))].clone()
801 };
802 return self.func_named(&name, &m.sig, &m.block, None);
803 }
804 return Err(format!("`impl {tr}` is not implemented"));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago805 }
806 for it in &im.items {
807 match it {
808 syn::ImplItem::Fn(m) => {
809 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
810 self.func(&m.sig, &m.block, recv)?;
811 }
812 _ => return Err("only `fn` items are supported inside `impl`".into()),
813 }
814 }
815 Ok(())
816 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago817 // `use` and `extern crate` are resolution directives with no Nim
818 // analogue once everything is one module.
819 Item::Use(_) | Item::ExternCrate(_) => Ok(()),
820 Item::Mod(m) if m.content.is_some() => {
821 // An inline `mod` is flattened; Nim has no nested modules in a
822 // single file.
823 let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
824 for i in &items {
825 self.item(i)?;
826 }
827 Ok(())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago828 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago829 Item::Mod(m) => {
830 // Satisfied if that file was passed in too; everything is one
831 // Nim module, so the declaration itself emits nothing.
832 if self.modules.iter().any(|x| *x == m.ident.to_string()) {
833 return Ok(());
834 }
835 Err(format!(
836 "`mod {};` refers to another file that was not passed to \
837 rustnim; add it to the input list",
838 m.ident
839 ))
840 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago841 other => Err(format!("unsupported item: {}", item_kind(other))),
842 }
843 }
844
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago845 /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
846 fn none_of(&self, expect: Option<&Nim>) -> String {
847 match expect {
848 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
849 format!("rsNone[{}]()", a[0].render())
850 }
851 _ => "rsNone()".to_string(),
852 }
853 }
854
855 fn emit_enum(&mut self, def: &EnumDef) {
856 let name = ident(&def.name);
857 if def.simple {
858 // Every variant is a unit variant, so a plain Nim enum is an exact
859 // fit: it compares, orders and `case`-checks like Rust's.
860 self.line(&format!("type {name}* = enum"));
861 self.indent += 1;
862 for v in &def.variants {
863 self.line(&format!("{}", ident(&v.name)));
864 }
865 self.indent -= 1;
866 self.blank();
867 self.line(&format!("proc rsDebug*(x: {name}): string ="));
868 self.indent += 1;
869 self.line("case x");
870 for v in &def.variants {
871 self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
872 }
873 self.indent -= 1;
874 self.blank();
875 return;
876 }
877
878 // A data-carrying enum is a Nim object variant: one discriminant enum
879 // plus a branch per variant. This is the same shape the prelude uses
880 // for `Option` and `Result`.
881 self.line("type");
882 self.indent += 1;
883 self.line(&format!("{}Kind* = enum", name));
884 self.indent += 1;
885 for v in &def.variants {
886 self.line(&def.kind_ident(&v.name));
887 }
888 self.indent -= 1;
889 self.blank();
890 self.line(&format!("{}* = object", name));
891 self.indent += 1;
892 self.line(&format!("case kind*: {}Kind", name));
893 for v in &def.variants {
894 if v.fields.is_empty() {
895 self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
896 } else {
897 self.line(&format!("of {}:", def.kind_ident(&v.name)));
898 self.indent += 1;
899 for (f, t) in &v.fields {
900 self.line(&format!("{}*: {}", ident(f), t.render()));
901 }
902 self.indent -= 1;
903 }
904 }
905 self.indent -= 2;
906 self.blank();
907
908 for v in &def.variants {
909 let args: Vec<String> = v
910 .fields
911 .iter()
912 .enumerate()
913 .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
914 .collect();
915 let inits: Vec<String> = v
916 .fields
917 .iter()
918 .enumerate()
919 .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
920 .collect();
921 let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
922 all.extend(inits);
923 self.line(&format!(
924 "proc {}*({}): {} = {}({})",
925 def.ctor_ident(&v.name),
926 args.join(", "),
927 name,
928 name,
929 all.join(", ")
930 ));
931 }
932 self.blank();
933
934 self.line(&format!("proc rsDebug*(x: {name}): string ="));
935 self.indent += 1;
936 self.line("case x.kind");
937 for v in &def.variants {
938 if v.fields.is_empty() {
939 self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
940 } else {
941 let parts: Vec<String> = v
942 .fields
943 .iter()
944 .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
945 .collect();
946 self.line(&format!(
947 "of {}: \"{}(\" & {} & \")\"",
948 def.kind_ident(&v.name),
949 v.name,
950 parts.join(" & \", \" & ")
951 ));
952 }
953 }
954 self.indent -= 1;
955 self.blank();
956 }
957
958 /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
959 /// to the enum that declares it.
960 fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
961 let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
962 let last = segs.last()?.clone();
963 if segs.len() >= 2 {
964 if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
965 if def.get(&last).is_some() {
966 return Some((def.clone(), last));
967 }
968 }
969 }
970 // Unqualified: only unambiguous if exactly one enum declares it.
971 match self.variant_owner.get(&last) {
972 Some(owners) if owners.len() == 1 => {
973 let def = self.enums.get(&owners[0])?;
974 Some((def.clone(), last))
975 }
976 _ => None,
977 }
978 }
979
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago980 /// Lower `impl Display for T`'s `fn fmt` into a proc returning a string.
981 ///
982 /// Rust's `Formatter` is a sink that a `fmt` method writes into; the
983 /// observable result of `{}` is exactly the bytes written. So the method
984 /// becomes `proc rsDisplay(self: T): string` and every write through the
985 /// formatter produces that string. A `fmt` body that does anything else
986 /// with the formatter -- padding, precision, `debug_struct` -- is rejected,
987 /// because those affect the output and this model does not carry them.
988 /// The window an expression names, if it names one.
989 fn window_of(&self, e: &Expr) -> Option<Alias> {
990 match e {
991 Expr::Path(p) => match self.lookup_alias(&path_name(&p.path)) {
992 Some(a @ Alias::Window { .. }) => Some(a),
993 _ => None,
994 },
995 Expr::Reference(r) => self.window_of(&r.expr),
996 Expr::Paren(p) => self.window_of(&p.expr),
997 Expr::Unary(u) if matches!(u.op, UnOp::Deref(_)) => self.window_of(&u.expr),
998 _ => None,
999 }
1000 }
1001
1002 /// Whether an expression is the `Formatter` parameter of the formatting
1003 /// impl currently being lowered.
1004 fn is_fmt_param(&self, e: &Expr) -> bool {
1005 let Some(f) = &self.fmt_param else { return false };
1006 match e {
1007 Expr::Path(p) => path_name(&p.path) == *f,
1008 Expr::Reference(r) => self.is_fmt_param(&r.expr),
1009 Expr::Paren(p) => self.is_fmt_param(&p.expr),
1010 _ => false,
1011 }
1012 }
1013
1014 fn fmt_impl(
1015 &mut self,
1016 tr: &str,
1017 self_ty: &Nim,
1018 sig: &syn::Signature,
1019 body: &syn::Block,
1020 ) -> Result<(), String> {
1021 let proc_name = fmt_proc(tr);
1022 // The formatter is the parameter after `self`.
1023 let f = sig
1024 .inputs
1025 .iter()
1026 .filter_map(|a| match a {
1027 FnArg::Typed(t) => match &*t.pat {
1028 Pat::Ident(i) => Some(i.ident.to_string()),
1029 _ => None,
1030 },
1031 _ => None,
1032 })
1033 .next()
1034 .ok_or("`fn fmt` needs a `Formatter` parameter")?;
1035
1036 self.push_scope();
1037 self.bind("self", self_ty.clone());
1038 let saved = self.fmt_param.replace(f);
1039 let outer_ret = self.ret.replace(Nim::Prim("string".into()));
1040 let outer_target = self
1041 .target
1042 .replace(("result".to_string(), Some(Nim::Prim("string".into()))));
1043
1044 self.line(&format!(
1045 "proc {}*(self: {}): string =",
1046 proc_name,
1047 self_ty.render()
1048 ));
1049 self.indent += 1;
1050 let before = self.out.len();
1051 let want = Nim::Prim("string".into());
1052 let tail = self.block_body_at(body, Some(&want))?;
1053 self.emit_tail(tail);
1054 if self.out.len() == before {
1055 self.line("discard");
1056 }
1057 self.indent -= 1;
1058
1059 self.target = outer_target;
1060 self.ret = outer_ret;
1061 self.fmt_param = saved;
1062 self.pop_scope();
1063 self.blank();
1064 Ok(())
1065 }
1066
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1067 fn func(
1068 &mut self,
1069 sig: &syn::Signature,
1070 body: &syn::Block,
1071 recv: Option<Nim>,
1072 ) -> Result<(), String> {
1073 let name = sig.ident.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1074 self.func_named(&name.clone(), sig, body, recv)
1075 }
1076
1077 fn func_named(
1078 &mut self,
1079 name: &str,
1080 sig: &syn::Signature,
1081 body: &syn::Block,
1082 recv: Option<Nim>,
1083 ) -> Result<(), String> {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1084 let (ptys, ret) = self.signature(sig)?;
1085
1086 self.push_scope();
1087 let mut rendered: Vec<String> = Vec::new();
1088
1089 if let Some(self_ty) = recv {
1090 // `&mut self` and `mut self` both mean the body may mutate the
1091 // receiver; only the former is observable by the caller, and a Nim
1092 // `var` parameter is the faithful spelling of that.
1093 let mutable = matches!(
1094 sig.inputs.first(),
1095 Some(FnArg::Receiver(r))
1096 if matches!(&r.kind, syn::ReceiverKind::Reference(_, _, m) if m.is_some())
1097 );
1098 let t = if mutable { Nim::Var(Box::new(self_ty.clone())) } else { self_ty.clone() };
1099 rendered.push(format!("self: {}", t.render()));
1100 self.bind("self", self_ty);
1101 }
1102
1103 let typed: Vec<&syn::PatType> = sig
1104 .inputs
1105 .iter()
1106 .filter_map(|a| match a {
1107 FnArg::Typed(t) => Some(t),
1108 _ => None,
1109 })
1110 .collect();
1111 for (p, t) in typed.iter().zip(ptys.iter()) {
1112 let pname = match &*p.pat {
1113 Pat::Ident(i) => i.ident.to_string(),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1114 // `fn from(_: Error) -> ..` — the parameter is unused, but Nim
1115 // still needs a name for it.
1116 Pat::Wild(_) => format!("unused{}", rendered.len()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1117 _ => return Err("only plain identifier parameters are supported".into()),
1118 };
1119 rendered.push(format!("{}: {}", ident(&pname), t.render()));
1120 // Inside the body a `var T` parameter is used exactly like a `T`.
1121 self.bind(&pname, t.clone().owned());
1122 }
1123
1124 let head = if ret == Nim::Unit {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1125 format!("proc {}*({}) =", ident(name), rendered.join(", "))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1126 } else {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1127 format!("proc {}*({}): {} =", ident(name), rendered.join(", "), ret.render())
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1128 };
1129 self.line(&head);
1130 self.indent += 1;
1131 let outer_ret = self.ret.replace(ret.clone());
1132
1133 // A Rust fn's trailing expression is its return value. Naming Nim's
1134 // implicit `result` as the target makes that true whether the tail is
1135 // a plain expression or an `if`/`match` with statement arms.
1136 let outer_target = if ret == Nim::Unit {
1137 self.target.take()
1138 } else {
1139 self.target.replace(("result".to_string(), Some(ret.clone())))
1140 };
1141 let before = self.out.len();
1142 let tail = self.block_body_at(body, Some(&ret))?;
1143 self.target = outer_target;
1144 match tail {
1145 Some(v) if ret != Nim::Unit => {
1146 let code = v.code.clone();
1147 self.line(&format!("result = {code}"));
1148 }
1149 Some(v) => {
1150 // A trailing expression in a `()`-returning fn is evaluated for
1151 // its effect; Nim requires an explicit discard.
1152 let needs_discard = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1153 if needs_discard && !v.code.is_empty() {
1154 let code = v.code.clone();
1155 self.line(&format!("discard {code}"));
1156 }
1157 }
1158 None => {}
1159 }
1160 if self.out.len() == before {
1161 self.line("discard");
1162 }
1163
1164 self.indent -= 1;
1165 self.ret = outer_ret;
1166 self.pop_scope();
1167 self.blank();
1168 Ok(())
1169 }
1170
1171 // ---------------------------------------------------------- statements
1172
1173 /// Lower a block's statements. Returns the block's trailing expression,
1174 /// if it has one, *without* emitting it — the caller decides whether that
1175 /// value is a return value, a binding, or discarded.
1176 fn block_body(&mut self, b: &syn::Block) -> Result<Option<Val>, String> {
1177 self.block_body_at(b, None)
1178 }
1179
1180 fn block_body_at(
1181 &mut self,
1182 b: &syn::Block,
1183 expect: Option<&Nim>,
1184 ) -> Result<Option<Val>, String> {
1185 // An assignment target belongs to *this* block's trailing expression
1186 // only. A non-final `if` is a statement and must not assign anything.
1187 let target = self.target.take();
1188 let n = b.stmts.len();
1189 let mut tail = None;
1190 for (i, st) in b.stmts.iter().enumerate() {
1191 let last = i + 1 == n;
1192 match st {
1193 Stmt::Expr(e, None) if last && expressible(e) => {
1194 tail = Some(self.expr_at(e, expect)?)
1195 }
1196 Stmt::Expr(e, None) if last => {
1197 // A trailing `if`/`match` with statement arms, or a loop.
1198 // Lower it as statements; if this block's value is wanted,
1199 // each arm assigns it.
1200 match &target {
1201 Some((t, ty)) => {
1202 let (t, ty) = (t.clone(), ty.clone());
1203 self.assign_from(e, &t, ty.as_ref())?;
1204 }
1205 None => self.stmt(st)?,
1206 }
1207 }
1208 _ => self.stmt(st)?,
1209 }
1210 }
1211 self.target = target;
1212 Ok(tail)
1213 }
1214
1215 /// Lower a block in statement position (loop bodies, `if` arms).
1216 fn nested_block(&mut self, b: &syn::Block) -> Result<(), String> {
1217 self.push_scope();
1218 self.indent += 1;
1219 let before = self.out.len();
1220 let want = self.target.clone().and_then(|(_, t)| t);
1221 let tail = self.block_body_at(b, want.as_ref())?;
1222 self.emit_tail(tail);
1223 if self.out.len() == before {
1224 self.line("discard");
1225 }
1226 self.indent -= 1;
1227 self.pop_scope();
1228 Ok(())
1229 }
1230
1231 fn stmt(&mut self, s: &Stmt) -> Result<(), String> {
1232 match s {
1233 Stmt::Local(l) => self.local(l),
1234 Stmt::Expr(e, _) => {
1235 let v = self.expr_stmt(e)?;
1236 if let Some(v) = v {
1237 // A bare expression with a value must be discarded in Nim.
1238 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1239 let code = v.code.clone();
1240 if needs {
1241 self.line(&format!("discard {code}"));
1242 } else if !code.is_empty() {
1243 self.line(&code);
1244 }
1245 }
1246 Ok(())
1247 }
1248 Stmt::Item(i) => self.item(i),
1249 Stmt::Macro(m) => {
1250 let line = self.macro_call(&m.mac)?;
1251 self.line(&line);
1252 Ok(())
1253 }
1254 }
1255 }
1256
1257 fn local(&mut self, l: &Local) -> Result<(), String> {
1258 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
1259 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
1260 Pat::Type(t) => match &*t.pat {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1261 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 8h ago1262 _ => return Err("only `let <ident>` bindings are supported".into()),
1263 },
1264 Pat::Wild(_) => ("_".into(), false, None),
1265 _ => return Err("destructuring `let` is not implemented yet".into()),
1266 };
1267
1268 let Some(init) = &l.init else {
1269 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
1270 // not. Rust's own rules make reading it before assignment illegal,
1271 // so the two agree on every program rustc accepts.
1272 let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
1273 let t = t.owned();
1274 self.line(&format!("var {}: {}", ident(&name), t.render()));
1275 self.bind(&name, t);
1276 return Ok(());
1277 };
1278 if init.diverge.is_some() {
1279 return Err("`let ... else` is not implemented yet".into());
1280 }
1281
1282 if !expressible(&init.expr) && name != "_" {
1283 // The initialiser is an `if`/`match` whose arms are statements.
1284 // Declare first, then let each arm assign into the binding.
1285 let t = ann
1286 .clone()
1287 .ok_or_else(|| {
1288 format!(
1289 "`let {name} = match/if ...` needs a type annotation: \
1290 its arms are statements, so the binding must be \
1291 declared before they run"
1292 )
1293 })?
1294 .owned();
1295 self.line(&format!("var {}: {}", ident(&name), t.render()));
1296 self.bind(&name, t.clone());
1297 let target = ident(&name);
1298 return self.assign_from(&init.expr, &target, Some(&t));
1299 }
1300
1301 let v = self.expr_at(&init.expr, ann.as_ref())?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1302 if let Some(w) = v.window.clone() {
1303 // `let dst = dst.get_mut(..n).ok_or(..)?;` -- the binding names a
1304 // view into the caller's buffer. Copying it into a `seq` would
1305 // still print the right bytes but would stop writes reaching the
1306 // caller, so it is bound as an alias.
1307 if v.guard.is_some() && v.guard_err.is_some() {
1308 return Err(format!(
1309 "`let {name} = ...get(..)` keeps an `Option` of a slice view, \
1310 which Nim cannot represent; apply `?` or `unwrap()` to it \
1311 in the same expression"
1312 ));
1313 }
1314 self.bind_alias(&name, w);
1315 return Ok(());
1316 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1317 let t = match (ann, &v.ty) {
1318 (Some(a), _) => a.owned(),
1319 (None, Some(t)) => t.clone().owned(),
1320 (None, None) => {
1321 return Err(format!(
1322 "cannot infer the type of `let {name}`; annotate it — \
1323 guessing here would change integer width, and with it the \
1324 meaning of any arithmetic on `{name}`"
1325 ))
1326 }
1327 };
1328
1329 if name == "_" {
1330 let code = v.code.clone();
1331 self.line(&format!("discard {code}"));
1332 return Ok(());
1333 }
1334 // Rust's immutable `let` is Nim's `let`; `let mut` is `var`. Shadowing
1335 // works in both, so a re-`let` of the same name needs no rename.
1336 let kw = if mutable { "var" } else { "let" };
1337 let line = format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code);
1338 self.line(&line);
1339 self.bind(&name, t);
1340 Ok(())
1341 }
1342
1343 /// Expressions that are statements in Rust and statements in Nim too
1344 /// (control flow). Returns `None` when it emitted lines itself.
1345 fn expr_stmt(&mut self, e: &Expr) -> Result<Option<Val>, String> {
1346 match e {
1347 Expr::If(_) => {
1348 self.if_stmt(e)?;
1349 Ok(None)
1350 }
1351 Expr::While(w) => {
1352 if w.label.is_some() {
1353 return Err("loop labels are not implemented yet".into());
1354 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1355 self.in_loop_cond = true;
1356 let c = self.expr(&w.cond);
1357 self.in_loop_cond = false;
1358 let c = c?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1359 self.line(&format!("while {}:", c.code));
1360 let saved = self.target.take();
1361 self.nested_block(&w.body)?;
1362 self.target = saved;
1363 Ok(None)
1364 }
1365 Expr::Loop(l) => {
1366 if l.label.is_some() {
1367 return Err("loop labels are not implemented yet".into());
1368 }
1369 self.line("while true:");
1370 let saved = self.target.take();
1371 self.nested_block(&l.body)?;
1372 self.target = saved;
1373 Ok(None)
1374 }
1375 Expr::ForLoop(f) => {
1376 self.for_loop(f)?;
1377 Ok(None)
1378 }
1379 Expr::Block(b) => {
1380 if b.label.is_some() {
1381 return Err("block labels are not implemented yet".into());
1382 }
1383 self.line("block:");
1384 self.nested_block(&b.block)?;
1385 Ok(None)
1386 }
1387 Expr::Match(_) => {
1388 self.match_stmt(e)?;
1389 Ok(None)
1390 }
1391 Expr::Return(r) => {
1392 match &r.expr {
1393 Some(e) => {
1394 let want = self.ret.clone();
1395 let v = self.expr_at(e, want.as_ref())?;
1396 self.line(&format!("return {}", v.code));
1397 }
1398 None => self.line("return"),
1399 }
1400 Ok(None)
1401 }
1402 Expr::Break(b) => {
1403 if b.expr.is_some() || b.label.is_some() {
1404 return Err("`break` with a value or a label is not implemented yet".into());
1405 }
1406 self.line("break");
1407 Ok(None)
1408 }
1409 Expr::Continue(c) => {
1410 if c.label.is_some() {
1411 return Err("labelled `continue` is not implemented yet".into());
1412 }
1413 self.line("continue");
1414 Ok(None)
1415 }
1416 Expr::Assign(a) => {
1417 let lhs = self.expr(&a.left)?;
1418 if !expressible(&a.right) {
1419 let target = lhs.code.clone();
1420 return self.assign_from(&a.right, &target, lhs.ty.as_ref()).map(|_| None);
1421 }
1422 let rhs = self.expr_at(&a.right, lhs.ty.as_ref())?;
1423 self.line(&format!("{} = {}", lhs.code, rhs.code));
1424 Ok(None)
1425 }
1426 Expr::Binary(b) if is_compound(&b.op) => {
1427 let lhs = self.expr(&b.left)?;
1428 // `i += 1` must widen the literal to `i`'s type, not to the
1429 // i32 an unconstrained Rust literal would default to.
1430 let rhs = self.expr_at(&b.right, lhs.ty.as_ref())?;
1431 let op = self.bin_op(&b.op, &lhs, &rhs)?;
1432 // Nim has no `shl=` etc., and `+=` on a `let` is illegal in
1433 // both languages, so the expanded form is always correct.
1434 self.line(&format!("{} = {} {} {}", lhs.code, lhs.code, op, rhs.code));
1435 Ok(None)
1436 }
1437 Expr::Macro(m) => {
1438 let line = self.macro_call(&m.mac)?;
1439 self.line(&line);
1440 Ok(None)
1441 }
1442 _ => Ok(Some(self.expr(e)?)),
1443 }
1444 }
1445
1446 /// Lower `e` in statement position, assigning each arm's value to
1447 /// `target`. This is how Rust's expression-oriented `if`/`match` survive
1448 /// the trip when their arms are too big for a Nim `if`-expression.
1449 fn assign_from(
1450 &mut self,
1451 e: &Expr,
1452 target: &str,
1453 expect: Option<&Nim>,
1454 ) -> Result<(), String> {
1455 let saved = self.target.replace((target.to_string(), expect.cloned()));
1456 let r = match e {
1457 Expr::If(_) => self.if_stmt(e),
1458 Expr::Match(_) => self.match_stmt(e),
1459 other => {
1460 let v = self.expr_at(other, expect)?;
1461 self.line(&format!("{} = {}", target, v.code));
1462 Ok(())
1463 }
1464 };
1465 self.target = saved;
1466 r
1467 }
1468
1469 /// Emit a block's value into the active assignment target, if there is
1470 /// one, or discard it if there is not.
1471 fn emit_tail(&mut self, v: Option<Val>) {
1472 let Some(v) = v else { return };
1473 match self.target.clone() {
1474 Some((t, _)) => {
1475 let code = v.code.clone();
1476 self.line(&format!("{t} = {code}"));
1477 }
1478 None => {
1479 let needs = v.ty.as_ref().is_none_or(|t| *t != Nim::Unit);
1480 let code = v.code.clone();
1481 if needs {
1482 self.line(&format!("discard {code}"));
1483 } else if !code.is_empty() {
1484 self.line(&code);
1485 }
1486 }
1487 }
1488 }
1489
1490 fn if_stmt(&mut self, e: &Expr) -> Result<(), String> {
1491 let Expr::If(i) = e else { unreachable!() };
1492 if let Expr::Let(_) = &*i.cond {
1493 return Err("`if let` is not implemented yet".into());
1494 }
1495 let c = self.expr(&i.cond)?;
1496 self.line(&format!("if {}:", c.code));
1497 self.nested_block(&i.then_branch)?;
1498 match &i.else_branch {
1499 None => {}
1500 Some((_, els)) => match &**els {
1501 Expr::If(_) => {
1502 // Nim needs `elif`; splice the nested `if` in as one.
1503 let mark = self.out.len();
1504 self.if_stmt(els)?;
1505 let tail = self.out.split_off(mark);
1506 let indent = " ".repeat(self.indent);
1507 self.out.push_str(&tail.replacen(&format!("{indent}if "), &format!("{indent}elif "), 1));
1508 }
1509 Expr::Block(b) => {
1510 self.line("else:");
1511 self.nested_block(&b.block)?;
1512 }
1513 _ => return Err("unsupported `else` form".into()),
1514 },
1515 }
1516 Ok(())
1517 }
1518
1519 fn for_loop(&mut self, f: &syn::ExprForLoop) -> Result<(), String> {
1520 if f.label.is_some() {
1521 return Err("loop labels are not implemented yet".into());
1522 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1523 let it = self.resolve_iter(&f.expr)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1524
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1525 // One index loop drives the whole chain. Rust's adaptors are lazy and
1526 // compose; resolving them to an index and binding each name to an
1527 // lvalue reproduces that without materialising anything.
1528 let i = self.fresh("Idx");
1529 self.line(&format!("for {} in 0 ..< int({}):", i, it.len()));
1530 self.indent += 1;
1531 self.push_scope();
1532 let before = self.out.len();
1533
1534 self.bind_pattern(&f.pat, &it, &i)?;
1535
1536 let saved = self.target.take();
1537 if let Some(v) = self.block_body(&f.body)? {
1538 let code = v.code.clone();
1539 self.line(&format!("discard {code}"));
1540 }
1541 self.target = saved;
1542 if self.out.len() == before {
1543 self.line("discard");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1544 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1545 self.pop_scope();
1546 self.indent -= 1;
1547 Ok(())
1548 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1549
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1550 /// Resolve a chain of iterator adaptors into a single `Iter`.
1551 ///
1552 /// Only adaptors with an exact index-loop equivalent are accepted. `map`,
1553 /// `filter`, `take_while` and friends are rejected rather than partially
1554 /// honoured: silently dropping an adaptor would change which elements the
1555 /// loop visits.
1556 fn resolve_iter(&mut self, e: &Expr) -> Result<Iter, String> {
1557 match e {
1558 Expr::Reference(r) => self.resolve_iter(&r.expr),
1559 Expr::Paren(p) => self.resolve_iter(&p.expr),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1560 Expr::Range(r) => {
1561 let lo = match &r.start {
1562 Some(e) => self.expr(e)?,
1563 None => return Err("a `for` over `..n` needs a start bound".into()),
1564 };
1565 let hi = match &r.end {
1566 Some(e) => self.expr(e)?,
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1567 None => {
1568 return Err("a `for` over an unbounded range would not terminate".into())
1569 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1570 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1571 let ty = lo.ty.clone().or(hi.ty.clone());
1572 Ok(Iter::Range {
1573 lo: lo.code,
1574 hi: hi.code,
1575 closed: matches!(r.limits, syn::RangeLimits::Closed(_)),
1576 ty,
1577 })
1578 }
1579 Expr::MethodCall(m) => {
1580 let name = m.method.to_string();
1581 match name.as_str() {
1582 "iter" | "into_iter" | "iter_mut" if m.args.is_empty() => {
1583 let mut it = self.resolve_iter(&m.receiver)?;
1584 if name == "iter_mut" {
1585 if let Iter::Elems { mutable, .. } = &mut it {
1586 *mutable = true;
1587 }
1588 }
1589 Ok(it)
1590 }
1591 "enumerate" if m.args.is_empty() => {
1592 Ok(Iter::Enumerate(Box::new(self.resolve_iter(&m.receiver)?)))
1593 }
1594 "zip" if m.args.len() == 1 => {
1595 let a = self.resolve_iter(&m.receiver)?;
1596 let b = self.resolve_iter(&m.args[0])?;
1597 Ok(Iter::Zip(Box::new(a), Box::new(b)))
1598 }
1599 "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => {
1600 let recv = self.expr(&m.receiver)?;
1601 let k = self.expr(&m.args[0])?;
1602 Ok(Iter::Chunks {
1603 code: recv.code,
1604 k: k.code,
1605 elem: elem_of(&recv.ty),
1606 mutable: name.ends_with("_mut"),
1607 })
1608 }
1609 "windows" if m.args.len() == 1 => {
1610 let recv = self.expr(&m.receiver)?;
1611 let k = self.expr(&m.args[0])?;
1612 Ok(Iter::Windows {
1613 code: recv.code,
1614 k: k.code,
1615 elem: elem_of(&recv.ty),
1616 })
1617 }
1618 other => Err(format!(
1619 "iterator adaptor `.{other}()` is not implemented; it has \
1620 no index-loop equivalent here, and dropping it would \
1621 change which elements the loop visits"
1622 )),
1623 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1624 }
1625 other => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1626 // A `for` binding that is itself a window iterates that window,
1627 // not the whole container it points into.
1628 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) {
1629 return Ok(Iter::Elems {
1630 code,
1631 off,
1632 len,
1633 elem,
1634 mutable: false,
1635 });
1636 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1637 let v = self.expr(other)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1638 Ok(Iter::Elems {
1639 len: format!("{}.len", v.code),
1640 elem: elem_of(&v.ty),
1641 code: v.code,
1642 off: "0".into(),
1643 mutable: false,
1644 })
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1645 }
1646 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago1647 }
1648
1649 /// Bind a `for` pattern against a resolved iterator at index `i`.
1650 fn bind_pattern(&mut self, p: &Pat, it: &Iter, i: &str) -> Result<(), String> {
1651 match (p, it) {
1652 (Pat::Tuple(t), Iter::Zip(a, b)) if t.elems.len() == 2 => {
1653 self.bind_pattern(&t.elems[0], a, i)?;
1654 self.bind_pattern(&t.elems[1], b, i)
1655 }
1656 (Pat::Tuple(t), Iter::Enumerate(inner)) if t.elems.len() == 2 => {
1657 if let Pat::Ident(id) = &t.elems[0] {
1658 let n = id.ident.to_string();
1659 // Rust's `enumerate` counts in `usize`.
1660 self.line(&format!("let {}: uint = uint({})", ident(&n), i));
1661 self.bind(&n, Nim::Prim("uint".into()));
1662 }
1663 self.bind_pattern(&t.elems[1], inner, i)
1664 }
1665 (_, Iter::Zip(..) | Iter::Enumerate(..)) => Err(
1666 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
1667 ),
1668 (Pat::Wild(_), _) => Ok(()),
1669 (Pat::Ident(id), _) => {
1670 let name = id.ident.to_string();
1671 match it {
1672 Iter::Range { lo, ty, .. } => {
1673 let t = ty.clone().unwrap_or(Nim::Prim("int".into()));
1674 // The loop counts from zero; the range's own start is
1675 // added back so the binding has Rust's value and type.
1676 self.line(&format!(
1677 "let {}: {} = {}({}) + {}",
1678 ident(&name),
1679 t.render(),
1680 t.render(),
1681 i,
1682 lo
1683 ));
1684 self.bind(&name, t);
1685 Ok(())
1686 }
1687 Iter::Elems { code, off, elem, mutable, .. } => {
1688 let access = if off == "0" {
1689 format!("{}[{}]", code, i)
1690 } else {
1691 format!("{}[{} + {}]", code, off, i)
1692 };
1693 if *mutable {
1694 // An alias, not a copy: assigning through the
1695 // binding must reach the original element.
1696 self.bind_alias(
1697 &name,
1698 Alias::Value { code: access, ty: elem.clone() },
1699 );
1700 } else {
1701 let t = elem
1702 .clone()
1703 .ok_or("cannot infer the element type of this `for`")?;
1704 self.line(&format!(
1705 "let {}: {} = {}",
1706 ident(&name),
1707 t.render(),
1708 access
1709 ));
1710 self.bind(&name, t);
1711 }
1712 Ok(())
1713 }
1714 Iter::Chunks { code, k, elem, .. } => {
1715 self.bind_alias(
1716 &name,
1717 Alias::Window {
1718 code: code.clone(),
1719 off: format!("({} * int({}))", i, k),
1720 len: format!("int({})", k),
1721 elem: elem.clone(),
1722 },
1723 );
1724 Ok(())
1725 }
1726 Iter::Windows { code, k, elem } => {
1727 self.bind_alias(
1728 &name,
1729 Alias::Window {
1730 code: code.clone(),
1731 off: i.to_string(),
1732 len: format!("int({})", k),
1733 elem: elem.clone(),
1734 },
1735 );
1736 Ok(())
1737 }
1738 // Handled above: a zip or enumerate needs a tuple pattern,
1739 // and binding one name to the pair is not supported.
1740 Iter::Zip(..) | Iter::Enumerate(..) => unreachable!(),
1741 }
1742 }
1743 _ => Err("unsupported `for` pattern".into()),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1744 }
1745 }
1746
1747 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
1748 let Expr::Match(m) = e else { unreachable!() };
1749 let scrut = self.expr(&m.expr)?;
1750 let t = scrut
1751 .ty
1752 .clone()
1753 .ok_or("cannot infer the type of a `match` scrutinee")?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1754 let name = self.fresh("Match");
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1755 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1756
1757 // A `match` whose arms neither bind nor guard is a Nim `case`, which
1758 // is exhaustiveness-checked the way Rust's is. Anything richer becomes
1759 // an if/elif chain, because Nim's `case` cannot destructure.
1760 let plain = m.arms.iter().all(|a| {
1761 !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
1762 });
1763 if plain {
1764 self.match_case(m, &name, &t)
1765 } else {
1766 self.match_chain(m, &name, &t)
1767 }
1768 }
1769
1770 fn match_case(
1771 &mut self,
1772 m: &syn::ExprMatch,
1773 name: &str,
1774 t: &Nim,
1775 ) -> Result<(), String> {
1776 // A variant object is discriminated by its `kind` field.
1777 let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
1778 self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1779
1780 let mut saw_wild = false;
1781 for arm in &m.arms {
1782 match &arm.pat {
1783 Pat::Wild(_) => {
1784 saw_wild = true;
1785 self.line("else:");
1786 }
1787 p => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1788 let labels = self.pat_labels(p, Some(t))?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1789 self.line(&format!("of {}:", labels.join(", ")));
1790 }
1791 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1792 self.arm_body(&arm.body)?;
1793 }
1794 if !saw_wild && !self.case_is_total(t, m) {
1795 // Rust checked exhaustiveness already, but Nim cannot always see
1796 // it -- an integer `case` needs every value covered -- so make the
1797 // unreachable arm explicit rather than leave a compile error.
1798 self.line("else:");
1799 self.line(" rsPanic(\"unreachable match arm\")");
1800 }
1801 Ok(())
1802 }
1803
1804 /// Whether a Nim `case` over this type is already total, in which case
1805 /// adding an `else` would be a compile error rather than a safety net.
1806 fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
1807 let Nim::Named(n, _) = t else { return false };
1808 let Some(def) = self.enums.get(n) else { return false };
1809 def.variants.len() == m.arms.len()
1810 }
1811
1812 /// The if/elif form, for arms that bind or destructure.
1813 fn match_chain(
1814 &mut self,
1815 m: &syn::ExprMatch,
1816 name: &str,
1817 t: &Nim,
1818 ) -> Result<(), String> {
1819 let mut first = true;
1820 let mut closed = false;
1821 for arm in &m.arms {
1822 let (pat, guard) = match &arm.pat {
1823 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
1824 p => (p, None),
1825 };
1826 if guard.is_some() && binds(pat) {
1827 return Err("a `match` guard on a binding pattern is not \
1828 implemented yet"
1829 .into());
1830 }
1831 let test = self.pat_test(pat, name, t)?;
1832 let test = match (test, guard) {
1833 (Some(t), Some(g)) => {
1834 let g = self.expr(g)?;
1835 Some(format!("({}) and ({})", t, g.code))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1836 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1837 (None, Some(g)) => Some(self.expr(g)?.code),
1838 (t, None) => t,
1839 };
1840 match test {
1841 Some(test) => {
1842 self.line(&format!(
1843 "{} {}:",
1844 if first { "if" } else { "elif" },
1845 test
1846 ));
1847 first = false;
1848 }
1849 None => {
1850 // An irrefutable pattern: everything left falls here.
1851 if first {
1852 self.line("block:");
1853 } else {
1854 self.line("else:");
1855 }
1856 closed = true;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1857 }
1858 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1859 self.indent += 1;
1860 self.push_scope();
1861 let before = self.out.len();
1862 self.pat_bind(pat, name, t)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1863 self.indent -= 1;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1864 self.arm_body_at(&arm.body, before)?;
1865 self.pop_scope();
1866 if closed {
1867 break;
1868 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1869 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1870 if !closed {
1871 // Rust proved this unreachable; Nim cannot see that, and leaving
1872 // the chain open would silently fall through instead.
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago1873 self.line("else:");
1874 self.line(" rsPanic(\"unreachable match arm\")");
1875 }
1876 Ok(())
1877 }
1878
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago1879 /// The condition that selects this arm, or `None` if it always matches.
1880 fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
1881 Ok(match p {
1882 Pat::Wild(_) => None,
1883 Pat::Ident(i) if i.subpat.is_none() => None,
1884 Pat::Or(o) => {
1885 let mut parts = Vec::new();
1886 for c in &o.cases {
1887 match self.pat_test(c, name, t)? {
1888 Some(x) => parts.push(x),
1889 None => return Ok(None),
1890 }
1891 }
1892 Some(format!("({})", parts.join(" or ")))
1893 }
1894 Pat::Lit(_) | Pat::Range(_) => {
1895 let labels = self.pat_labels(p, Some(t))?;
1896 Some(match p {
1897 Pat::Range(_) => format!("({} in {})", name, labels[0]),
1898 _ => format!("({} == {})", name, labels[0]),
1899 })
1900 }
1901 Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
1902 Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
1903 Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
1904 Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
1905 Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
1906 _ => return Err("unsupported `match` pattern".into()),
1907 })
1908 }
1909
1910 /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
1911 fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
1912 let last = path_name(path);
1913 match last.as_str() {
1914 "Ok" => return Ok(format!("{name}.ok")),
1915 "Err" => return Ok(format!("(not {name}.ok)")),
1916 "Some" => return Ok(format!("{name}.has")),
1917 "None" => return Ok(format!("(not {name}.has)")),
1918 _ => {}
1919 }
1920 let Some((def, v)) = self.resolve_variant(path) else {
1921 return Err(format!(
1922 "`{last}` in a pattern is not a known enum variant; if it names \
1923 an enum declared in another module, that is not implemented yet"
1924 ));
1925 };
1926 if let Nim::Named(n, _) = t {
1927 if *n != def.name {
1928 return Err(format!(
1929 "pattern `{}::{}` does not match the scrutinee type `{}`",
1930 def.name, v, n
1931 ));
1932 }
1933 }
1934 Ok(if def.simple {
1935 format!("({} == {}.{})", name, ident(&def.name), ident(&v))
1936 } else {
1937 format!("({}.kind == {})", name, def.kind_ident(&v))
1938 })
1939 }
1940
1941 /// Emit the `let`s that a pattern's bindings introduce.
1942 fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
1943 match p {
1944 Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
1945 Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
1946 Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
1947 Pat::Ident(i) if i.subpat.is_none() => {
1948 let b = i.ident.to_string();
1949 self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
1950 self.bind(&b, t.clone());
1951 Ok(())
1952 }
1953 Pat::TupleStruct(ts) => {
1954 let fields = self.variant_fields(&ts.path, t)?;
1955 for (i, sub) in ts.elems.iter().enumerate() {
1956 let Some((fname, fty)) = fields.get(i) else {
1957 return Err(format!(
1958 "pattern binds {} field(s) but the variant has {}",
1959 ts.elems.len(),
1960 fields.len()
1961 ));
1962 };
1963 let access = format!("{}.{}", name, ident(fname));
1964 self.pat_bind(sub, &access, fty)?;
1965 }
1966 Ok(())
1967 }
1968 Pat::Struct(st) => {
1969 let fields = self.variant_fields(&st.path, t)?;
1970 for f in &st.fields {
1971 let syn::Member::Named(m) = &f.member else {
1972 return Err("unsupported struct pattern field".into());
1973 };
1974 let m = m.to_string();
1975 let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
1976 return Err(format!("unknown field `{m}` in pattern"));
1977 };
1978 let access = format!("{}.{}", name, ident(fname));
1979 self.pat_bind(&f.pat, &access, fty)?;
1980 }
1981 Ok(())
1982 }
1983 _ => Err("unsupported `match` pattern".into()),
1984 }
1985 }
1986
1987 /// The payload fields a variant pattern destructures.
1988 fn variant_fields(
1989 &self,
1990 path: &syn::Path,
1991 t: &Nim,
1992 ) -> Result<Vec<(String, Nim)>, String> {
1993 let last = path_name(path);
1994 // `Ok`/`Err`/`Some` read the prelude's own field names.
1995 if let Nim::Named(n, a) = t {
1996 match (n.as_str(), last.as_str()) {
1997 ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
1998 ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
1999 ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
2000 _ => {}
2001 }
2002 }
2003 let Some((def, v)) = self.resolve_variant(path) else {
2004 return Err(format!("`{last}` is not a known enum variant"));
2005 };
2006 Ok(def.get(&v).map(|v| v.fields.clone()).unwrap_or_default())
2007 }
2008
2009 fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
2010 self.indent += 1;
2011 let before = self.out.len();
2012 self.indent -= 1;
2013 self.arm_body_at(body, before)
2014 }
2015
2016 fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
2017 match body {
2018 Expr::Block(b) => self.nested_block(&b.block)?,
2019 other => {
2020 self.indent += 1;
2021 // An arm's value is the `match`'s value, so it is typed by
2022 // whatever the `match` is being assigned to -- without which
2023 // an `Ok(..)` arm has no way to know its `Result<T, E>`.
2024 let want = self.target.clone().and_then(|(_, t)| t);
2025 let v = match (want, expressible(other)) {
2026 (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
2027 _ => self.expr_stmt(other)?,
2028 };
2029 self.emit_tail(v);
2030 self.indent -= 1;
2031 }
2032 }
2033 if self.out.len() == before {
2034 self.indent += 1;
2035 self.line("discard");
2036 self.indent -= 1;
2037 }
2038 Ok(())
2039 }
2040
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2041 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
2042 match p {
2043 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
2044 Pat::Or(o) => {
2045 let mut out = Vec::new();
2046 for p in &o.cases {
2047 out.extend(self.pat_labels(p, expect)?);
2048 }
2049 Ok(out)
2050 }
2051 Pat::Range(r) => {
2052 let lo = r.start.as_ref().ok_or("open-ended range pattern")?;
2053 let hi = r.end.as_ref().ok_or("open-ended range pattern")?;
2054 let (lo, hi) = (self.expr_at(lo, expect)?, self.expr_at(hi, expect)?);
2055 let op = match r.limits {
2056 syn::RangeLimits::HalfOpen(_) => "..<",
2057 syn::RangeLimits::Closed(_) => "..",
2058 };
2059 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
2060 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2061 Pat::Path(pp) => {
2062 if let Some((def, v)) = self.resolve_variant(&pp.path) {
2063 return Ok(vec![if def.simple {
2064 format!("{}.{}", ident(&def.name), ident(&v))
2065 } else {
2066 def.kind_ident(&v)
2067 }]);
2068 }
2069 Ok(vec![ident(&path_name(&pp.path))])
2070 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2071 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2072 alternatives, enum variants and `_` are implemented"
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2073 .into()),
2074 }
2075 }
2076
2077 // --------------------------------------------------------- expressions
2078
2079 fn expr(&mut self, e: &Expr) -> Result<Val, String> {
2080 self.expr_at(e, None)
2081 }
2082
2083 /// Lower `e`, with the type the surrounding code expects of it.
2084 ///
2085 /// Rust infers an unsuffixed integer literal's type from its context and
2086 /// falls back to `i32`; Nim falls back to 64-bit `int`. Carrying the
2087 /// expected type down to the literal is what makes `let x: u8 = 255` and
2088 /// `x.wrapping_add(100)` mean the same thing on both sides. Without it the
2089 /// widths silently diverge, which is exactly the class of bug this
2090 /// project refuses to ship.
2091 fn expr_at(&mut self, e: &Expr, expect: Option<&Nim>) -> Result<Val, String> {
2092 match e {
2093 Expr::Lit(l) => self.lit_at(&l.lit, expect),
2094 Expr::Path(p) => {
2095 let name = path_name(&p.path);
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2096 if name == "None" {
2097 return Ok(Val::new(self.none_of(expect), expect.cloned()));
2098 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2099 // A unit struct used as a value: `fmt::Error`, or a `struct S;`
2100 // declared here. In Nim that is a constructor call.
2101 if p.path.segments.len() > 1 {
2102 let ty = syn::Type::Path(syn::TypePath { attrs: Vec::new(), qself: None, path: p.path.clone() });
2103 if let Ok(Nim::Prim(n)) = ty::map(&ty) {
2104 if n == "FmtError" {
2105 return Ok(Val::new("FmtError()", Some(Nim::Prim(n))));
2106 }
2107 }
2108 }
2109 if self.structs.get(&name).is_some_and(|f| f.is_empty()) {
2110 return Ok(Val::new(
2111 format!("{}()", ident(&name)),
2112 Some(Nim::Named(name.clone(), vec![])),
2113 ));
2114 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2115 // A unit enum variant used as a value: `Error::InvalidLength`.
2116 if let Some((def, v)) = self.resolve_variant(&p.path) {
2117 let ty = Some(Nim::Named(def.name.clone(), vec![]));
2118 return Ok(if def.simple {
2119 Val::new(format!("{}.{}", ident(&def.name), ident(&v)), ty)
2120 } else {
2121 Val::new(format!("{}()", def.ctor_ident(&v)), ty)
2122 });
2123 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2124 // A `for` binding that stands for an element of the container
2125 // it came from: using it must read (and assigning through it
2126 // must write) that element, not a copy.
2127 if let Some(a) = self.lookup_alias(&name) {
2128 return Ok(match a {
2129 Alias::Value { code, ty } => Val::new(code, ty),
2130 // A window *is* a slice; as a value it is the view it
2131 // denotes, which is what Rust's `&[T]` means too.
2132 Alias::Window { code, off, len, elem } => Val::new(
2133 format!("{}.toOpenArray({}, {} + {} - 1)", code, off, off, len),
2134 elem.map(|e| Nim::OpenArray(Box::new(e))),
2135 ),
2136 });
2137 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2138 if let Some(t) = self.lookup(&name) {
2139 return Ok(Val::new(ident(&name), Some(t)));
2140 }
2141 // A top-level function used as a value, e.g. passed to a
2142 // parameter of `impl Fn(..)` type.
2143 if let Some(sig) = self.fns.get(&name) {
2144 let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone()));
2145 return Ok(Val::new(ident(&name), Some(t)));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2146 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2147 Ok(Val::new(ident(&name), None))
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2148 }
2149 Expr::Paren(p) => {
2150 let v = self.expr_at(&p.expr, expect)?;
2151 Ok(Val::new(format!("({})", v.code), v.ty))
2152 }
2153 Expr::Group(g) => self.expr_at(&g.expr, expect),
2154 // `&x` is a value in Nim; `&mut x` in an argument position binds to
2155 // a `var` parameter, which is also just `x` at the call site.
2156 Expr::Reference(r) => self.expr_at(&r.expr, expect),
2157 Expr::Unary(u) => self.unary(u, expect),
2158 Expr::Binary(b) => self.binary(b, expect),
2159 Expr::Cast(c) => self.cast(c),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2160 Expr::Index(i) if matches!(&*i.index, Expr::Range(_)) => {
2161 let Expr::Range(r) = &*i.index else { unreachable!() };
2162 let base = self.expr(&i.expr)?;
2163 let lo = match &r.start {
2164 Some(e) => format!("int({})", self.expr(e)?.code),
2165 None => "0".into(),
2166 };
2167 // Nim's `toOpenArray` takes an inclusive upper bound.
2168 let hi = match (&r.end, r.limits) {
2169 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2170 format!("int({}) - 1", self.expr(e)?.code)
2171 }
2172 (Some(e), syn::RangeLimits::Closed(_)) => {
2173 format!("int({})", self.expr(e)?.code)
2174 }
2175 (None, _) => format!("{}.len - 1", base.code),
2176 };
2177 let elem = elem_of(&base.ty)
2178 .ok_or("cannot infer the element type of this slice")?;
2179 Ok(Val::new(
2180 format!("{}.toOpenArray({}, {})", base.code, lo, hi),
2181 Some(Nim::OpenArray(Box::new(elem))),
2182 ))
2183 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2184 Expr::Index(i) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2185 if let Some(Alias::Window { code, off, elem, .. }) = self.window_of(&i.expr) {
2186 let idx = self.expr(&i.index)?;
2187 return Ok(Val::new(
2188 format!("{}[{} + int({})]", code, off, idx.code),
2189 elem,
2190 ));
2191 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2192 let base = self.expr(&i.expr)?;
2193 let idx = self.expr(&i.index)?;
2194 // Rust indexes with usize; Nim wants an `int`, and a `uint`
2195 // index is a type error there rather than a silent conversion.
2196 let idx_code = match &idx.ty {
2197 Some(t) if t.is_unsigned() => format!("int({})", idx.code),
2198 _ => idx.code.clone(),
2199 };
2200 let elem = match base.ty.clone() {
2201 Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) | Some(Nim::Array(_, t)) => Some(*t),
2202 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
2203 _ => None,
2204 };
2205 Ok(Val::new(format!("{}[{}]", base.code, idx_code), elem))
2206 }
2207 Expr::Field(f) => {
2208 let base = self.expr(&f.base)?;
2209 let name = match &f.member {
2210 syn::Member::Named(n) => n.to_string(),
2211 syn::Member::Unnamed(i) => format!("f{}", i.index),
2212 };
2213 let t = match &base.ty {
2214 Some(Nim::Named(s, _)) => self
2215 .structs
2216 .get(s)
2217 .and_then(|fs| fs.iter().find(|(f, _)| *f == name))
2218 .map(|(_, t)| t.clone()),
2219 _ => None,
2220 };
2221 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
2222 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2223 Expr::Try(t) => self.try_op(t),
2224 Expr::Call(c) => self.call(c, expect),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2225 Expr::MethodCall(m) => self.method(m, expect),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2226 Expr::Macro(m) => {
2227 let code = self.macro_call(&m.mac)?;
2228 Ok(Val::new(code, None))
2229 }
2230 Expr::Struct(s) => {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2231 if s.rest.is_some() {
2232 return Err("struct update syntax `..rest` is not implemented yet".into());
2233 }
2234 // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
2235 // which is constructed positionally in Nim.
2236 if let Some((def, v)) = self.resolve_variant(&s.path) {
2237 let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
2238 let mut args = vec![String::new(); fields.len()];
2239 for f in &s.fields {
2240 let syn::Member::Named(m) = &f.member else {
2241 return Err("unsupported enum variant field".into());
2242 };
2243 let want = format!("{}_{}", v, m);
2244 let i = fields
2245 .iter()
2246 .position(|(n, _)| *n == want)
2247 .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
2248 args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
2249 }
2250 if let Some(i) = args.iter().position(|a| a.is_empty()) {
2251 return Err(format!(
2252 "`{}::{}` is missing field `{}`",
2253 def.name, v, fields[i].0
2254 ));
2255 }
2256 return Ok(Val::new(
2257 format!("{}({})", def.ctor_ident(&v), args.join(", ")),
2258 Some(Nim::Named(def.name.clone(), vec![])),
2259 ));
2260 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2261 let name = path_name(&s.path);
2262 let mut parts = Vec::new();
2263 for f in &s.fields {
2264 let fname = match &f.member {
2265 syn::Member::Named(n) => n.to_string(),
2266 syn::Member::Unnamed(i) => format!("f{}", i.index),
2267 };
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2268 let want = self
2269 .structs
2270 .get(&name)
2271 .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
2272 .map(|(_, t)| t.clone());
2273 let v = self.expr_at(&f.expr, want.as_ref())?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2274 parts.push(format!("{}: {}", ident(&fname), v.code));
2275 }
2276 Ok(Val::new(
2277 format!("{}({})", ident(&name), parts.join(", ")),
2278 Some(Nim::Named(name, vec![])),
2279 ))
2280 }
2281 Expr::Array(a) => {
2282 let mut parts = Vec::new();
2283 let mut elem = match expect {
2284 Some(Nim::Array(_, t)) | Some(Nim::Seq(t)) | Some(Nim::OpenArray(t)) => {
2285 Some((**t).clone())
2286 }
2287 _ => None,
2288 };
2289 for e in &a.elems {
2290 let want = elem.clone();
2291 let v = self.expr_at(e, want.as_ref())?;
2292 elem = elem.or(v.ty.clone());
2293 parts.push(v.code);
2294 }
2295 let t = elem.map(|t| Nim::Array(a.elems.len(), Box::new(t)));
2296 Ok(Val::new(format!("[{}]", parts.join(", ")), t))
2297 }
2298 Expr::Repeat(r) => {
2299 let v = self.expr(&r.expr)?;
2300 let n = self.expr(&r.len)?;
2301 let t = v.ty.clone().map(|t| Nim::Seq(Box::new(t)));
2302 Ok(Val::new(format!("newSeqWith(int({}), {})", n.code, v.code), t))
2303 }
2304 Expr::Tuple(t) if t.elems.is_empty() => Ok(Val::new("", Some(Nim::Unit))),
2305 Expr::Tuple(t) => {
2306 let mut parts = Vec::new();
2307 let mut tys = Vec::new();
2308 for e in &t.elems {
2309 let v = self.expr(e)?;
2310 tys.push(v.ty.clone());
2311 parts.push(v.code);
2312 }
2313 let ty = tys
2314 .iter()
2315 .cloned()
2316 .collect::<Option<Vec<_>>>()
2317 .map(Nim::Tuple);
2318 Ok(Val::new(format!("({})", parts.join(", ")), ty))
2319 }
2320 // `if` and `match` are expressions in both languages, but only
2321 // when every arm is itself a single expression.
2322 Expr::If(i) => self.if_expr(i, expect),
2323 Expr::Block(b) if b.block.stmts.len() == 1 => {
2324 if let Some(Stmt::Expr(e, None)) = b.block.stmts.first() {
2325 self.expr_at(e, expect)
2326 } else {
2327 Err("block expression with statements in value position is not implemented yet".into())
2328 }
2329 }
2330 other => Err(format!(
2331 "unsupported expression in value position: {}",
2332 expr_kind(other)
2333 )),
2334 }
2335 }
2336
2337 fn if_expr(&mut self, i: &syn::ExprIf, expect: Option<&Nim>) -> Result<Val, String> {
2338 let (Some(then), Some((_, els))) = (single_expr(&i.then_branch), &i.else_branch) else {
2339 return Err(
2340 "an `if` used as a value must have an `else` and single-expression arms".into(),
2341 );
2342 };
2343 let c = self.expr(&i.cond)?;
2344 let t = self.expr_at(then, expect)?;
2345 let want = expect.cloned().or_else(|| t.ty.clone());
2346 let e = match &**els {
2347 Expr::Block(b) => match single_expr(&b.block) {
2348 Some(x) => self.expr_at(x, want.as_ref())?,
2349 None => return Err("an `if` used as a value must have single-expression arms".into()),
2350 },
2351 other => self.expr_at(other, want.as_ref())?,
2352 };
2353 let ty = t.ty.clone().or(e.ty.clone());
2354 Ok(Val::new(
2355 format!("(if {}: {} else: {})", c.code, t.code, e.code),
2356 ty,
2357 ))
2358 }
2359
2360 fn lit_at(&mut self, l: &Lit, expect: Option<&Nim>) -> Result<Val, String> {
2361 match l {
2362 Lit::Int(i) => {
2363 let suffix = i.suffix();
2364 if let Some(why) = ty::rejected(suffix) {
2365 return Err(format!("integer literal `{}`: {}", i, why));
2366 }
2367 let digits = i.base10_digits().to_string();
2368 // Rust's default for an unconstrained integer literal is i32.
2369 // Nim's is `int` (64-bit). Making the width explicit is what
2370 // keeps overflow behaviour the same on both sides.
2371 let t = if suffix.is_empty() {
2372 match expect {
2373 Some(t) if t.is_integer() => t.clone(),
2374 // Rust's fallback for an otherwise-unconstrained
2375 // integer literal.
2376 _ => Nim::Prim("int32".into()),
2377 }
2378 } else {
2379 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
2380 };
2381 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
2382 }
2383 Lit::Float(f) => {
2384 let t = match f.suffix() {
2385 "" => match expect {
2386 Some(Nim::Prim(p)) if p == "float32" => Nim::Prim("float32".into()),
2387 _ => Nim::Prim("float64".into()),
2388 },
2389 "f64" => Nim::Prim("float64".into()),
2390 "f32" => Nim::Prim("float32".into()),
2391 s => return Err(format!("unknown float suffix `{s}`")),
2392 };
2393 let d = f.base10_digits();
2394 let d = if d.contains('.') || d.contains('e') { d.to_string() } else { format!("{d}.0") };
2395 Ok(Val::new(d, Some(t)))
2396 }
2397 Lit::Bool(b) => Ok(Val::new(b.value.to_string(), Some(Nim::Prim("bool".into())))),
2398 Lit::Str(s) => Ok(Val::new(
2399 fmt::nim_str(&s.value()),
2400 Some(Nim::Prim("string".into())),
2401 )),
2402 Lit::Char(c) => Ok(Val::new(
2403 format!("Rune({})", c.value() as u32),
2404 Some(Nim::Prim("Rune".into())),
2405 )),
2406 Lit::Byte(b) => Ok(Val::new(
2407 format!("{}'u8", b.value()),
2408 Some(Nim::Prim("uint8".into())),
2409 )),
2410 Lit::ByteStr(b) => {
2411 let bytes: Vec<String> = b.value().iter().map(|x| format!("{x}'u8")).collect();
2412 Ok(Val::new(
2413 format!("@[{}]", bytes.join(", ")),
2414 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
2415 ))
2416 }
2417 other => Err(format!("unsupported literal: {other:?}")),
2418 }
2419 }
2420
2421 fn unary(&mut self, u: &syn::ExprUnary, expect: Option<&Nim>) -> Result<Val, String> {
2422 // `-128i8` is a literal in Rust, but `-(128'i8)` in Nim would overflow
2423 // the positive half of the range before the negation runs. Folding the
2424 // sign into the literal keeps `i8::MIN` and friends expressible.
2425 if let (UnOp::Neg(_), Expr::Lit(l)) = (&u.op, &*u.expr) {
2426 if matches!(l.lit, Lit::Int(_) | Lit::Float(_)) {
2427 let v = self.lit_at(&l.lit, expect)?;
2428 return Ok(Val::new(format!("-{}", v.code), v.ty));
2429 }
2430 }
2431 let v = self.expr_at(&u.expr, expect)?;
2432 match u.op {
2433 UnOp::Neg(_) => Ok(Val::new(format!("(-{})", v.code), v.ty)),
2434 // Rust's `!` is logical on bool and bitwise-complement on integers.
2435 // Nim spells those `not` and `not` as well, so one mapping covers
2436 // both — but only because Nim overloads `not` the same way.
2437 UnOp::Not(_) => Ok(Val::new(format!("(not {})", v.code), v.ty)),
2438 UnOp::Deref(_) => Ok(v),
2439 _ => Err("unsupported unary operator".into()),
2440 }
2441 }
2442
2443 fn binary(&mut self, b: &syn::ExprBinary, expect: Option<&Nim>) -> Result<Val, String> {
2444 // A comparison's operands are unrelated to the `bool` it produces, so
2445 // the outer expectation is not passed through to them.
2446 let down = match b.op {
2447 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
2448 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => None,
2449 _ => expect,
2450 };
2451 let mut l = self.expr_at(&b.left, down)?;
2452 // Rust unifies the two operand types; propagating whichever side is
2453 // known to the other reproduces that, and disagreement then surfaces
2454 // as a Nim type error rather than as a silent width change.
2455 let mut r = self.expr_at(&b.right, l.ty.as_ref().or(down))?;
2456 if l.ty.is_none() && r.ty.is_some() {
2457 l = self.expr_at(&b.left, r.ty.as_ref())?;
2458 }
2459 let r = std::mem::replace(&mut r, Val::untyped(""));
2460 let op = self.bin_op(&b.op, &l, &r)?;
2461 let ty = match b.op {
2462 BinOp::Eq(_) | BinOp::Ne(_) | BinOp::Lt(_) | BinOp::Le(_) | BinOp::Gt(_)
2463 | BinOp::Ge(_) | BinOp::And(_) | BinOp::Or(_) => Some(Nim::Prim("bool".into())),
2464 // Rust's shift takes its result type from the *left* operand, and
2465 // the right may be a different width entirely.
2466 BinOp::Shl(_) | BinOp::Shr(_) => l.ty.clone(),
2467 _ => l.ty.clone().or(r.ty.clone()),
2468 };
2469 Ok(Val::new(format!("({} {} {})", l.code, op, r.code), ty))
2470 }
2471
2472 fn bin_op(&mut self, op: &BinOp, l: &Val, r: &Val) -> Result<&'static str, String> {
2473 Ok(match op {
2474 BinOp::Add(_) | BinOp::AddAssign(_) => "+",
2475 BinOp::Sub(_) | BinOp::SubAssign(_) => "-",
2476 BinOp::Mul(_) | BinOp::MulAssign(_) => "*",
2477 BinOp::Div(_) | BinOp::DivAssign(_) => {
2478 // Nim spells integer division `div`. Both languages truncate
2479 // toward zero, so once the right operator is chosen the
2480 // semantics match, including for negative operands.
2481 let t = l.ty.clone().or(r.ty.clone()).ok_or(
2482 "cannot tell integer from float division here; annotate the operands",
2483 )?;
2484 if t.is_integer() { "div" } else { "/" }
2485 }
2486 BinOp::Rem(_) | BinOp::RemAssign(_) => {
2487 let t = l.ty.clone().or(r.ty.clone()).ok_or(
2488 "cannot tell integer from float remainder here; annotate the operands",
2489 )?;
2490 if t.is_integer() { "mod" } else { return Err("float `%` is not implemented yet".into()) }
2491 }
2492 BinOp::And(_) => "and",
2493 BinOp::Or(_) => "or",
2494 // Nim's `and`/`or`/`xor` are bitwise on integers and logical on
2495 // bools, exactly as Rust's `&`/`|`/`^` are.
2496 BinOp::BitAnd(_) | BinOp::BitAndAssign(_) => "and",
2497 BinOp::BitOr(_) | BinOp::BitOrAssign(_) => "or",
2498 BinOp::BitXor(_) | BinOp::BitXorAssign(_) => "xor",
2499 // Settled empirically: Nim's `shr` on a signed integer is
2500 // arithmetic, matching Rust. See DESIGN.md.
2501 BinOp::Shl(_) | BinOp::ShlAssign(_) => "shl",
2502 BinOp::Shr(_) | BinOp::ShrAssign(_) => "shr",
2503 BinOp::Eq(_) => "==",
2504 BinOp::Ne(_) => "!=",
2505 BinOp::Lt(_) => "<",
2506 BinOp::Le(_) => "<=",
2507 BinOp::Gt(_) => ">",
2508 BinOp::Ge(_) => ">=",
2509 other => return Err(format!("unsupported binary operator {other:?}")),
2510 })
2511 }
2512
2513 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
2514 let v = self.expr(&c.expr)?;
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2515 let to = self.map_ty(&c.ty)?;
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2516 let from = v.ty.clone().ok_or_else(|| {
2517 format!(
2518 "cannot lower `as {}`: the source type is unknown, and `as` \
2519 truncates, so the source width decides the result",
2520 to.render()
2521 )
2522 })?;
2523
2524 let code = match (&from, &to) {
2525 (f, t) if f.is_integer() && t.is_integer() => {
2526 // Rust's `as` between integers is a pure bit-width truncation
2527 // or sign-extension — never a range check. Nim's `T(x)` *does*
2528 // range-check and would raise where Rust wraps, so `cast` is
2529 // the only faithful spelling. Probed against both compilers.
2530 format!("cast[{}]({})", t.render(), v.code)
2531 }
2532 (f, Nim::Prim(p)) if f.is_integer() && (p == "float64" || p == "float32") => {
2533 format!("{}({})", p, v.code)
2534 }
2535 (Nim::Prim(b), t) if b == "bool" && t.is_integer() => {
2536 format!("{}(ord({}))", t.render(), v.code)
2537 }
2538 (Nim::Prim(r), t) if r == "Rune" && t.is_integer() => {
2539 format!("cast[{}](int32({}))", t.render(), v.code)
2540 }
2541 (f, Nim::Prim(r)) if f.is_integer() && r == "Rune" => {
2542 format!("Rune(int32({}))", v.code)
2543 }
2544 (Nim::Prim(a), Nim::Prim(b)) if a == b => v.code.clone(),
2545 (f, t) if matches!(f, Nim::Prim(p) if p.starts_with("float")) && t.is_integer() => {
2546 // Rust saturates float->int casts; Nim rounds and range-errors.
2547 // Not the same operation, so it is refused rather than mapped.
2548 return Err(format!(
2549 "`as {}` from a float: Rust saturates, Nim rounds and range-checks; \
2550 no faithful mapping is implemented",
2551 t.render()
2552 ));
2553 }
2554 (f, t) => {
2555 return Err(format!(
2556 "unsupported cast from `{}` to `{}`",
2557 f.render(),
2558 t.render()
2559 ))
2560 }
2561 };
2562 Ok(Val::new(code, Some(to)))
2563 }
2564
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2565 /// Rust's `?`: return early on the error branch, otherwise yield the value.
2566 ///
2567 /// The early return is statements, not an expression, so they are emitted
2568 /// ahead of the line being built. Every caller lowers its sub-expressions
2569 /// before emitting its own line, which is what makes that ordering hold.
2570 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
2571 if self.in_loop_cond {
2572 return Err("`?` in a loop condition is not implemented yet: the \
2573 early-return it expands to would be evaluated once, \
2574 before the loop, rather than on each iteration"
2575 .into());
2576 }
2577 let v = self.expr(&t.expr)?;
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2578 if let (Some(guard), Some(w)) = (v.guard.clone(), v.window.clone()) {
2579 // An `Option`/`Result` of a view: the check is emitted here and the
2580 // view itself survives as an alias, since it has no value form.
2581 let ret = self.ret.clone().ok_or("`?` outside a function with a return type")?;
2582 let err = v.guard_err.clone().ok_or(
2583 "`?` on a `get`/`get_mut` needs an `ok_or` to say what the error is",
2584 )?;
2585 let Nim::Named(n, ra) = &ret else {
2586 return Err(format!("`?` in a function returning `{}`", ret.render()));
2587 };
2588 if n != "Result" || ra.len() != 2 {
2589 return Err(format!("`?` in a function returning `{}`", ret.render()));
2590 }
2591 self.line(&format!("if not {}:", guard));
2592 self.line(&format!(
2593 " return rsErr[{}, {}]({})",
2594 ra[0].render(),
2595 ra[1].render(),
2596 err
2597 ));
2598 let mut out = Val::new(String::new(), None);
2599 out.window = Some(w);
2600 return Ok(out);
2601 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2602 let vt = v.ty.clone().ok_or(
2603 "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
2604 )?;
2605 let ret = self
2606 .ret
2607 .clone()
2608 .ok_or("`?` outside a function with a return type")?;
2609 let tmp = self.fresh("Try");
2610 self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
2611
2612 match (&vt, &ret) {
2613 (Nim::Named(a, ai), Nim::Named(b, bi))
2614 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
2615 {
2616 // Rust inserts a `From::from` on the error here. We only accept
2617 // the case where the error types already agree, rather than
2618 // silently dropping a conversion that might not be the identity.
2619 if ai[1] != bi[1] {
2620 return Err(format!(
2621 "`?` would need `From<{}> for {}`: an error-type conversion \
2622 is not implemented, and assuming it is the identity would \
2623 be a guess",
2624 ai[1].render(),
2625 bi[1].render()
2626 ));
2627 }
2628 self.line(&format!("if not {}.ok:", tmp));
2629 self.line(&format!(
2630 " return rsErr[{}, {}]({}.err)",
2631 bi[0].render(),
2632 bi[1].render(),
2633 tmp
2634 ));
2635 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
2636 }
2637 (Nim::Named(a, ai), Nim::Named(b, bi))
2638 if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
2639 {
2640 self.line(&format!("if not {}.has:", tmp));
2641 self.line(&format!(" return rsNone[{}]()", bi[0].render()));
2642 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
2643 }
2644 _ => Err(format!(
2645 "`?` on `{}` in a function returning `{}` is not a supported \
2646 combination",
2647 vt.render(),
2648 ret.render()
2649 )),
2650 }
2651 }
2652
2653 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 8h ago2654 let Expr::Path(p) = &*c.func else {
2655 return Err("only calls to named functions are supported".into());
2656 };
2657 let name = path_name(&p.path);
2658 let ptys: Vec<Nim> = self
2659 .fns
2660 .get(&name)
2661 .map(|s| s.params.clone())
2662 .unwrap_or_default();
2663 let mut args = Vec::new();
2664 for (i, a) in c.args.iter().enumerate() {
2665 let want = ptys.get(i).cloned();
2666 args.push(self.expr_at(a, want.as_ref())?);
2667 }
2668 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
2669
2670 // Constructors from the prelude.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2671 // `Ok`/`Err` must name the *whole* Result type, not just the half
2672 // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
2673 match name.as_str() {
2674 "Some" => {
2675 let inner = match expect {
2676 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
2677 _ => {
2678 return Err("`Some(..)` needs a known `Option<T>` type here; \
2679 annotate the binding or the return type"
2680 .into())
2681 }
2682 };
2683 return Ok(Val::new(
2684 format!("rsSome[{}]({})", inner, codes.join(", ")),
2685 expect.cloned(),
2686 ));
2687 }
2688 "Ok" | "Err" => {
2689 let (t, e) = match expect {
2690 Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
2691 (a[0].render(), a[1].render())
2692 }
2693 _ => {
2694 return Err(format!(
2695 "`{name}(..)` needs a known `Result<T, E>` type here; \
2696 annotate the binding or the return type"
2697 ))
2698 }
2699 };
2700 let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
2701 let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
2702 return Ok(Val::new(
2703 format!("{}[{}, {}]({})", ctor, t, e, arg),
2704 expect.cloned(),
2705 ));
2706 }
2707 _ => {}
2708 }
2709
2710 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
2711 if let Some((def, v)) = self.resolve_variant(&p.path) {
2712 return Ok(Val::new(
2713 format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
2714 Some(Nim::Named(def.name.clone(), vec![])),
2715 ));
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2716 }
2717
2718 // A bare path that names a primitive type is Rust's tuple-struct-like
2719 // conversion, e.g. `String::from(..)`; handled by the method path.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2720 // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
2721 // is invoked.
2722 if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
2723 return Ok(Val::new(
2724 format!("{}({})", ident(&name), codes.join(", ")),
2725 Some((*ret).clone()),
2726 ));
2727 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2728 let ret = self.fns.get(&name).map(|s| s.ret.clone());
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2729 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 8h ago2730 return Err(format!(
2731 "call to unknown function `{name}`; only functions defined in \
2732 this file and the supported standard-library subset can be lowered"
2733 ));
2734 }
2735 Ok(Val::new(
2736 format!("{}({})", ident(&name), codes.join(", ")),
2737 ret,
2738 ))
2739 }
2740
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2741 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 8h ago2742 let name = m.method.to_string();
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2743 if let Some(Alias::Window { len, .. }) = self.window_of(&m.receiver) {
2744 match name.as_str() {
2745 "len" => {
2746 return Ok(Val::new(format!("uint({})", len), Some(Nim::Prim("uint".into()))))
2747 }
2748 "is_empty" => {
2749 return Ok(Val::new(format!("({} == 0)", len), Some(Nim::Prim("bool".into()))))
2750 }
2751 other => {
2752 return Err(format!(
2753 "`.{other}()` on a slice window from `chunks_exact`/\
2754 `windows` is not implemented; only indexing and \
2755 `len()` are"
2756 ))
2757 }
2758 }
2759 }
2760 let recv = self.expr(&m.receiver)?;
2761 let rt0 = recv.ty.clone();
2762
2763// `s.get(a..b)` / `s.get_mut(a..b)`: an `Option<&[T]>`. Nim has no
2764 // way to put a view in an object, so instead of materialising an
2765 // Option the view and its validity condition travel together until
2766 // an `ok_or`/`?`/`unwrap` resolves them.
2767 if matches!(name.as_str(), "get" | "get_mut")
2768 && matches!(m.args.first(), Some(Expr::Range(_)))
2769 {
2770 let Some(Expr::Range(r)) = m.args.first() else { unreachable!() };
2771 let lo = match &r.start {
2772 Some(e) => format!("int({})", self.expr(e)?.code),
2773 None => "0".into(),
2774 };
2775 let len = match (&r.end, r.limits) {
2776 (Some(e), syn::RangeLimits::HalfOpen(_)) => {
2777 format!("(int({}) - {})", self.expr(e)?.code, lo)
2778 }
2779 (Some(e), syn::RangeLimits::Closed(_)) => {
2780 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
2781 }
2782 (None, _) => format!("({}.len - {})", recv.code, lo),
2783 };
2784 let elem = elem_of(&rt0).ok_or("cannot infer the element type of this slice")?;
2785 let mut v = Val::new(
2786 String::new(),
2787 Some(Nim::Named(
2788 "Option".into(),
2789 vec![Nim::OpenArray(Box::new(elem.clone()))],
2790 )),
2791 );
2792 v.guard = Some(format!("({} + {} <= {}.len)", lo, len, recv.code));
2793 v.window = Some(Alias::Window {
2794 code: recv.code.clone(),
2795 off: lo,
2796 len,
2797 elem: Some(elem),
2798 });
2799 return Ok(v);
2800 }
2801
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2802 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
2803 // own type; `v.push(e)` takes the element type.
2804 let arg_want = match (name.as_str(), &recv.ty) {
2805 ("push", Some(Nim::Seq(t))) | ("push", Some(Nim::OpenArray(t))) => Some((**t).clone()),
2806 (_, t) => t.clone(),
2807 };
2808 let mut args = Vec::new();
2809 for a in &m.args {
2810 args.push(self.expr_at(a, arg_want.as_ref())?);
2811 }
2812 let a0 = args.first().map(|a| a.code.clone());
2813 let rt = recv.ty.clone();
2814
2815 let (code, ty) = match name.as_str() {
2816 // Rust's `len()` is `usize`; Nim's is `int`. The conversion is
2817 // explicit so that a `usize` binding type-checks on the Nim side.
2818 "len" => (format!("uint({}.len)", recv.code), Some(Nim::Prim("uint".into()))),
2819 "is_empty" => (format!("({}.len == 0)", recv.code), Some(Nim::Prim("bool".into()))),
2820 "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)),
2821 "clone" | "to_vec" | "to_owned" | "as_slice" | "as_ref" | "as_mut" | "iter"
2822 | "into_iter" => (recv.code.clone(), rt.clone()),
2823 "unwrap" | "expect" => {
2824 let inner = match &rt {
2825 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
2826 Some(a[0].clone())
2827 }
2828 _ => None,
2829 };
2830 (format!("unwrap({})", recv.code), inner)
2831 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2832 "ok_or" if recv.guard.is_some() => {
2833 let e = args.first().ok_or("`ok_or` takes one argument")?;
2834 let ety = e.ty.clone();
2835 let mut v = recv.clone();
2836 v.guard_err = Some(e.code.clone());
2837 v.ty = match (&recv.ty, ety) {
2838 (Some(Nim::Named(_, a)), Some(et)) if a.len() == 1 => {
2839 Some(Nim::Named("Result".into(), vec![a[0].clone(), et]))
2840 }
2841 _ => None,
2842 };
2843 return Ok(v);
2844 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago2845 "ok_or" => {
2846 let inner = match &rt {
2847 Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
2848 _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
2849 };
2850 let e = args.first().ok_or("`ok_or` takes one argument")?;
2851 let ety = e
2852 .ty
2853 .clone()
2854 .ok_or("`ok_or` needs a known error type for its argument")?;
2855 (
2856 format!(
2857 "rsOkOr[{}, {}]({}, {})",
2858 inner.render(),
2859 ety.render(),
2860 recv.code,
2861 e.code
2862 ),
2863 Some(Nim::Named("Result".into(), vec![inner, ety])),
2864 )
2865 }
2866 "unwrap_or" => {
2867 let inner = match &rt {
2868 Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
2869 Some(a[0].clone())
2870 }
2871 _ => None,
2872 };
2873 (
2874 format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
2875 inner,
2876 )
2877 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2878 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
2879 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
2880 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
2881 "is_err" => (format!("(not {}.ok)", recv.code), Some(Nim::Prim("bool".into()))),
2882
2883 // Settled empirically: Nim's fixed-width *unsigned* arithmetic
2884 // wraps silently, matching Rust's `wrapping_*`. For *signed* types
2885 // Nim raises OverflowDefect, so the operation is routed through
2886 // the unsigned view of the same width, which is what Rust's
2887 // wrapping_* is defined to compute.
2888 "wrapping_add" | "wrapping_sub" | "wrapping_mul" => {
2889 let op = match name.as_str() {
2890 "wrapping_add" => "+",
2891 "wrapping_sub" => "-",
2892 _ => "*",
2893 };
2894 let t = rt.clone().ok_or_else(|| {
2895 format!("`{name}` needs a known receiver type to pick the wrapping width")
2896 })?;
2897 if !t.is_integer() {
2898 return Err(format!("`{name}` on a non-integer type"));
2899 }
2900 let arg = a0.ok_or_else(|| format!("`{name}` takes one argument"))?;
2901 if t.is_unsigned() {
2902 (format!("({} {} {})", recv.code, op, arg), Some(t))
2903 } else {
2904 let u = unsigned_peer(&t)?;
2905 (
2906 format!(
2907 "cast[{}](cast[{}]({}) {} cast[{}]({}))",
2908 t.render(), u, recv.code, op, u, arg
2909 ),
2910 Some(t),
2911 )
2912 }
2913 }
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2914 // Inside a formatting impl, a write through the `Formatter` *is*
2915 // the value the proc returns, so it lowers to the string written.
2916 "write_str" | "write_char" if self.is_fmt_param(&m.receiver) => (
2917 a0.ok_or("`write_str` takes one argument")?,
2918 Some(Nim::Prim("string".into())),
2919 ),
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2920 "abs" => (format!("abs({})", recv.code), rt.clone()),
2921 "min" => (format!("min({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
2922 "max" => (format!("max({}, {})", recv.code, a0.unwrap_or_default()), rt.clone()),
2923 "to_string" => (format!("rsDisplay({})", recv.code), Some(Nim::Prim("string".into()))),
2924 "as_bytes" | "into_bytes" => (
2925 format!("rsBytes({})", recv.code),
2926 Some(Nim::Seq(Box::new(Nim::Prim("uint8".into())))),
2927 ),
2928
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2929 "into" => {
2930 // `.into()` resolves through the `impl From` declarations, and
2931 // needs the target type to pick one.
2932 let from = rt
2933 .clone()
2934 .ok_or("`.into()` needs a known receiver type")?;
2935 let to = expect
2936 .ok_or("`.into()` needs a known target type; annotate the binding")?;
2937 let key = (type_name(&from), type_name(to));
2938 let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
2939 format!(
2940 "no `impl From<{}> for {}` in this file, so `.into()` has \
2941 no conversion to call",
2942 key.0, key.1
2943 )
2944 })?;
2945 (format!("{}({})", f, recv.code), Some(to.clone()))
2946 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2947 _ => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2948 // A method defined in this file via `impl`, found by the
2949 // receiver's type rather than by name alone.
2950 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
2951 let sig = key.and_then(|k| self.methods.get(&k)).map(|s| s.ret.clone());
2952 if let Some(ret) = sig {
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago2953 let mut all = vec![recv.code.clone()];
2954 all.extend(args.iter().map(|a| a.code.clone()));
2955 (format!("{}({})", ident(&name), all.join(", ")), Some(ret))
2956 } else {
2957 return Err(format!(
2958 "unsupported method `.{name}()`; it is neither defined in \
2959 this file nor part of the standard-library subset that \
2960 has a verified Nim equivalent"
2961 ));
2962 }
2963 }
2964 };
2965 Ok(Val::new(code, ty))
2966 }
2967
2968 // -------------------------------------------------------------- macros
2969
2970 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
2971 let name = path_name(&mac.path);
2972 match name.as_str() {
2973 "println" | "print" | "eprintln" | "eprint" => {
2974 let s = self.format_args(mac)?;
2975 let nl = name.ends_with("ln");
2976 Ok(match (name.starts_with('e'), nl) {
2977 (false, true) => format!("echo {s}"),
2978 (false, false) => format!("stdout.write({s})"),
2979 (true, true) => format!("stderr.writeLine({s})"),
2980 (true, false) => format!("stderr.write({s})"),
2981 })
2982 }
2983 "format" => self.format_args(mac),
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago2984 "write" | "writeln" => {
2985 // `write!(f, "..", ..)` inside a formatting impl: the first
2986 // argument is the sink, the rest is an ordinary format call.
2987 let args: Vec<Expr> = mac
2988 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
2989 .map_err(|e| format!("write!: {e}"))?
2990 .into_iter()
2991 .collect();
2992 let sink = args.first().ok_or("`write!` needs a sink")?;
2993 if !self.is_fmt_param(sink) {
2994 return Err("`write!` to anything but the `Formatter` of the \
2995 enclosing formatting impl is not implemented"
2996 .into());
2997 }
2998 let s = self.format_pieces(&args[1..])?;
2999 Ok(if name == "writeln" {
3000 format!("({} & \"\\n\")", s)
3001 } else {
3002 s
3003 })
3004 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago3005 "panic" => {
3006 let s = self.format_args(mac)?;
3007 Ok(format!("rsPanic({s})"))
3008 }
3009 "assert" => {
3010 let e: Expr = mac.parse_body().map_err(|e| format!("assert!: {e}"))?;
3011 let v = self.expr(&e)?;
3012 Ok(format!(
3013 "(if not ({}): rsPanic(\"assertion failed\"))",
3014 v.code
3015 ))
3016 }
3017 "vec" => {
3018 let body = mac.tokens.to_string();
3019 if body.trim().is_empty() {
3020 return Ok("@[]".into());
3021 }
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3022 // `vec![elem; n]` is the repeat form, not a list. The macro
3023 // body has no brackets, so it is parsed directly.
3024 if body.contains(';') {
3025 let (v, n) = mac
3026 .parse_body_with(|input: syn::parse::ParseStream| {
3027 let v: Expr = input.parse()?;
3028 input.parse::<syn::Token![;]>()?;
3029 let n: Expr = input.parse()?;
3030 Ok((v, n))
3031 })
3032 .map_err(|e| format!("vec![elem; n]: {e}"))?;
3033 let v = self.expr(&v)?;
3034 let n = self.expr(&n)?;
3035 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
3036 }
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago3037 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
3038 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
3039 .map_err(|e| format!("vec!: {e}"))?;
3040 let mut parts = Vec::new();
3041 for e in &elems {
3042 parts.push(self.expr(e)?.code);
3043 }
3044 Ok(format!("@[{}]", parts.join(", ")))
3045 }
3046 other => Err(format!(
3047 "unsupported macro `{other}!`; a macro whose expansion is not \
3048 known cannot be lowered faithfully"
3049 )),
3050 }
3051 }
3052
3053 /// `println!("{} {}", a, b)` -> a Nim string-concatenation expression.
3054 fn format_args(&mut self, mac: &syn::Macro) -> Result<String, String> {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago3055 let args: Vec<Expr> = mac
3056 .parse_body_with(syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated)
3057 .map_err(|e| format!("format arguments: {e}"))?
3058 .into_iter()
3059 .collect();
3060 self.format_pieces(&args)
3061 }
3062
3063 /// `["{} {}", a, b]` -> a Nim string-concatenation expression.
3064 fn format_pieces(&mut self, args: &[Expr]) -> Result<String, String> {
3065 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 8h ago3066 if args.is_empty() {
3067 return Ok("\"\"".into());
3068 }
3069 return Err("the first argument must be a literal format string".into());
3070 };
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago3071 let rest: Vec<&Expr> = args[1..].iter().collect();
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago3072
3073 let pieces = fmt::parse(&s.value())?;
3074 let mut parts: Vec<String> = Vec::new();
3075 let mut next = 0usize;
3076 let mut used = vec![false; rest.len()];
3077 for p in &pieces {
3078 match p {
3079 fmt::Piece::Lit(l) => parts.push(fmt::nim_str(l)),
3080 fmt::Piece::Arg { r#ref, spec } => {
3081 let v = match r#ref {
3082 fmt::Ref::Next => {
3083 let e = rest.get(next).ok_or("too few arguments for format string")?;
3084 used[next] = true;
3085 next += 1;
3086 self.expr(e)?
3087 }
3088 fmt::Ref::Index(i) => {
3089 let e = rest.get(*i).ok_or("format index out of range")?;
3090 used[*i] = true;
3091 self.expr(e)?
3092 }
3093 fmt::Ref::Named(n) => {
3094 let t = self.lookup(n).ok_or_else(|| {
3095 format!("`{{{n}}}` captures `{n}`, which is not in scope")
3096 })?;
3097 Val::new(ident(n), Some(t))
3098 }
3099 };
3100 parts.push(fmt::render_arg(&v.code, spec));
3101 }
3102 }
3103 }
3104 // Rust rejects an argument that no `{}` consumes; so do we, rather
3105 // than dropping it from the output.
3106 if let Some(i) = used.iter().position(|u| !u) {
3107 return Err(format!(
3108 "argument {} is never used by the format string",
3109 i + 1
3110 ));
3111 }
3112 Ok(if parts.is_empty() { "\"\"".into() } else { parts.join(" & ") })
3113 }
3114}
3115
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3116/// Whether a pattern introduces a binding.
3117fn binds(p: &Pat) -> bool {
3118 match p {
3119 Pat::Ident(_) => true,
3120 Pat::Guard(g) => binds(&g.pat),
3121 Pat::Paren(x) => binds(&x.pat),
3122 Pat::Reference(r) => binds(&r.pat),
3123 Pat::Or(o) => o.cases.iter().any(binds),
3124 Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
3125 Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
3126 _ => false,
3127 }
3128}
3129
3130/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
3131fn destructures(p: &Pat) -> bool {
3132 matches!(
3133 p,
3134 Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
3135 ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
3136 || matches!(p, Pat::Paren(x) if destructures(&x.pat))
3137 || matches!(p, Pat::Reference(r) if destructures(&r.pat))
3138}
3139
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago3140/// Whether an expression has a direct Nim expression form.
3141///
3142/// Nim's `if` is an expression only when every arm is a single expression, and
3143/// its `case` is never one here. Anything else has to be lowered as statements
3144/// that assign into a target.
3145fn expressible(e: &Expr) -> bool {
3146 match e {
3147 Expr::If(i) => {
3148 let Some(then) = single_expr(&i.then_branch) else { return false };
3149 if !expressible(then) {
3150 return false;
3151 }
3152 match &i.else_branch {
3153 None => false,
3154 Some((_, els)) => match &**els {
3155 Expr::Block(b) => single_expr(&b.block).is_some_and(expressible),
3156 other => expressible(other),
3157 },
3158 }
3159 }
3160 Expr::Match(_) | Expr::Block(_) | Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => false,
3161 _ => true,
3162 }
3163}
3164
3165/// The single expression a block consists of, if that is all it is. An `if`
3166/// can only be lowered as a Nim `if`-expression when both arms are this shape.
3167fn single_expr(b: &syn::Block) -> Option<&Expr> {
3168 match (b.stmts.len(), b.stmts.first()) {
3169 (1, Some(Stmt::Expr(e, None))) => Some(e),
3170 _ => None,
3171 }
3172}
3173
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3174/// Substitute `params[i] -> args[i]` through a type. Enough of the type
3175/// grammar is covered to expand the aliases we accept; anything else is left
3176/// alone and will be reported by `ty::map` if it is unsupported.
3177fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
3178 use syn::Type;
3179 match t {
3180 Type::Path(p) => {
3181 if p.qself.is_none() && p.path.segments.len() == 1 {
3182 let seg = &p.path.segments[0];
3183 if seg.arguments.is_empty() {
3184 let name = seg.ident.to_string();
3185 if let Some(i) = params.iter().position(|x| *x == name) {
3186 return args[i].clone();
3187 }
3188 }
3189 }
3190 let mut p = p.clone();
3191 for seg in &mut p.path.segments {
3192 if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
3193 for g in &mut a.args {
3194 if let syn::GenericArgument::Type(t) = g {
3195 *t = substitute(t, params, args);
3196 }
3197 }
3198 }
3199 }
3200 Type::Path(p)
3201 }
3202 Type::Reference(r) => {
3203 let mut r = r.clone();
3204 r.elem = Box::new(substitute(&r.elem, params, args));
3205 Type::Reference(r)
3206 }
3207 Type::Slice(sl) => {
3208 let mut sl = sl.clone();
3209 sl.elem = Box::new(substitute(&sl.elem, params, args));
3210 Type::Slice(sl)
3211 }
3212 Type::Array(a) => {
3213 let mut a = a.clone();
3214 a.elem = Box::new(substitute(&a.elem, params, args));
3215 Type::Array(a)
3216 }
3217 Type::Tuple(tp) => {
3218 let mut tp = tp.clone();
3219 tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
3220 Type::Tuple(tp)
3221 }
3222 Type::Paren(p) => substitute(&p.elem, params, args),
3223 Type::Group(g) => substitute(&g.elem, params, args),
3224 other => other.clone(),
3225 }
3226}
3227
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago3228// --------------------------------------------------------------- utilities
3229
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 8h ago3230/// Whether a return type is a borrow of one of the arguments, which Nim
3231/// models with a view rather than with an owned copy.
3232fn returns_borrow(t: &syn::Type) -> bool {
3233 match t {
3234 syn::Type::Reference(r) => matches!(&*r.elem, syn::Type::Slice(_)),
3235 syn::Type::Paren(p) => returns_borrow(&p.elem),
3236 syn::Type::Group(g) => returns_borrow(&g.elem),
3237 _ => false,
3238 }
3239}
3240
3241/// The element type of a sequence-like Nim type.
3242fn elem_of(t: &Option<Nim>) -> Option<Nim> {
3243 match t {
3244 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) | Some(Nim::Array(_, e)) => Some((**e).clone()),
3245 Some(Nim::Prim(p)) if p == "string" => Some(Nim::Prim("char".into())),
3246 _ => None,
3247 }
3248}
3249
3250/// The short name a Nim type is known by, for keying method tables.
3251fn type_name(t: &Nim) -> String {
3252 match t {
3253 Nim::Named(n, _) => n.clone(),
3254 Nim::Prim(p) => p.clone(),
3255 other => other.render(),
3256 }
3257}
3258
3259fn is_fmt_trait(t: &str) -> bool {
3260 matches!(t, "Display" | "Debug" | "LowerHex" | "UpperHex" | "Binary" | "Octal")
3261}
3262
3263/// The prelude proc a formatting trait's output is produced by.
3264fn fmt_proc(t: &str) -> &'static str {
3265 match t {
3266 "Display" => "rsDisplay",
3267 "Debug" => "rsDebug",
3268 "LowerHex" => "rsLowerHex",
3269 "UpperHex" => "rsUpperHex",
3270 "Binary" => "rsBinary",
3271 _ => "rsOctal",
3272 }
3273}
3274
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago3275fn takes_self(sig: &syn::Signature) -> bool {
3276 matches!(sig.inputs.first(), Some(FnArg::Receiver(_)))
3277}
3278
3279fn path_name(p: &syn::Path) -> String {
3280 p.segments
3281 .last()
3282 .map(|s| s.ident.to_string())
3283 .unwrap_or_default()
3284}
3285
3286fn is_compound(op: &BinOp) -> bool {
3287 matches!(
3288 op,
3289 BinOp::AddAssign(_)
3290 | BinOp::SubAssign(_)
3291 | BinOp::MulAssign(_)
3292 | BinOp::DivAssign(_)
3293 | BinOp::RemAssign(_)
3294 | BinOp::BitAndAssign(_)
3295 | BinOp::BitOrAssign(_)
3296 | BinOp::BitXorAssign(_)
3297 | BinOp::ShlAssign(_)
3298 | BinOp::ShrAssign(_)
3299 )
3300}
3301
3302/// The Nim literal suffix for an integer type (`5'i32`).
3303fn nim_suffix(t: &Nim) -> Result<&'static str, String> {
3304 let Nim::Prim(p) = t else {
3305 return Err("not a primitive integer".into());
3306 };
3307 Ok(match p.as_str() {
3308 "int8" => "i8",
3309 "int16" => "i16",
3310 "int32" => "i32",
3311 "int64" => "i64",
3312 "int" => "i",
3313 "uint8" => "u8",
3314 "uint16" => "u16",
3315 "uint32" => "u32",
3316 "uint64" => "u64",
3317 "uint" => "u",
3318 other => return Err(format!("no Nim literal suffix for `{other}`")),
3319 })
3320}
3321
3322/// The unsigned integer type of the same width, used to spell `wrapping_*`.
3323fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
3324 let Nim::Prim(p) = t else {
3325 return Err("not a primitive integer".into());
3326 };
3327 Ok(match p.as_str() {
3328 "int8" => "uint8",
3329 "int16" => "uint16",
3330 "int32" => "uint32",
3331 "int64" => "uint64",
3332 "int" => "uint",
3333 other => return Err(format!("`{other}` has no unsigned peer")),
3334 })
3335}
3336
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 8h ago3337fn quote_meta(m: &syn::Meta) -> String {
3338 match m {
3339 syn::Meta::Path(p) => path_name(p),
3340 syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
3341 syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
3342 }
3343}
3344
3345fn item_attrs(i: &Item) -> &[syn::Attribute] {
3346 match i {
3347 Item::Fn(f) => &f.attrs,
3348 Item::Struct(s) => &s.attrs,
3349 Item::Enum(e) => &e.attrs,
3350 Item::Impl(x) => &x.attrs,
3351 Item::Const(c) => &c.attrs,
3352 Item::Type(t) => &t.attrs,
3353 Item::Mod(m) => &m.attrs,
3354 Item::Use(u) => &u.attrs,
3355 Item::ExternCrate(e) => &e.attrs,
3356 Item::Static(s) => &s.attrs,
3357 _ => &[],
3358 }
3359}
3360
Add the differential test runner, and a lowering to measure with it 8ac32af nandithebull 8h ago3361fn item_kind(i: &Item) -> &'static str {
3362 match i {
3363 Item::Trait(_) => "`trait`",
3364 Item::Static(_) => "`static`",
3365 Item::Macro(_) => "macro definition",
3366 Item::Union(_) => "`union`",
3367 Item::ForeignMod(_) => "`extern` block",
3368 _ => "item",
3369 }
3370}
3371
3372fn expr_kind(e: &Expr) -> &'static str {
3373 match e {
3374 Expr::Closure(_) => "closure",
3375 Expr::Async(_) => "`async` block",
3376 Expr::Await(_) => "`.await`",
3377 Expr::Try(_) => "`?`",
3378 Expr::Range(_) => "range",
3379 Expr::Match(_) => "`match` (only statement position is implemented)",
3380 Expr::Let(_) => "`let` expression",
3381 Expr::Unsafe(_) => "`unsafe` block",
3382 Expr::Loop(_) | Expr::While(_) | Expr::ForLoop(_) => "loop (has no value in Nim)",
3383 _ => "expression",
3384 }
3385}