nandi/rustnimpublic Fork 0
b0ccd80
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.

Add enums, Option/Result, `?`, and the machinery base16ct needs around them

Enums lower two ways. A C-like enum becomes a plain Nim enum, which compares
and `case`-checks like Rust's. A data-carrying enum becomes a Nim object
variant -- the same shape the prelude already uses for Option and Result --
with payload fields prefixed per variant, since Nim requires the branches of
a variant object to have distinct field names.

`match` correspondingly grows a second form. Arms that neither bind nor
destructure stay a Nim `case`, which keeps Rust's exhaustiveness check. Arms
that bind become an if/elif chain with the bindings emitted as `let`s,
because Nim's `case` cannot destructure. The chain always closes with a
panicking arm: Rust proved it unreachable, Nim cannot see that, and an open
chain would silently fall through.

Ok/Err/Some are emitted with full type arguments, because Nim cannot infer E
from an Ok(v) alone -- which is why the expected type now also has to reach a
match arm, not just a `let`. `?` expands to a temporary plus an early return,
emitted ahead of the line being built. Rust's implicit From::from on the
error is *not* assumed to be the identity: mismatched error types are
rejected. `?` in a while condition is rejected too, since the early return
would run once before the loop rather than per iteration.

Also here, because base16ct needs them: generic type aliases (expanded before
any type is mapped, so an alias cannot be honoured in one position and missed
in another), function-typed parameters via `impl Fn(A) -> B`, multi-file
input flattened into one Nim module, and `#[cfg]` evaluated against a feature
set given as `--cfg feature=x`. Evaluating cfg is what rustc does, so a
dropped item is genuinely not part of the program; a predicate we cannot
evaluate is reported rather than assumed.

Two of the earlier base16ct blockers turned out to be our bugs rather than
real limits: lifetime parameters were being rejected as generics though they
carry no runtime meaning, and `fmt::Result` was being mistaken for the
crate's own `Result` alias because only the last path segment was compared.

27 differential cases and 5 integration tests, all green. Case 022 is
base16ct's Error, decoded_len and decode_nibble verbatim, decoding lower,
upper and mixed hex byte-identically to rustc. The crate as a whole does not
go through yet; DESIGN.md records the exact remaining blockers, which reduce
to trait impls and slice iterator adaptors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-18T19:34:41-07:00 Browse files
b0ccd80 parent: 8ac32af
modified DESIGN.md +68 -15
@@ -2,15 +2,16 @@
22
33 ## Status
44
5-**Transpiling, and measured.** The differential runner is in place and the
6-whole corpus is green: 21 cases, 17 behavioural and 4 rejections, every one
7-of which compiles under both rustc and Nim and produces identical stdout and
8-exit status. Run it with `cargo test`.
9-
10-Passing today: functions, `impl` methods, structs, `let`/`let mut`, the full
11-integer and float operator set at exact widths, `as` casts, `if`/`while`/
12-`loop`/`for`, `match`, `Vec`/slices/arrays, and `println!`/`format!` with
13-`{}`, `{:?}`, `{:x}`, `{:b}`, positional and inline-named arguments, and
5+**Transpiling, and measured.** 27 differential cases, 23 behavioural and 4
6+rejections, plus 5 unit/integration tests. All green. Run `cargo test`.
7+
8+Passing today: functions, `impl` methods, structs, enums (C-like and
9+data-carrying), `Option`/`Result` with `?`, `let`/`let mut`, the full integer
10+and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`,
11+`match` including patterns that bind, `Vec`/slices/arrays, type aliases
12+(including generic ones), function-typed parameters (`impl Fn(A) -> B`),
13+multi-file input, `#[cfg]` evaluation, and `println!`/`format!` with `{}`,
14+`{:?}`, `{:x}`, `{:b}`, positional and inline-named arguments, and
1415 zero/space padding.
1516
1617 ## Why this exists
@@ -69,6 +70,33 @@ Planned modules:
6970 | `src/main.rs` | CLI: `rustnim <in.rs> -o <out.nim>` | written |
7071 | `tests/differential.rs` | the runner described below | written |
7172
73+### Enums, `Option` and `Result`
74+
75+A C-like enum becomes a plain Nim `enum`, which compares, orders and
76+`case`-checks the way Rust's does. A data-carrying enum becomes a Nim object
77+variant — a discriminant enum plus one branch per variant — which is the same
78+shape the prelude already uses for `Option` and `Result`. Nim requires the
79+branches of a variant object to have distinct field names, so each payload
80+field is prefixed with its variant.
81+
82+`match` takes one of two forms. Arms that neither bind nor destructure become
83+a Nim `case`, which is exhaustiveness-checked the way Rust's is. Arms that do
84+bind become an `if`/`elif` chain with the bindings emitted as `let`s, because
85+Nim's `case` cannot destructure. The chain always ends in an arm that panics:
86+Rust proved it unreachable, but Nim cannot see that, and leaving the chain
87+open would silently fall through instead.
88+
89+`Ok`, `Err` and `Some` are emitted with their full type arguments
90+(`rsOk[T, E](v)`), because Nim cannot infer `E` from an `Ok(v)` alone. That is
91+why the expected type has to reach a `match` arm as well as a `let`.
92+
93+`?` expands to statements — a temporary, a discriminant test, and an early
94+`return` — which are emitted ahead of the line being built. Rust inserts a
95+`From::from` on the error there; we accept only the case where the two error
96+types already agree, rather than assume a conversion is the identity. `?` in a
97+`while` condition is rejected: the early return would run once before the
98+loop rather than on each iteration.
99+
72100 ### Type propagation is load-bearing
73101
74102 Rust infers an unsuffixed integer literal's type from context and falls back
@@ -121,11 +149,16 @@ runner) rather than a wrong answer.
121149
122150 5. `checked_*` and `saturating_*` are not mapped yet; they are currently
123151 rejected as unsupported methods rather than approximated.
124-6. Generics, traits, enums, closures, iterator adaptors and `?` are all
125- rejected with a reason. `base16ct` needs enums and `Result`-carrying
126- functions, so those are next.
152+6. Generics (type and const parameters), traits and trait impls, closures and
153+ iterator adaptors are rejected with a reason. Lifetime parameters are *not*
154+ a rejection: they carry no runtime meaning and Nim is GC'd, so
155+ `fn encode<'a>(..)` lowers fine.
127156 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but
128157 the exponent-form thresholds have only been checked at `1e21`.
158+8. Flattening several files into one module can collide a crate's own
159+ `type Result<T>` with the builtin `Result<T, E>`. Rust kept them apart by
160+ module; we tell them apart by arity. That is a real difference from Rust's
161+ resolution and would need proper module scoping to fix.
129162
130163 ## Testing: differential, not golden
131164
@@ -179,6 +212,26 @@ or any parent, or via `RUSTNIM_NIM`.
179212 ## Milestone 1
180213
181214 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and
182-have its decoder produce byte-identical output to the Rust original. It is a
183-good target: 613 lines, `no_std`, no dependencies, and its constant-time
184-integer arithmetic is exactly the kind of thing a sloppy transpiler gets wrong.
215+have its decoder produce byte-identical output to the Rust original.
216+
217+**Not reached.** What is reached, and measured, is `tests/cases/022`: the
218+`Error` enum, `decoded_len` and `decode_nibble` **verbatim from base16ct
219+1.0.0**, decoding lower, upper and mixed hex and both error cases, with output
220+byte-identical to rustc's. That covers the constant-time nibble arithmetic —
221+the i16 wrapping and arithmetic shift whose exact semantics the other
222+transpiler's float64 universal AST cannot represent at all. The loop around it
223+is rewritten with indexing, and the case says so.
224+
225+Running the real crate now gives these diagnostics, which are the todo list:
226+
227+| file | blocker |
228+|---|---|
229+| `error.rs` | `impl fmt::Display for Error`, `impl core::error::Error`, `impl From<Error> for fmt::Error` — trait impls |
230+| `lib.rs` | `decode_inner`: `dst.get_mut(..n)` (a mutable subslice view), `chunks_exact(2)`, `zip`, `iter_mut`, and `*dst = ..` |
231+| `lower.rs`, `upper.rs`, `mixed.rs` | the same, plus `encode`'s `chunks_exact_mut` |
232+| `display.rs` | `impl fmt::UpperHex for HexDisplay` — trait impls again |
233+
234+So the remaining work is two features, not a long tail: **trait impls**, and
235+**iterator adaptors over slices** together with the mutable slice views they
236+borrow from. `mod`/multi-file and `#[cfg]` are done; pass the crate's files
237+together and add `--cfg feature=alloc` for the `alloc` half.
@@ -2,15 +2,16 @@
2 2
3 ## Status3 ## Status
4 4
5-**Transpiling, and measured.** The differential runner is in place and the5+**Transpiling, and measured.** 27 differential cases, 23 behavioural and 4
6-whole corpus is green: 21 cases, 17 behavioural and 4 rejections, every one6+rejections, plus 5 unit/integration tests. All green. Run `cargo test`.
7-of which compiles under both rustc and Nim and produces identical stdout and7+
8-exit status. Run it with `cargo test`.8+Passing today: functions, `impl` methods, structs, enums (C-like and
9-9+data-carrying), `Option`/`Result` with `?`, `let`/`let mut`, the full integer
10-Passing today: functions, `impl` methods, structs, `let`/`let mut`, the full10+and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`,
11-integer and float operator set at exact widths, `as` casts, `if`/`while`/11+`match` including patterns that bind, `Vec`/slices/arrays, type aliases
12-`loop`/`for`, `match`, `Vec`/slices/arrays, and `println!`/`format!` with12+(including generic ones), function-typed parameters (`impl Fn(A) -> B`),
13-`{}`, `{:?}`, `{:x}`, `{:b}`, positional and inline-named arguments, and13+multi-file input, `#[cfg]` evaluation, and `println!`/`format!` with `{}`,
14+`{:?}`, `{:x}`, `{:b}`, positional and inline-named arguments, and
14 zero/space padding.15 zero/space padding.
15 16
16 ## Why this exists17 ## Why this exists
@@ -69,6 +70,33 @@ Planned modules:
69 | `src/main.rs` | CLI: `rustnim <in.rs> -o <out.nim>` | written |70 | `src/main.rs` | CLI: `rustnim <in.rs> -o <out.nim>` | written |
70 | `tests/differential.rs` | the runner described below | written |71 | `tests/differential.rs` | the runner described below | written |
71 72
73+### Enums, `Option` and `Result`
74+
75+A C-like enum becomes a plain Nim `enum`, which compares, orders and
76+`case`-checks the way Rust's does. A data-carrying enum becomes a Nim object
77+variant — a discriminant enum plus one branch per variant — which is the same
78+shape the prelude already uses for `Option` and `Result`. Nim requires the
79+branches of a variant object to have distinct field names, so each payload
80+field is prefixed with its variant.
81+
82+`match` takes one of two forms. Arms that neither bind nor destructure become
83+a Nim `case`, which is exhaustiveness-checked the way Rust's is. Arms that do
84+bind become an `if`/`elif` chain with the bindings emitted as `let`s, because
85+Nim's `case` cannot destructure. The chain always ends in an arm that panics:
86+Rust proved it unreachable, but Nim cannot see that, and leaving the chain
87+open would silently fall through instead.
88+
89+`Ok`, `Err` and `Some` are emitted with their full type arguments
90+(`rsOk[T, E](v)`), because Nim cannot infer `E` from an `Ok(v)` alone. That is
91+why the expected type has to reach a `match` arm as well as a `let`.
92+
93+`?` expands to statements — a temporary, a discriminant test, and an early
94+`return` — which are emitted ahead of the line being built. Rust inserts a
95+`From::from` on the error there; we accept only the case where the two error
96+types already agree, rather than assume a conversion is the identity. `?` in a
97+`while` condition is rejected: the early return would run once before the
98+loop rather than on each iteration.
99+
72 ### Type propagation is load-bearing100 ### Type propagation is load-bearing
73 101
74 Rust infers an unsuffixed integer literal's type from context and falls back102 Rust infers an unsuffixed integer literal's type from context and falls back
@@ -121,11 +149,16 @@ runner) rather than a wrong answer.
121 149
122 5. `checked_*` and `saturating_*` are not mapped yet; they are currently150 5. `checked_*` and `saturating_*` are not mapped yet; they are currently
123 rejected as unsupported methods rather than approximated.151 rejected as unsupported methods rather than approximated.
124-6. Generics, traits, enums, closures, iterator adaptors and `?` are all152+6. Generics (type and const parameters), traits and trait impls, closures and
125- rejected with a reason. `base16ct` needs enums and `Result`-carrying153+ iterator adaptors are rejected with a reason. Lifetime parameters are *not*
126- functions, so those are next.154+ a rejection: they carry no runtime meaning and Nim is GC'd, so
155+ `fn encode<'a>(..)` lowers fine.
127 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but156 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but
128 the exponent-form thresholds have only been checked at `1e21`.157 the exponent-form thresholds have only been checked at `1e21`.
158+8. Flattening several files into one module can collide a crate's own
159+ `type Result<T>` with the builtin `Result<T, E>`. Rust kept them apart by
160+ module; we tell them apart by arity. That is a real difference from Rust's
161+ resolution and would need proper module scoping to fix.
129 162
130 ## Testing: differential, not golden163 ## Testing: differential, not golden
131 164
@@ -179,6 +212,26 @@ or any parent, or via `RUSTNIM_NIM`.
179 ## Milestone 1212 ## Milestone 1
180 213
181 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and214 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and
182-have its decoder produce byte-identical output to the Rust original. It is a215+have its decoder produce byte-identical output to the Rust original.
183-good target: 613 lines, `no_std`, no dependencies, and its constant-time216+
184-integer arithmetic is exactly the kind of thing a sloppy transpiler gets wrong.217+**Not reached.** What is reached, and measured, is `tests/cases/022`: the
218+`Error` enum, `decoded_len` and `decode_nibble` **verbatim from base16ct
219+1.0.0**, decoding lower, upper and mixed hex and both error cases, with output
220+byte-identical to rustc's. That covers the constant-time nibble arithmetic —
221+the i16 wrapping and arithmetic shift whose exact semantics the other
222+transpiler's float64 universal AST cannot represent at all. The loop around it
223+is rewritten with indexing, and the case says so.
224+
225+Running the real crate now gives these diagnostics, which are the todo list:
226+
227+| file | blocker |
228+|---|---|
229+| `error.rs` | `impl fmt::Display for Error`, `impl core::error::Error`, `impl From<Error> for fmt::Error` — trait impls |
230+| `lib.rs` | `decode_inner`: `dst.get_mut(..n)` (a mutable subslice view), `chunks_exact(2)`, `zip`, `iter_mut`, and `*dst = ..` |
231+| `lower.rs`, `upper.rs`, `mixed.rs` | the same, plus `encode`'s `chunks_exact_mut` |
232+| `display.rs` | `impl fmt::UpperHex for HexDisplay` — trait impls again |
233+
234+So the remaining work is two features, not a long tail: **trait impls**, and
235+**iterator adaptors over slices** together with the mutable slice views they
236+borrow from. `mod`/multi-file and `#[cfg]` are done; pass the crate's files
237+together and add `--cfg feature=alloc` for the `alloc` half.
modified src/lower.rs +993 -68
@@ -10,7 +10,7 @@ use crate::fmt;
1010 use crate::ty::{self, Nim};
1111 use std::collections::HashMap;
1212 use syn::{
13- BinOp, Expr, FnArg, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp,
13+ BinOp, Expr, FnArg, GenericArgument, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp,
1414 };
1515
1616 // --------------------------------------------------------------- vocabulary
@@ -61,6 +61,37 @@ struct Sig {
6161 ret: Nim,
6262 }
6363
64+/// One variant of a Rust enum.
65+#[derive(Clone)]
66+struct Variant {
67+ name: String,
68+ /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
69+ /// `f0`, `f1`, ...; every field is prefixed with the variant name because
70+ /// Nim requires the branches of a variant object to have distinct fields.
71+ fields: Vec<(String, Nim)>,
72+}
73+
74+#[derive(Clone)]
75+struct EnumDef {
76+ name: String,
77+ /// True when every variant is a unit variant, which Nim represents as a
78+ /// plain `enum` rather than an object variant.
79+ simple: bool,
80+ variants: Vec<Variant>,
81+}
82+
83+impl EnumDef {
84+ fn kind_ident(&self, v: &str) -> String {
85+ format!("k{}{}", self.name, v)
86+ }
87+ fn ctor_ident(&self, v: &str) -> String {
88+ format!("{}{}", self.name, v)
89+ }
90+ fn get(&self, v: &str) -> Option<&Variant> {
91+ self.variants.iter().find(|x| x.name == v)
92+ }
93+}
94+
6495 pub struct Lowerer {
6596 out: String,
6697 indent: usize,
@@ -68,12 +99,26 @@ pub struct Lowerer {
6899 fns: HashMap<String, Sig>,
69100 /// struct name -> (field, type)
70101 structs: HashMap<String, Vec<(String, Nim)>>,
102+ enums: HashMap<String, EnumDef>,
103+ /// variant name -> enums declaring it. A variant named by more than one
104+ /// enum must be written qualified, or it is rejected as ambiguous.
105+ variant_owner: HashMap<String, Vec<String>>,
106+ /// `type X<T> = ...`, expanded before any type is mapped.
107+ aliases: HashMap<String, (Vec<String>, syn::Type)>,
108+ /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
109+ /// evaluated against these exactly as rustc would, so an item that is
110+ /// dropped here is genuinely not part of the program being compiled.
111+ pub features: Vec<String>,
112+ dropped_by_cfg: usize,
71113 /// Return type of the proc being lowered, so `return e` and a trailing
72114 /// expression can type their literals the way Rust's inference would.
73115 ret: Option<Nim>,
74116 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
75117 /// statement must assign their value to.
76118 target: Option<(String, Option<Nim>)>,
119+ /// Set while lowering a `while` condition, which Nim re-evaluates each
120+ /// iteration and so cannot have statements hoisted out of it.
121+ in_loop_cond: bool,
77122 tmp: usize,
78123 }
79124
@@ -85,8 +130,14 @@ impl Lowerer {
85130 scopes: vec![HashMap::new()],
86131 fns: HashMap::new(),
87132 structs: HashMap::new(),
133+ enums: HashMap::new(),
134+ variant_owner: HashMap::new(),
135+ aliases: HashMap::new(),
136+ features: Vec::new(),
137+ dropped_by_cfg: 0,
88138 ret: None,
89139 target: None,
140+ in_loop_cond: false,
90141 tmp: 0,
91142 }
92143 }
@@ -162,6 +213,14 @@ impl Lowerer {
162213 }
163214
164215 fn collect(&mut self, item: &Item) -> Result<(), String> {
216+ // A `#[cfg(..)]` item exists only under some feature set. Dropping it
217+ // silently would change what the program does; picking a feature set
218+ // on the user's behalf would be a guess. So it is reported, except on
219+ // items that carry no runtime meaning here anyway.
220+ if !self.cfg_keeps(item_attrs(item))? {
221+ self.dropped_by_cfg += 1;
222+ return Ok(());
223+ }
165224 match item {
166225 Item::Fn(f) => {
167226 let (params, ret) = self.signature(&f.sig)?;
@@ -174,12 +233,64 @@ impl Lowerer {
174233 Some(id) => id.to_string(),
175234 None => format!("f{i}"), // tuple struct
176235 };
177- fields.push((name, ty::map(&f.ty)?.owned()));
236+ fields.push((name, self.map_ty(&f.ty)?.owned()));
178237 }
179238 self.structs.insert(s.ident.to_string(), fields);
180239 }
240+ Item::Type(t) => {
241+ let params: Vec<String> = t
242+ .generics
243+ .params
244+ .iter()
245+ .filter_map(|g| match g {
246+ syn::GenericParam::Type(t) => Some(t.ident.to_string()),
247+ _ => None,
248+ })
249+ .collect();
250+ self.aliases
251+ .insert(t.ident.to_string(), (params, (*t.ty).clone()));
252+ }
253+ Item::Enum(e) => {
254+ let name = e.ident.to_string();
255+ if e.generics.params.iter().any(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
256+ return Err(format!("`enum {name}` is generic: not implemented yet"));
257+ }
258+ let mut variants = Vec::new();
259+ for v in &e.variants {
260+ let vname = v.ident.to_string();
261+ if v.discriminant.is_some() {
262+ return Err(format!(
263+ "`{name}::{vname}` has an explicit discriminant; Rust's \
264+ `as` on such an enum has a value this lowering does not \
265+ yet preserve"
266+ ));
267+ }
268+ let mut fields = Vec::new();
269+ for (i, f) in v.fields.iter().enumerate() {
270+ // Nim requires the branches of a variant object to have
271+ // distinct field names, so each is prefixed.
272+ let fname = match &f.ident {
273+ Some(id) => format!("{vname}_{id}"),
274+ None => format!("{vname}_f{i}"),
275+ };
276+ fields.push((fname, self.map_ty(&f.ty)?.owned()));
277+ }
278+ variants.push(Variant { name: vname, fields });
279+ }
280+ let simple = variants.iter().all(|v| v.fields.is_empty());
281+ for v in &variants {
282+ self.variant_owner
283+ .entry(v.name.clone())
284+ .or_default()
285+ .push(name.clone());
286+ }
287+ self.enums.insert(
288+ name.clone(),
289+ EnumDef { name, simple, variants },
290+ );
291+ }
181292 Item::Impl(im) => {
182- let self_ty = ty::map(&im.self_ty)?;
293+ let self_ty = self.map_ty(&im.self_ty)?;
183294 for it in &im.items {
184295 if let syn::ImplItem::Fn(m) = it {
185296 let (mut params, ret) = self.signature(&m.sig)?;
@@ -195,25 +306,127 @@ impl Lowerer {
195306 Ok(())
196307 }
197308
309+ /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
310+ ///
311+ /// This is evaluation, not approximation: rustc does the same thing, and
312+ /// an item whose predicate is false is not part of the compiled program.
313+ /// A predicate that cannot be evaluated is reported rather than assumed.
314+ fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
315+ for a in attrs {
316+ if a.path().is_ident("cfg") {
317+ let pred: syn::Meta = a
318+ .parse_args()
319+ .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
320+ if !self.cfg_eval(&pred)? {
321+ return Ok(false);
322+ }
323+ }
324+ }
325+ Ok(true)
326+ }
327+
328+ fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
329+ match m {
330+ syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
331+ let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
332+ return Err("`feature = ..` expects a string".into());
333+ };
334+ Ok(self.features.iter().any(|f| *f == s.value()))
335+ }
336+ syn::Meta::List(l) if l.path.is_ident("not") => {
337+ let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
338+ Ok(!self.cfg_eval(&inner)?)
339+ }
340+ syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
341+ let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
342+ .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
343+ .map_err(|e| e.to_string())?;
344+ let all = l.path.is_ident("all");
345+ let mut acc = all;
346+ for i in &items {
347+ let v = self.cfg_eval(i)?;
348+ acc = if all { acc && v } else { acc || v };
349+ }
350+ Ok(acc)
351+ }
352+ other => Err(format!(
353+ "`#[cfg({})]` is not a predicate rustnim can evaluate; only \
354+ `feature = \"..\"`, `not`, `all` and `any` are implemented",
355+ quote_meta(other)
356+ )),
357+ }
358+ }
359+
360+ /// Map a Rust type, expanding any `type` alias first. Every type in the
361+ /// lowering goes through here rather than calling `ty::map` directly, so
362+ /// an alias cannot be missed in one position and honoured in another.
363+ fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
364+ ty::map(&self.expand(t, 0)?)
365+ }
366+
367+ fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
368+ if depth > 16 {
369+ return Err("type alias expansion did not terminate; is it cyclic?".into());
370+ }
371+ let syn::Type::Path(p) = t else { return Ok(t.clone()) };
372+ // Only an unqualified name can be one of this file's aliases.
373+ // `fmt::Result` and `core::result::Result` are different types that
374+ // merely end in the same segment.
375+ if p.path.segments.len() != 1 {
376+ return Ok(t.clone());
377+ }
378+ let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
379+ let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
380+ return Ok(t.clone());
381+ };
382+ let args: Vec<syn::Type> = match &seg.arguments {
383+ syn::PathArguments::AngleBracketed(a) => a
384+ .args
385+ .iter()
386+ .filter_map(|g| match g {
387+ GenericArgument::Type(t) => Some(t.clone()),
388+ _ => None,
389+ })
390+ .collect(),
391+ _ => vec![],
392+ };
393+ if args.len() != params.len() {
394+ // Flattening several files into one module can bring a crate's own
395+ // alias (`type Result<T> = Result<T, Error>`) into scope at a site
396+ // that meant the builtin (`Result<T, E>`). Rust kept them apart by
397+ // module; here they are told apart by arity, and a use that fits
398+ // neither is left for `ty::map` to report.
399+ return Ok(t.clone());
400+ }
401+ self.expand(&substitute(target, params, &args), depth + 1)
402+ }
403+
198404 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
199405 if sig.asyncness.is_some() {
200406 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
201407 }
202- if !sig.generics.params.is_empty() {
408+ // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
409+ // `fn encode<'a>(..)` is not generic for our purposes. Type and const
410+ // parameters genuinely are, and are rejected.
411+ if let Some(p) = sig.generics.params.iter().find(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
412+ let what = match p {
413+ syn::GenericParam::Const(_) => "const",
414+ _ => "type",
415+ };
203416 return Err(format!(
204- "`fn {}` is generic: generics are not implemented yet",
417+ "`fn {}` has a {what} parameter: generics are not implemented yet",
205418 sig.ident
206419 ));
207420 }
208421 let mut params = Vec::new();
209422 for a in &sig.inputs {
210423 if let FnArg::Typed(t) = a {
211- params.push(ty::map(&t.ty)?);
424+ params.push(self.map_ty(&t.ty)?);
212425 }
213426 }
214427 let ret = match &sig.output {
215428 ReturnType::Default => Nim::Unit,
216- ReturnType::Type(_, t) => ty::map(t)?.owned(),
429+ ReturnType::Type(_, t) => self.map_ty(t)?.owned(),
217430 };
218431 Ok((params, ret))
219432 }
@@ -221,6 +434,9 @@ impl Lowerer {
221434 // --------------------------------------------------------------- items
222435
223436 fn item(&mut self, item: &Item) -> Result<(), String> {
437+ if !self.cfg_keeps(item_attrs(item))? {
438+ return Ok(());
439+ }
224440 match item {
225441 Item::Fn(f) => self.func(&f.sig, &f.block, None),
226442 Item::Struct(s) => {
@@ -238,8 +454,14 @@ impl Lowerer {
238454 self.blank();
239455 Ok(())
240456 }
457+ Item::Type(_) => Ok(()), // expanded at every use site
458+ Item::Enum(e) => {
459+ let def = self.enums[&e.ident.to_string()].clone();
460+ self.emit_enum(&def);
461+ Ok(())
462+ }
241463 Item::Const(c) => {
242- let t = ty::map(&c.ty)?.owned();
464+ let t = self.map_ty(&c.ty)?.owned();
243465 let v = self.expr(&c.expr)?;
244466 self.bind(&c.ident.to_string(), t.clone());
245467 let line = format!("const {}*: {} = {}", ident(&c.ident.to_string()), t.render(), v.code);
@@ -248,7 +470,7 @@ impl Lowerer {
248470 Ok(())
249471 }
250472 Item::Impl(im) => {
251- let self_ty = ty::map(&im.self_ty)?;
473+ let self_ty = self.map_ty(&im.self_ty)?;
252474 if im.trait_.is_some() {
253475 return Err(format!(
254476 "`impl Trait for {}`: trait impls are not implemented yet",
@@ -266,14 +488,165 @@ impl Lowerer {
266488 }
267489 Ok(())
268490 }
269- Item::Use(_) => Ok(()), // `use` has no Nim analogue in a single module
270- Item::Mod(m) if m.content.is_none() => {
271- Err(format!("`mod {};` (external file) is not implemented yet", m.ident))
491+ // `use` and `extern crate` are resolution directives with no Nim
492+ // analogue once everything is one module.
493+ Item::Use(_) | Item::ExternCrate(_) => Ok(()),
494+ Item::Mod(m) if m.content.is_some() => {
495+ // An inline `mod` is flattened; Nim has no nested modules in a
496+ // single file.
497+ let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
498+ for i in &items {
499+ self.collect(i)?;
500+ }
501+ for i in &items {
502+ self.item(i)?;
503+ }
504+ Ok(())
272505 }
506+ Item::Mod(m) => Err(format!(
507+ "`mod {};` refers to another file; pass that file to rustnim as \
508+ an additional input instead",
509+ m.ident
510+ )),
273511 other => Err(format!("unsupported item: {}", item_kind(other))),
274512 }
275513 }
276514
515+ /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
516+ fn none_of(&self, expect: Option<&Nim>) -> String {
517+ match expect {
518+ Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
519+ format!("rsNone[{}]()", a[0].render())
520+ }
521+ _ => "rsNone()".to_string(),
522+ }
523+ }
524+
525+ fn emit_enum(&mut self, def: &EnumDef) {
526+ let name = ident(&def.name);
527+ if def.simple {
528+ // Every variant is a unit variant, so a plain Nim enum is an exact
529+ // fit: it compares, orders and `case`-checks like Rust's.
530+ self.line(&format!("type {name}* = enum"));
531+ self.indent += 1;
532+ for v in &def.variants {
533+ self.line(&format!("{}", ident(&v.name)));
534+ }
535+ self.indent -= 1;
536+ self.blank();
537+ self.line(&format!("proc rsDebug*(x: {name}): string ="));
538+ self.indent += 1;
539+ self.line("case x");
540+ for v in &def.variants {
541+ self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
542+ }
543+ self.indent -= 1;
544+ self.blank();
545+ return;
546+ }
547+
548+ // A data-carrying enum is a Nim object variant: one discriminant enum
549+ // plus a branch per variant. This is the same shape the prelude uses
550+ // for `Option` and `Result`.
551+ self.line("type");
552+ self.indent += 1;
553+ self.line(&format!("{}Kind* = enum", name));
554+ self.indent += 1;
555+ for v in &def.variants {
556+ self.line(&def.kind_ident(&v.name));
557+ }
558+ self.indent -= 1;
559+ self.blank();
560+ self.line(&format!("{}* = object", name));
561+ self.indent += 1;
562+ self.line(&format!("case kind*: {}Kind", name));
563+ for v in &def.variants {
564+ if v.fields.is_empty() {
565+ self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
566+ } else {
567+ self.line(&format!("of {}:", def.kind_ident(&v.name)));
568+ self.indent += 1;
569+ for (f, t) in &v.fields {
570+ self.line(&format!("{}*: {}", ident(f), t.render()));
571+ }
572+ self.indent -= 1;
573+ }
574+ }
575+ self.indent -= 2;
576+ self.blank();
577+
578+ for v in &def.variants {
579+ let args: Vec<String> = v
580+ .fields
581+ .iter()
582+ .enumerate()
583+ .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
584+ .collect();
585+ let inits: Vec<String> = v
586+ .fields
587+ .iter()
588+ .enumerate()
589+ .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
590+ .collect();
591+ let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
592+ all.extend(inits);
593+ self.line(&format!(
594+ "proc {}*({}): {} = {}({})",
595+ def.ctor_ident(&v.name),
596+ args.join(", "),
597+ name,
598+ name,
599+ all.join(", ")
600+ ));
601+ }
602+ self.blank();
603+
604+ self.line(&format!("proc rsDebug*(x: {name}): string ="));
605+ self.indent += 1;
606+ self.line("case x.kind");
607+ for v in &def.variants {
608+ if v.fields.is_empty() {
609+ self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
610+ } else {
611+ let parts: Vec<String> = v
612+ .fields
613+ .iter()
614+ .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
615+ .collect();
616+ self.line(&format!(
617+ "of {}: \"{}(\" & {} & \")\"",
618+ def.kind_ident(&v.name),
619+ v.name,
620+ parts.join(" & \", \" & ")
621+ ));
622+ }
623+ }
624+ self.indent -= 1;
625+ self.blank();
626+ }
627+
628+ /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
629+ /// to the enum that declares it.
630+ fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
631+ let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
632+ let last = segs.last()?.clone();
633+ if segs.len() >= 2 {
634+ if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
635+ if def.get(&last).is_some() {
636+ return Some((def.clone(), last));
637+ }
638+ }
639+ }
640+ // Unqualified: only unambiguous if exactly one enum declares it.
641+ match self.variant_owner.get(&last) {
642+ Some(owners) if owners.len() == 1 => {
643+ let def = self.enums.get(&owners[0])?;
644+ Some((def.clone(), last))
645+ }
646+ _ => None,
647+ }
648+ }
649+
277650 fn func(
278651 &mut self,
279652 sig: &syn::Signature,
@@ -455,7 +828,7 @@ impl Lowerer {
455828 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
456829 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
457830 Pat::Type(t) => match &*t.pat {
458- Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), Some(ty::map(&t.ty)?)),
831+ Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), Some(self.map_ty(&t.ty)?)),
459832 _ => return Err("only `let <ident>` bindings are supported".into()),
460833 },
461834 Pat::Wild(_) => ("_".into(), false, None),
@@ -534,7 +907,10 @@ impl Lowerer {
534907 if w.label.is_some() {
535908 return Err("loop labels are not implemented yet".into());
536909 }
537- let c = self.expr(&w.cond)?;
910+ self.in_loop_cond = true;
911+ let c = self.expr(&w.cond);
912+ self.in_loop_cond = false;
913+ let c = c?;
538914 self.line(&format!("while {}:", c.code));
539915 let saved = self.target.take();
540916 self.nested_block(&w.body)?;
@@ -774,61 +1150,297 @@ impl Lowerer {
7741150 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
7751151 let Expr::Match(m) = e else { unreachable!() };
7761152 let scrut = self.expr(&m.expr)?;
777- // A `match` whose arms are all literal or `_` patterns is a Nim `case`,
778- // which is exhaustiveness-checked the same way. Anything richer is
779- // rejected rather than flattened into an if-chain that loses the
780- // check.
781- let name = self.fresh("Match");
7821153 let t = scrut
7831154 .ty
7841155 .clone()
7851156 .ok_or("cannot infer the type of a `match` scrutinee")?;
1157+ let name = self.fresh("Match");
7861158 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
787- self.line(&format!("case {}", name));
1159+
1160+ // A `match` whose arms neither bind nor guard is a Nim `case`, which
1161+ // is exhaustiveness-checked the way Rust's is. Anything richer becomes
1162+ // an if/elif chain, because Nim's `case` cannot destructure.
1163+ let plain = m.arms.iter().all(|a| {
1164+ !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
1165+ });
1166+ if plain {
1167+ self.match_case(m, &name, &t)
1168+ } else {
1169+ self.match_chain(m, &name, &t)
1170+ }
1171+ }
1172+
1173+ fn match_case(
1174+ &mut self,
1175+ m: &syn::ExprMatch,
1176+ name: &str,
1177+ t: &Nim,
1178+ ) -> Result<(), String> {
1179+ // A variant object is discriminated by its `kind` field.
1180+ let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
1181+ self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
7881182
7891183 let mut saw_wild = false;
7901184 for arm in &m.arms {
7911185 match &arm.pat {
792- Pat::Guard(_) => {
793- return Err("`match` guards are not implemented yet".into())
794- }
7951186 Pat::Wild(_) => {
7961187 saw_wild = true;
7971188 self.line("else:");
7981189 }
7991190 p => {
800- let labels = self.pat_labels(p, Some(&t))?;
1191+ let labels = self.pat_labels(p, Some(t))?;
8011192 self.line(&format!("of {}:", labels.join(", ")));
8021193 }
8031194 }
804- self.indent += 1;
805- let before = self.out.len();
806- match &*arm.body {
807- Expr::Block(b) => {
808- self.indent -= 1;
809- self.nested_block(&b.block)?;
810- self.indent += 1;
1195+ self.arm_body(&arm.body)?;
1196+ }
1197+ if !saw_wild && !self.case_is_total(t, m) {
1198+ // Rust checked exhaustiveness already, but Nim cannot always see
1199+ // it -- an integer `case` needs every value covered -- so make the
1200+ // unreachable arm explicit rather than leave a compile error.
1201+ self.line("else:");
1202+ self.line(" rsPanic(\"unreachable match arm\")");
1203+ }
1204+ Ok(())
1205+ }
1206+
1207+ /// Whether a Nim `case` over this type is already total, in which case
1208+ /// adding an `else` would be a compile error rather than a safety net.
1209+ fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
1210+ let Nim::Named(n, _) = t else { return false };
1211+ let Some(def) = self.enums.get(n) else { return false };
1212+ def.variants.len() == m.arms.len()
1213+ }
1214+
1215+ /// The if/elif form, for arms that bind or destructure.
1216+ fn match_chain(
1217+ &mut self,
1218+ m: &syn::ExprMatch,
1219+ name: &str,
1220+ t: &Nim,
1221+ ) -> Result<(), String> {
1222+ let mut first = true;
1223+ let mut closed = false;
1224+ for arm in &m.arms {
1225+ let (pat, guard) = match &arm.pat {
1226+ Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
1227+ p => (p, None),
1228+ };
1229+ if guard.is_some() && binds(pat) {
1230+ return Err("a `match` guard on a binding pattern is not \
1231+ implemented yet"
1232+ .into());
1233+ }
1234+ let test = self.pat_test(pat, name, t)?;
1235+ let test = match (test, guard) {
1236+ (Some(t), Some(g)) => {
1237+ let g = self.expr(g)?;
1238+ Some(format!("({}) and ({})", t, g.code))
8111239 }
812- other => {
813- let v = self.expr_stmt(other)?;
814- self.emit_tail(v);
1240+ (None, Some(g)) => Some(self.expr(g)?.code),
1241+ (t, None) => t,
1242+ };
1243+ match test {
1244+ Some(test) => {
1245+ self.line(&format!(
1246+ "{} {}:",
1247+ if first { "if" } else { "elif" },
1248+ test
1249+ ));
1250+ first = false;
1251+ }
1252+ None => {
1253+ // An irrefutable pattern: everything left falls here.
1254+ if first {
1255+ self.line("block:");
1256+ } else {
1257+ self.line("else:");
1258+ }
1259+ closed = true;
8151260 }
8161261 }
817- if self.out.len() == before {
818- self.line("discard");
819- }
1262+ self.indent += 1;
1263+ self.push_scope();
1264+ let before = self.out.len();
1265+ self.pat_bind(pat, name, t)?;
8201266 self.indent -= 1;
1267+ self.arm_body_at(&arm.body, before)?;
1268+ self.pop_scope();
1269+ if closed {
1270+ break;
1271+ }
8211272 }
822- if !saw_wild {
823- // Rust checked exhaustiveness already, but Nim cannot always see
824- // it (an integer `case` needs every value covered), so make the
825- // unreachable arm explicit rather than leaving a compile error.
1273+ if !closed {
1274+ // Rust proved this unreachable; Nim cannot see that, and leaving
1275+ // the chain open would silently fall through instead.
8261276 self.line("else:");
8271277 self.line(" rsPanic(\"unreachable match arm\")");
8281278 }
8291279 Ok(())
8301280 }
8311281
1282+ /// The condition that selects this arm, or `None` if it always matches.
1283+ fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
1284+ Ok(match p {
1285+ Pat::Wild(_) => None,
1286+ Pat::Ident(i) if i.subpat.is_none() => None,
1287+ Pat::Or(o) => {
1288+ let mut parts = Vec::new();
1289+ for c in &o.cases {
1290+ match self.pat_test(c, name, t)? {
1291+ Some(x) => parts.push(x),
1292+ None => return Ok(None),
1293+ }
1294+ }
1295+ Some(format!("({})", parts.join(" or ")))
1296+ }
1297+ Pat::Lit(_) | Pat::Range(_) => {
1298+ let labels = self.pat_labels(p, Some(t))?;
1299+ Some(match p {
1300+ Pat::Range(_) => format!("({} in {})", name, labels[0]),
1301+ _ => format!("({} == {})", name, labels[0]),
1302+ })
1303+ }
1304+ Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
1305+ Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
1306+ Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
1307+ Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
1308+ Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
1309+ _ => return Err("unsupported `match` pattern".into()),
1310+ })
1311+ }
1312+
1313+ /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
1314+ fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
1315+ let last = path_name(path);
1316+ match last.as_str() {
1317+ "Ok" => return Ok(format!("{name}.ok")),
1318+ "Err" => return Ok(format!("(not {name}.ok)")),
1319+ "Some" => return Ok(format!("{name}.has")),
1320+ "None" => return Ok(format!("(not {name}.has)")),
1321+ _ => {}
1322+ }
1323+ let Some((def, v)) = self.resolve_variant(path) else {
1324+ return Err(format!(
1325+ "`{last}` in a pattern is not a known enum variant; if it names \
1326+ an enum declared in another module, that is not implemented yet"
1327+ ));
1328+ };
1329+ if let Nim::Named(n, _) = t {
1330+ if *n != def.name {
1331+ return Err(format!(
1332+ "pattern `{}::{}` does not match the scrutinee type `{}`",
1333+ def.name, v, n
1334+ ));
1335+ }
1336+ }
1337+ Ok(if def.simple {
1338+ format!("({} == {}.{})", name, ident(&def.name), ident(&v))
1339+ } else {
1340+ format!("({}.kind == {})", name, def.kind_ident(&v))
1341+ })
1342+ }
1343+
1344+ /// Emit the `let`s that a pattern's bindings introduce.
1345+ fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
1346+ match p {
1347+ Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
1348+ Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
1349+ Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
1350+ Pat::Ident(i) if i.subpat.is_none() => {
1351+ let b = i.ident.to_string();
1352+ self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
1353+ self.bind(&b, t.clone());
1354+ Ok(())
1355+ }
1356+ Pat::TupleStruct(ts) => {
1357+ let fields = self.variant_fields(&ts.path, t)?;
1358+ for (i, sub) in ts.elems.iter().enumerate() {
1359+ let Some((fname, fty)) = fields.get(i) else {
1360+ return Err(format!(
1361+ "pattern binds {} field(s) but the variant has {}",
1362+ ts.elems.len(),
1363+ fields.len()
1364+ ));
1365+ };
1366+ let access = format!("{}.{}", name, ident(fname));
1367+ self.pat_bind(sub, &access, fty)?;
1368+ }
1369+ Ok(())
1370+ }
1371+ Pat::Struct(st) => {
1372+ let fields = self.variant_fields(&st.path, t)?;
1373+ for f in &st.fields {
1374+ let syn::Member::Named(m) = &f.member else {
1375+ return Err("unsupported struct pattern field".into());
1376+ };
1377+ let m = m.to_string();
1378+ let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
1379+ return Err(format!("unknown field `{m}` in pattern"));
1380+ };
1381+ let access = format!("{}.{}", name, ident(fname));
1382+ self.pat_bind(&f.pat, &access, fty)?;
1383+ }
1384+ Ok(())
1385+ }
1386+ _ => Err("unsupported `match` pattern".into()),
1387+ }
1388+ }
1389+
1390+ /// The payload fields a variant pattern destructures.
1391+ fn variant_fields(
1392+ &self,
1393+ path: &syn::Path,
1394+ t: &Nim,
1395+ ) -> Result<Vec<(String, Nim)>, String> {
1396+ let last = path_name(path);
1397+ // `Ok`/`Err`/`Some` read the prelude's own field names.
1398+ if let Nim::Named(n, a) = t {
1399+ match (n.as_str(), last.as_str()) {
1400+ ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
1401+ ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
1402+ ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
1403+ _ => {}
1404+ }
1405+ }
1406+ let Some((def, v)) = self.resolve_variant(path) else {
1407+ return Err(format!("`{last}` is not a known enum variant"));
1408+ };
1409+ Ok(def.get(&v).map(|v| v.fields.clone()).unwrap_or_default())
1410+ }
1411+
1412+ fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
1413+ self.indent += 1;
1414+ let before = self.out.len();
1415+ self.indent -= 1;
1416+ self.arm_body_at(body, before)
1417+ }
1418+
1419+ fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
1420+ match body {
1421+ Expr::Block(b) => self.nested_block(&b.block)?,
1422+ other => {
1423+ self.indent += 1;
1424+ // An arm's value is the `match`'s value, so it is typed by
1425+ // whatever the `match` is being assigned to -- without which
1426+ // an `Ok(..)` arm has no way to know its `Result<T, E>`.
1427+ let want = self.target.clone().and_then(|(_, t)| t);
1428+ let v = match (want, expressible(other)) {
1429+ (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
1430+ _ => self.expr_stmt(other)?,
1431+ };
1432+ self.emit_tail(v);
1433+ self.indent -= 1;
1434+ }
1435+ }
1436+ if self.out.len() == before {
1437+ self.indent += 1;
1438+ self.line("discard");
1439+ self.indent -= 1;
1440+ }
1441+ Ok(())
1442+ }
1443+
8321444 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
8331445 match p {
8341446 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
@@ -849,9 +1461,18 @@ impl Lowerer {
8491461 };
8501462 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
8511463 }
852- Pat::Path(p) => Ok(vec![ident(&path_name(&p.path))]),
1464+ Pat::Path(pp) => {
1465+ if let Some((def, v)) = self.resolve_variant(&pp.path) {
1466+ return Ok(vec![if def.simple {
1467+ format!("{}.{}", ident(&def.name), ident(&v))
1468+ } else {
1469+ def.kind_ident(&v)
1470+ }]);
1471+ }
1472+ Ok(vec![ident(&path_name(&pp.path))])
1473+ }
8531474 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
854- alternatives and `_` are implemented"
1475+ alternatives, enum variants and `_` are implemented"
8551476 .into()),
8561477 }
8571478 }
@@ -875,13 +1496,28 @@ impl Lowerer {
8751496 Expr::Lit(l) => self.lit_at(&l.lit, expect),
8761497 Expr::Path(p) => {
8771498 let name = path_name(&p.path);
878- match name.as_str() {
879- "None" => Ok(Val::untyped("rsNone()")),
880- _ => {
881- let t = self.lookup(&name);
882- Ok(Val::new(ident(&name), t))
883- }
1499+ if name == "None" {
1500+ return Ok(Val::new(self.none_of(expect), expect.cloned()));
1501+ }
1502+ // A unit enum variant used as a value: `Error::InvalidLength`.
1503+ if let Some((def, v)) = self.resolve_variant(&p.path) {
1504+ let ty = Some(Nim::Named(def.name.clone(), vec![]));
1505+ return Ok(if def.simple {
1506+ Val::new(format!("{}.{}", ident(&def.name), ident(&v)), ty)
1507+ } else {
1508+ Val::new(format!("{}()", def.ctor_ident(&v)), ty)
1509+ });
1510+ }
1511+ if let Some(t) = self.lookup(&name) {
1512+ return Ok(Val::new(ident(&name), Some(t)));
1513+ }
1514+ // A top-level function used as a value, e.g. passed to a
1515+ // parameter of `impl Fn(..)` type.
1516+ if let Some(sig) = self.fns.get(&name) {
1517+ let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone()));
1518+ return Ok(Val::new(ident(&name), Some(t)));
8841519 }
1520+ Ok(Val::new(ident(&name), None))
8851521 }
8861522 Expr::Paren(p) => {
8871523 let v = self.expr_at(&p.expr, expect)?;
@@ -926,13 +1562,44 @@ impl Lowerer {
9261562 };
9271563 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
9281564 }
929- Expr::Call(c) => self.call(c),
1565+ Expr::Try(t) => self.try_op(t),
1566+ Expr::Call(c) => self.call(c, expect),
9301567 Expr::MethodCall(m) => self.method(m),
9311568 Expr::Macro(m) => {
9321569 let code = self.macro_call(&m.mac)?;
9331570 Ok(Val::new(code, None))
9341571 }
9351572 Expr::Struct(s) => {
1573+ if s.rest.is_some() {
1574+ return Err("struct update syntax `..rest` is not implemented yet".into());
1575+ }
1576+ // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
1577+ // which is constructed positionally in Nim.
1578+ if let Some((def, v)) = self.resolve_variant(&s.path) {
1579+ let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
1580+ let mut args = vec![String::new(); fields.len()];
1581+ for f in &s.fields {
1582+ let syn::Member::Named(m) = &f.member else {
1583+ return Err("unsupported enum variant field".into());
1584+ };
1585+ let want = format!("{}_{}", v, m);
1586+ let i = fields
1587+ .iter()
1588+ .position(|(n, _)| *n == want)
1589+ .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
1590+ args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
1591+ }
1592+ if let Some(i) = args.iter().position(|a| a.is_empty()) {
1593+ return Err(format!(
1594+ "`{}::{}` is missing field `{}`",
1595+ def.name, v, fields[i].0
1596+ ));
1597+ }
1598+ return Ok(Val::new(
1599+ format!("{}({})", def.ctor_ident(&v), args.join(", ")),
1600+ Some(Nim::Named(def.name.clone(), vec![])),
1601+ ));
1602+ }
9361603 let name = path_name(&s.path);
9371604 let mut parts = Vec::new();
9381605 for f in &s.fields {
@@ -940,12 +1607,14 @@ impl Lowerer {
9401607 syn::Member::Named(n) => n.to_string(),
9411608 syn::Member::Unnamed(i) => format!("f{}", i.index),
9421609 };
943- let v = self.expr(&f.expr)?;
1610+ let want = self
1611+ .structs
1612+ .get(&name)
1613+ .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
1614+ .map(|(_, t)| t.clone());
1615+ let v = self.expr_at(&f.expr, want.as_ref())?;
9441616 parts.push(format!("{}: {}", ident(&fname), v.code));
9451617 }
946- if s.rest.is_some() {
947- return Err("struct update syntax `..rest` is not implemented yet".into());
948- }
9491618 Ok(Val::new(
9501619 format!("{}({})", ident(&name), parts.join(", ")),
9511620 Some(Nim::Named(name, vec![])),
@@ -1185,7 +1854,7 @@ impl Lowerer {
11851854
11861855 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
11871856 let v = self.expr(&c.expr)?;
1188- let to = ty::map(&c.ty)?;
1857+ let to = self.map_ty(&c.ty)?;
11891858 let from = v.ty.clone().ok_or_else(|| {
11901859 format!(
11911860 "cannot lower `as {}`: the source type is unknown, and `as` \
@@ -1235,7 +1904,71 @@ impl Lowerer {
12351904 Ok(Val::new(code, Some(to)))
12361905 }
12371906
1238- fn call(&mut self, c: &syn::ExprCall) -> Result<Val, String> {
1907+ /// Rust's `?`: return early on the error branch, otherwise yield the value.
1908+ ///
1909+ /// The early return is statements, not an expression, so they are emitted
1910+ /// ahead of the line being built. Every caller lowers its sub-expressions
1911+ /// before emitting its own line, which is what makes that ordering hold.
1912+ fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
1913+ if self.in_loop_cond {
1914+ return Err("`?` in a loop condition is not implemented yet: the \
1915+ early-return it expands to would be evaluated once, \
1916+ before the loop, rather than on each iteration"
1917+ .into());
1918+ }
1919+ let v = self.expr(&t.expr)?;
1920+ let vt = v.ty.clone().ok_or(
1921+ "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
1922+ )?;
1923+ let ret = self
1924+ .ret
1925+ .clone()
1926+ .ok_or("`?` outside a function with a return type")?;
1927+ let tmp = self.fresh("Try");
1928+ self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
1929+
1930+ match (&vt, &ret) {
1931+ (Nim::Named(a, ai), Nim::Named(b, bi))
1932+ if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
1933+ {
1934+ // Rust inserts a `From::from` on the error here. We only accept
1935+ // the case where the error types already agree, rather than
1936+ // silently dropping a conversion that might not be the identity.
1937+ if ai[1] != bi[1] {
1938+ return Err(format!(
1939+ "`?` would need `From<{}> for {}`: an error-type conversion \
1940+ is not implemented, and assuming it is the identity would \
1941+ be a guess",
1942+ ai[1].render(),
1943+ bi[1].render()
1944+ ));
1945+ }
1946+ self.line(&format!("if not {}.ok:", tmp));
1947+ self.line(&format!(
1948+ " return rsErr[{}, {}]({}.err)",
1949+ bi[0].render(),
1950+ bi[1].render(),
1951+ tmp
1952+ ));
1953+ Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
1954+ }
1955+ (Nim::Named(a, ai), Nim::Named(b, bi))
1956+ if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
1957+ {
1958+ self.line(&format!("if not {}.has:", tmp));
1959+ self.line(&format!(" return rsNone[{}]()", bi[0].render()));
1960+ Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
1961+ }
1962+ _ => Err(format!(
1963+ "`?` on `{}` in a function returning `{}` is not a supported \
1964+ combination",
1965+ vt.render(),
1966+ ret.render()
1967+ )),
1968+ }
1969+ }
1970+
1971+ fn call(&mut self, c: &syn::ExprCall, expect: Option<&Nim>) -> Result<Val, String> {
12391972 let Expr::Path(p) = &*c.func else {
12401973 return Err("only calls to named functions are supported".into());
12411974 };
@@ -1253,20 +1986,65 @@ impl Lowerer {
12531986 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
12541987
12551988 // Constructors from the prelude.
1256- // Constructors that live in the prelude rather than in the input file.
1257- if let Some(ctor) = match name.as_str() {
1258- "Some" => Some("rsSome"),
1259- "Ok" => Some("rsOk"),
1260- "Err" => Some("rsErr"),
1261- _ => None,
1262- } {
1263- return Ok(Val::new(format!("{}({})", ctor, codes.join(", ")), None));
1989+ // `Ok`/`Err` must name the *whole* Result type, not just the half
1990+ // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
1991+ match name.as_str() {
1992+ "Some" => {
1993+ let inner = match expect {
1994+ Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
1995+ _ => {
1996+ return Err("`Some(..)` needs a known `Option<T>` type here; \
1997+ annotate the binding or the return type"
1998+ .into())
1999+ }
2000+ };
2001+ return Ok(Val::new(
2002+ format!("rsSome[{}]({})", inner, codes.join(", ")),
2003+ expect.cloned(),
2004+ ));
2005+ }
2006+ "Ok" | "Err" => {
2007+ let (t, e) = match expect {
2008+ Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
2009+ (a[0].render(), a[1].render())
2010+ }
2011+ _ => {
2012+ return Err(format!(
2013+ "`{name}(..)` needs a known `Result<T, E>` type here; \
2014+ annotate the binding or the return type"
2015+ ))
2016+ }
2017+ };
2018+ let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
2019+ let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
2020+ return Ok(Val::new(
2021+ format!("{}[{}, {}]({})", ctor, t, e, arg),
2022+ expect.cloned(),
2023+ ));
2024+ }
2025+ _ => {}
2026+ }
2027+
2028+ // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
2029+ if let Some((def, v)) = self.resolve_variant(&p.path) {
2030+ return Ok(Val::new(
2031+ format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
2032+ Some(Nim::Named(def.name.clone(), vec![])),
2033+ ));
12642034 }
12652035
12662036 // A bare path that names a primitive type is Rust's tuple-struct-like
12672037 // conversion, e.g. `String::from(..)`; handled by the method path.
2038+ // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
2039+ // is invoked.
2040+ if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
2041+ return Ok(Val::new(
2042+ format!("{}({})", ident(&name), codes.join(", ")),
2043+ Some((*ret).clone()),
2044+ ));
2045+ }
12682046 let ret = self.fns.get(&name).map(|s| s.ret.clone());
1269- if ret.is_none() && !self.structs.contains_key(&name) {
2047+ if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
12702048 return Err(format!(
12712049 "call to unknown function `{name}`; only functions defined in \
12722050 this file and the supported standard-library subset can be lowered"
@@ -1311,6 +2089,39 @@ impl Lowerer {
13112089 };
13122090 (format!("unwrap({})", recv.code), inner)
13132091 }
2092+ "ok_or" => {
2093+ let inner = match &rt {
2094+ Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
2095+ _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
2096+ };
2097+ let e = args.first().ok_or("`ok_or` takes one argument")?;
2098+ let ety = e
2099+ .ty
2100+ .clone()
2101+ .ok_or("`ok_or` needs a known error type for its argument")?;
2102+ (
2103+ format!(
2104+ "rsOkOr[{}, {}]({}, {})",
2105+ inner.render(),
2106+ ety.render(),
2107+ recv.code,
2108+ e.code
2109+ ),
2110+ Some(Nim::Named("Result".into(), vec![inner, ety])),
2111+ )
2112+ }
2113+ "unwrap_or" => {
2114+ let inner = match &rt {
2115+ Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
2116+ Some(a[0].clone())
2117+ }
2118+ _ => None,
2119+ };
2120+ (
2121+ format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
2122+ inner,
2123+ )
2124+ }
13142125 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
13152126 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
13162127 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
@@ -1409,6 +2220,21 @@ impl Lowerer {
14092220 if body.trim().is_empty() {
14102221 return Ok("@[]".into());
14112222 }
2223+ // `vec![elem; n]` is the repeat form, not a list. The macro
2224+ // body has no brackets, so it is parsed directly.
2225+ if body.contains(';') {
2226+ let (v, n) = mac
2227+ .parse_body_with(|input: syn::parse::ParseStream| {
2228+ let v: Expr = input.parse()?;
2229+ input.parse::<syn::Token![;]>()?;
2230+ let n: Expr = input.parse()?;
2231+ Ok((v, n))
2232+ })
2233+ .map_err(|e| format!("vec![elem; n]: {e}"))?;
2234+ let v = self.expr(&v)?;
2235+ let n = self.expr(&n)?;
2236+ return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
2237+ }
14122238 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
14132239 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
14142240 .map_err(|e| format!("vec!: {e}"))?;
@@ -1482,6 +2308,30 @@ impl Lowerer {
14822308 }
14832309 }
14842310
2311+/// Whether a pattern introduces a binding.
2312+fn binds(p: &Pat) -> bool {
2313+ match p {
2314+ Pat::Ident(_) => true,
2315+ Pat::Guard(g) => binds(&g.pat),
2316+ Pat::Paren(x) => binds(&x.pat),
2317+ Pat::Reference(r) => binds(&r.pat),
2318+ Pat::Or(o) => o.cases.iter().any(binds),
2319+ Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
2320+ Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
2321+ _ => false,
2322+ }
2323+}
2324+
2325+/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
2326+fn destructures(p: &Pat) -> bool {
2327+ matches!(
2328+ p,
2329+ Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
2330+ ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
2331+ || matches!(p, Pat::Paren(x) if destructures(&x.pat))
2332+ || matches!(p, Pat::Reference(r) if destructures(&r.pat))
2333+}
2334+
14852335 /// Whether an expression has a direct Nim expression form.
14862336 ///
14872337 /// Nim's `if` is an expression only when every arm is a single expression, and
@@ -1516,6 +2366,60 @@ fn single_expr(b: &syn::Block) -> Option<&Expr> {
15162366 }
15172367 }
15182368
2369+/// Substitute `params[i] -> args[i]` through a type. Enough of the type
2370+/// grammar is covered to expand the aliases we accept; anything else is left
2371+/// alone and will be reported by `ty::map` if it is unsupported.
2372+fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
2373+ use syn::Type;
2374+ match t {
2375+ Type::Path(p) => {
2376+ if p.qself.is_none() && p.path.segments.len() == 1 {
2377+ let seg = &p.path.segments[0];
2378+ if seg.arguments.is_empty() {
2379+ let name = seg.ident.to_string();
2380+ if let Some(i) = params.iter().position(|x| *x == name) {
2381+ return args[i].clone();
2382+ }
2383+ }
2384+ }
2385+ let mut p = p.clone();
2386+ for seg in &mut p.path.segments {
2387+ if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
2388+ for g in &mut a.args {
2389+ if let syn::GenericArgument::Type(t) = g {
2390+ *t = substitute(t, params, args);
2391+ }
2392+ }
2393+ }
2394+ }
2395+ Type::Path(p)
2396+ }
2397+ Type::Reference(r) => {
2398+ let mut r = r.clone();
2399+ r.elem = Box::new(substitute(&r.elem, params, args));
2400+ Type::Reference(r)
2401+ }
2402+ Type::Slice(sl) => {
2403+ let mut sl = sl.clone();
2404+ sl.elem = Box::new(substitute(&sl.elem, params, args));
2405+ Type::Slice(sl)
2406+ }
2407+ Type::Array(a) => {
2408+ let mut a = a.clone();
2409+ a.elem = Box::new(substitute(&a.elem, params, args));
2410+ Type::Array(a)
2411+ }
2412+ Type::Tuple(tp) => {
2413+ let mut tp = tp.clone();
2414+ tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
2415+ Type::Tuple(tp)
2416+ }
2417+ Type::Paren(p) => substitute(&p.elem, params, args),
2418+ Type::Group(g) => substitute(&g.elem, params, args),
2419+ other => other.clone(),
2420+ }
2421+}
2422+
15192423 // --------------------------------------------------------------- utilities
15202424
15212425 fn takes_self(sig: &syn::Signature) -> bool {
@@ -1580,15 +2484,36 @@ fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
15802484 })
15812485 }
15822486
2487+fn quote_meta(m: &syn::Meta) -> String {
2488+ match m {
2489+ syn::Meta::Path(p) => path_name(p),
2490+ syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
2491+ syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
2492+ }
2493+}
2494+
2495+fn item_attrs(i: &Item) -> &[syn::Attribute] {
2496+ match i {
2497+ Item::Fn(f) => &f.attrs,
2498+ Item::Struct(s) => &s.attrs,
2499+ Item::Enum(e) => &e.attrs,
2500+ Item::Impl(x) => &x.attrs,
2501+ Item::Const(c) => &c.attrs,
2502+ Item::Type(t) => &t.attrs,
2503+ Item::Mod(m) => &m.attrs,
2504+ Item::Use(u) => &u.attrs,
2505+ Item::ExternCrate(e) => &e.attrs,
2506+ Item::Static(s) => &s.attrs,
2507+ _ => &[],
2508+ }
2509+}
2510+
15832511 fn item_kind(i: &Item) -> &'static str {
15842512 match i {
15852513 Item::Trait(_) => "`trait`",
1586- Item::Enum(_) => "`enum`",
1587- Item::Type(_) => "`type` alias",
15882514 Item::Static(_) => "`static`",
15892515 Item::Macro(_) => "macro definition",
15902516 Item::Union(_) => "`union`",
1591- Item::ExternCrate(_) => "`extern crate`",
15922517 Item::ForeignMod(_) => "`extern` block",
15932518 _ => "item",
15942519 }
@@ -10,7 +10,7 @@ use crate::fmt;
10 use crate::ty::{self, Nim};10 use crate::ty::{self, Nim};
11 use std::collections::HashMap;11 use std::collections::HashMap;
12 use syn::{12 use syn::{
13- BinOp, Expr, FnArg, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp,13+ BinOp, Expr, FnArg, GenericArgument, Item, Lit, Local, Pat, ReturnType, Stmt, UnOp,
14 };14 };
15 15
16 // --------------------------------------------------------------- vocabulary16 // --------------------------------------------------------------- vocabulary
@@ -61,6 +61,37 @@ struct Sig {
61 ret: Nim,61 ret: Nim,
62 }62 }
63 63
64+/// One variant of a Rust enum.
65+#[derive(Clone)]
66+struct Variant {
67+ name: String,
68+ /// `(nim field name, type)`. Empty for a unit variant. Tuple variants get
69+ /// `f0`, `f1`, ...; every field is prefixed with the variant name because
70+ /// Nim requires the branches of a variant object to have distinct fields.
71+ fields: Vec<(String, Nim)>,
72+}
73+
74+#[derive(Clone)]
75+struct EnumDef {
76+ name: String,
77+ /// True when every variant is a unit variant, which Nim represents as a
78+ /// plain `enum` rather than an object variant.
79+ simple: bool,
80+ variants: Vec<Variant>,
81+}
82+
83+impl EnumDef {
84+ fn kind_ident(&self, v: &str) -> String {
85+ format!("k{}{}", self.name, v)
86+ }
87+ fn ctor_ident(&self, v: &str) -> String {
88+ format!("{}{}", self.name, v)
89+ }
90+ fn get(&self, v: &str) -> Option<&Variant> {
91+ self.variants.iter().find(|x| x.name == v)
92+ }
93+}
94+
64 pub struct Lowerer {95 pub struct Lowerer {
65 out: String,96 out: String,
66 indent: usize,97 indent: usize,
@@ -68,12 +99,26 @@ pub struct Lowerer {
68 fns: HashMap<String, Sig>,99 fns: HashMap<String, Sig>,
69 /// struct name -> (field, type)100 /// struct name -> (field, type)
70 structs: HashMap<String, Vec<(String, Nim)>>,101 structs: HashMap<String, Vec<(String, Nim)>>,
102+ enums: HashMap<String, EnumDef>,
103+ /// variant name -> enums declaring it. A variant named by more than one
104+ /// enum must be written qualified, or it is rejected as ambiguous.
105+ variant_owner: HashMap<String, Vec<String>>,
106+ /// `type X<T> = ...`, expanded before any type is mapped.
107+ aliases: HashMap<String, (Vec<String>, syn::Type)>,
108+ /// Cargo features that are on, as `--cfg feature=<name>`. `#[cfg]` is
109+ /// evaluated against these exactly as rustc would, so an item that is
110+ /// dropped here is genuinely not part of the program being compiled.
111+ pub features: Vec<String>,
112+ dropped_by_cfg: usize,
71 /// Return type of the proc being lowered, so `return e` and a trailing113 /// Return type of the proc being lowered, so `return e` and a trailing
72 /// expression can type their literals the way Rust's inference would.114 /// expression can type their literals the way Rust's inference would.
73 ret: Option<Nim>,115 ret: Option<Nim>,
74 /// `(name, type)` that the arms of the `if`/`match` being lowered as a116 /// `(name, type)` that the arms of the `if`/`match` being lowered as a
75 /// statement must assign their value to.117 /// statement must assign their value to.
76 target: Option<(String, Option<Nim>)>,118 target: Option<(String, Option<Nim>)>,
119+ /// Set while lowering a `while` condition, which Nim re-evaluates each
120+ /// iteration and so cannot have statements hoisted out of it.
121+ in_loop_cond: bool,
77 tmp: usize,122 tmp: usize,
78 }123 }
79 124
@@ -85,8 +130,14 @@ impl Lowerer {
85 scopes: vec![HashMap::new()],130 scopes: vec![HashMap::new()],
86 fns: HashMap::new(),131 fns: HashMap::new(),
87 structs: HashMap::new(),132 structs: HashMap::new(),
133+ enums: HashMap::new(),
134+ variant_owner: HashMap::new(),
135+ aliases: HashMap::new(),
136+ features: Vec::new(),
137+ dropped_by_cfg: 0,
88 ret: None,138 ret: None,
89 target: None,139 target: None,
140+ in_loop_cond: false,
90 tmp: 0,141 tmp: 0,
91 }142 }
92 }143 }
@@ -162,6 +213,14 @@ impl Lowerer {
162 }213 }
163 214
164 fn collect(&mut self, item: &Item) -> Result<(), String> {215 fn collect(&mut self, item: &Item) -> Result<(), String> {
216+ // A `#[cfg(..)]` item exists only under some feature set. Dropping it
217+ // silently would change what the program does; picking a feature set
218+ // on the user's behalf would be a guess. So it is reported, except on
219+ // items that carry no runtime meaning here anyway.
220+ if !self.cfg_keeps(item_attrs(item))? {
221+ self.dropped_by_cfg += 1;
222+ return Ok(());
223+ }
165 match item {224 match item {
166 Item::Fn(f) => {225 Item::Fn(f) => {
167 let (params, ret) = self.signature(&f.sig)?;226 let (params, ret) = self.signature(&f.sig)?;
@@ -174,12 +233,64 @@ impl Lowerer {
174 Some(id) => id.to_string(),233 Some(id) => id.to_string(),
175 None => format!("f{i}"), // tuple struct234 None => format!("f{i}"), // tuple struct
176 };235 };
177- fields.push((name, ty::map(&f.ty)?.owned()));236+ fields.push((name, self.map_ty(&f.ty)?.owned()));
178 }237 }
179 self.structs.insert(s.ident.to_string(), fields);238 self.structs.insert(s.ident.to_string(), fields);
180 }239 }
240+ Item::Type(t) => {
241+ let params: Vec<String> = t
242+ .generics
243+ .params
244+ .iter()
245+ .filter_map(|g| match g {
246+ syn::GenericParam::Type(t) => Some(t.ident.to_string()),
247+ _ => None,
248+ })
249+ .collect();
250+ self.aliases
251+ .insert(t.ident.to_string(), (params, (*t.ty).clone()));
252+ }
253+ Item::Enum(e) => {
254+ let name = e.ident.to_string();
255+ if e.generics.params.iter().any(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
256+ return Err(format!("`enum {name}` is generic: not implemented yet"));
257+ }
258+ let mut variants = Vec::new();
259+ for v in &e.variants {
260+ let vname = v.ident.to_string();
261+ if v.discriminant.is_some() {
262+ return Err(format!(
263+ "`{name}::{vname}` has an explicit discriminant; Rust's \
264+ `as` on such an enum has a value this lowering does not \
265+ yet preserve"
266+ ));
267+ }
268+ let mut fields = Vec::new();
269+ for (i, f) in v.fields.iter().enumerate() {
270+ // Nim requires the branches of a variant object to have
271+ // distinct field names, so each is prefixed.
272+ let fname = match &f.ident {
273+ Some(id) => format!("{vname}_{id}"),
274+ None => format!("{vname}_f{i}"),
275+ };
276+ fields.push((fname, self.map_ty(&f.ty)?.owned()));
277+ }
278+ variants.push(Variant { name: vname, fields });
279+ }
280+ let simple = variants.iter().all(|v| v.fields.is_empty());
281+ for v in &variants {
282+ self.variant_owner
283+ .entry(v.name.clone())
284+ .or_default()
285+ .push(name.clone());
286+ }
287+ self.enums.insert(
288+ name.clone(),
289+ EnumDef { name, simple, variants },
290+ );
291+ }
181 Item::Impl(im) => {292 Item::Impl(im) => {
182- let self_ty = ty::map(&im.self_ty)?;293+ let self_ty = self.map_ty(&im.self_ty)?;
183 for it in &im.items {294 for it in &im.items {
184 if let syn::ImplItem::Fn(m) = it {295 if let syn::ImplItem::Fn(m) = it {
185 let (mut params, ret) = self.signature(&m.sig)?;296 let (mut params, ret) = self.signature(&m.sig)?;
@@ -195,25 +306,127 @@ impl Lowerer {
195 Ok(())306 Ok(())
196 }307 }
197 308
309+ /// Whether `#[cfg(..)]` keeps this item, given the enabled features.
310+ ///
311+ /// This is evaluation, not approximation: rustc does the same thing, and
312+ /// an item whose predicate is false is not part of the compiled program.
313+ /// A predicate that cannot be evaluated is reported rather than assumed.
314+ fn cfg_keeps(&self, attrs: &[syn::Attribute]) -> Result<bool, String> {
315+ for a in attrs {
316+ if a.path().is_ident("cfg") {
317+ let pred: syn::Meta = a
318+ .parse_args()
319+ .map_err(|e| format!("cannot parse `#[cfg(..)]`: {e}"))?;
320+ if !self.cfg_eval(&pred)? {
321+ return Ok(false);
322+ }
323+ }
324+ }
325+ Ok(true)
326+ }
327+
328+ fn cfg_eval(&self, m: &syn::Meta) -> Result<bool, String> {
329+ match m {
330+ syn::Meta::NameValue(nv) if nv.path.is_ident("feature") => {
331+ let syn::Expr::Lit(syn::ExprLit { lit: Lit::Str(s), .. }) = &nv.value else {
332+ return Err("`feature = ..` expects a string".into());
333+ };
334+ Ok(self.features.iter().any(|f| *f == s.value()))
335+ }
336+ syn::Meta::List(l) if l.path.is_ident("not") => {
337+ let inner: syn::Meta = l.parse_args().map_err(|e| e.to_string())?;
338+ Ok(!self.cfg_eval(&inner)?)
339+ }
340+ syn::Meta::List(l) if l.path.is_ident("all") || l.path.is_ident("any") => {
341+ let items: syn::punctuated::Punctuated<syn::Meta, syn::Token![,]> = l
342+ .parse_args_with(syn::punctuated::Punctuated::parse_terminated)
343+ .map_err(|e| e.to_string())?;
344+ let all = l.path.is_ident("all");
345+ let mut acc = all;
346+ for i in &items {
347+ let v = self.cfg_eval(i)?;
348+ acc = if all { acc && v } else { acc || v };
349+ }
350+ Ok(acc)
351+ }
352+ other => Err(format!(
353+ "`#[cfg({})]` is not a predicate rustnim can evaluate; only \
354+ `feature = \"..\"`, `not`, `all` and `any` are implemented",
355+ quote_meta(other)
356+ )),
357+ }
358+ }
359+
360+ /// Map a Rust type, expanding any `type` alias first. Every type in the
361+ /// lowering goes through here rather than calling `ty::map` directly, so
362+ /// an alias cannot be missed in one position and honoured in another.
363+ fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
364+ ty::map(&self.expand(t, 0)?)
365+ }
366+
367+ fn expand(&self, t: &syn::Type, depth: usize) -> Result<syn::Type, String> {
368+ if depth > 16 {
369+ return Err("type alias expansion did not terminate; is it cyclic?".into());
370+ }
371+ let syn::Type::Path(p) = t else { return Ok(t.clone()) };
372+ // Only an unqualified name can be one of this file's aliases.
373+ // `fmt::Result` and `core::result::Result` are different types that
374+ // merely end in the same segment.
375+ if p.path.segments.len() != 1 {
376+ return Ok(t.clone());
377+ }
378+ let Some(seg) = p.path.segments.last() else { return Ok(t.clone()) };
379+ let Some((params, target)) = self.aliases.get(&seg.ident.to_string()) else {
380+ return Ok(t.clone());
381+ };
382+ let args: Vec<syn::Type> = match &seg.arguments {
383+ syn::PathArguments::AngleBracketed(a) => a
384+ .args
385+ .iter()
386+ .filter_map(|g| match g {
387+ GenericArgument::Type(t) => Some(t.clone()),
388+ _ => None,
389+ })
390+ .collect(),
391+ _ => vec![],
392+ };
393+ if args.len() != params.len() {
394+ // Flattening several files into one module can bring a crate's own
395+ // alias (`type Result<T> = Result<T, Error>`) into scope at a site
396+ // that meant the builtin (`Result<T, E>`). Rust kept them apart by
397+ // module; here they are told apart by arity, and a use that fits
398+ // neither is left for `ty::map` to report.
399+ return Ok(t.clone());
400+ }
401+ self.expand(&substitute(target, params, &args), depth + 1)
402+ }
403+
198 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {404 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
199 if sig.asyncness.is_some() {405 if sig.asyncness.is_some() {
200 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));406 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
201 }407 }
202- if !sig.generics.params.is_empty() {408+ // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
409+ // `fn encode<'a>(..)` is not generic for our purposes. Type and const
410+ // parameters genuinely are, and are rejected.
411+ if let Some(p) = sig.generics.params.iter().find(|p| !matches!(p, syn::GenericParam::Lifetime(_))) {
412+ let what = match p {
413+ syn::GenericParam::Const(_) => "const",
414+ _ => "type",
415+ };
203 return Err(format!(416 return Err(format!(
204- "`fn {}` is generic: generics are not implemented yet",417+ "`fn {}` has a {what} parameter: generics are not implemented yet",
205 sig.ident418 sig.ident
206 ));419 ));
207 }420 }
208 let mut params = Vec::new();421 let mut params = Vec::new();
209 for a in &sig.inputs {422 for a in &sig.inputs {
210 if let FnArg::Typed(t) = a {423 if let FnArg::Typed(t) = a {
211- params.push(ty::map(&t.ty)?);424+ params.push(self.map_ty(&t.ty)?);
212 }425 }
213 }426 }
214 let ret = match &sig.output {427 let ret = match &sig.output {
215 ReturnType::Default => Nim::Unit,428 ReturnType::Default => Nim::Unit,
216- ReturnType::Type(_, t) => ty::map(t)?.owned(),429+ ReturnType::Type(_, t) => self.map_ty(t)?.owned(),
217 };430 };
218 Ok((params, ret))431 Ok((params, ret))
219 }432 }
@@ -221,6 +434,9 @@ impl Lowerer {
221 // --------------------------------------------------------------- items434 // --------------------------------------------------------------- items
222 435
223 fn item(&mut self, item: &Item) -> Result<(), String> {436 fn item(&mut self, item: &Item) -> Result<(), String> {
437+ if !self.cfg_keeps(item_attrs(item))? {
438+ return Ok(());
439+ }
224 match item {440 match item {
225 Item::Fn(f) => self.func(&f.sig, &f.block, None),441 Item::Fn(f) => self.func(&f.sig, &f.block, None),
226 Item::Struct(s) => {442 Item::Struct(s) => {
@@ -238,8 +454,14 @@ impl Lowerer {
238 self.blank();454 self.blank();
239 Ok(())455 Ok(())
240 }456 }
457+ Item::Type(_) => Ok(()), // expanded at every use site
458+ Item::Enum(e) => {
459+ let def = self.enums[&e.ident.to_string()].clone();
460+ self.emit_enum(&def);
461+ Ok(())
462+ }
241 Item::Const(c) => {463 Item::Const(c) => {
242- let t = ty::map(&c.ty)?.owned();464+ let t = self.map_ty(&c.ty)?.owned();
243 let v = self.expr(&c.expr)?;465 let v = self.expr(&c.expr)?;
244 self.bind(&c.ident.to_string(), t.clone());466 self.bind(&c.ident.to_string(), t.clone());
245 let line = format!("const {}*: {} = {}", ident(&c.ident.to_string()), t.render(), v.code);467 let line = format!("const {}*: {} = {}", ident(&c.ident.to_string()), t.render(), v.code);
@@ -248,7 +470,7 @@ impl Lowerer {
248 Ok(())470 Ok(())
249 }471 }
250 Item::Impl(im) => {472 Item::Impl(im) => {
251- let self_ty = ty::map(&im.self_ty)?;473+ let self_ty = self.map_ty(&im.self_ty)?;
252 if im.trait_.is_some() {474 if im.trait_.is_some() {
253 return Err(format!(475 return Err(format!(
254 "`impl Trait for {}`: trait impls are not implemented yet",476 "`impl Trait for {}`: trait impls are not implemented yet",
@@ -266,14 +488,165 @@ impl Lowerer {
266 }488 }
267 Ok(())489 Ok(())
268 }490 }
269- Item::Use(_) => Ok(()), // `use` has no Nim analogue in a single module491+ // `use` and `extern crate` are resolution directives with no Nim
270- Item::Mod(m) if m.content.is_none() => {492+ // analogue once everything is one module.
271- Err(format!("`mod {};` (external file) is not implemented yet", m.ident))493+ Item::Use(_) | Item::ExternCrate(_) => Ok(()),
494+ Item::Mod(m) if m.content.is_some() => {
495+ // An inline `mod` is flattened; Nim has no nested modules in a
496+ // single file.
497+ let items = m.content.as_ref().map(|(_, i)| i.clone()).unwrap_or_default();
498+ for i in &items {
499+ self.collect(i)?;
500+ }
501+ for i in &items {
502+ self.item(i)?;
503+ }
504+ Ok(())
272 }505 }
506+ Item::Mod(m) => Err(format!(
507+ "`mod {};` refers to another file; pass that file to rustnim as \
508+ an additional input instead",
509+ m.ident
510+ )),
273 other => Err(format!("unsupported item: {}", item_kind(other))),511 other => Err(format!("unsupported item: {}", item_kind(other))),
274 }512 }
275 }513 }
276 514
515+ /// `None` carries no type of its own, so Nim needs the `Option[T]` named.
516+ fn none_of(&self, expect: Option<&Nim>) -> String {
517+ match expect {
518+ Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => {
519+ format!("rsNone[{}]()", a[0].render())
520+ }
521+ _ => "rsNone()".to_string(),
522+ }
523+ }
524+
525+ fn emit_enum(&mut self, def: &EnumDef) {
526+ let name = ident(&def.name);
527+ if def.simple {
528+ // Every variant is a unit variant, so a plain Nim enum is an exact
529+ // fit: it compares, orders and `case`-checks like Rust's.
530+ self.line(&format!("type {name}* = enum"));
531+ self.indent += 1;
532+ for v in &def.variants {
533+ self.line(&format!("{}", ident(&v.name)));
534+ }
535+ self.indent -= 1;
536+ self.blank();
537+ self.line(&format!("proc rsDebug*(x: {name}): string ="));
538+ self.indent += 1;
539+ self.line("case x");
540+ for v in &def.variants {
541+ self.line(&format!("of {}.{}: \"{}\"", name, ident(&v.name), v.name));
542+ }
543+ self.indent -= 1;
544+ self.blank();
545+ return;
546+ }
547+
548+ // A data-carrying enum is a Nim object variant: one discriminant enum
549+ // plus a branch per variant. This is the same shape the prelude uses
550+ // for `Option` and `Result`.
551+ self.line("type");
552+ self.indent += 1;
553+ self.line(&format!("{}Kind* = enum", name));
554+ self.indent += 1;
555+ for v in &def.variants {
556+ self.line(&def.kind_ident(&v.name));
557+ }
558+ self.indent -= 1;
559+ self.blank();
560+ self.line(&format!("{}* = object", name));
561+ self.indent += 1;
562+ self.line(&format!("case kind*: {}Kind", name));
563+ for v in &def.variants {
564+ if v.fields.is_empty() {
565+ self.line(&format!("of {}: discard", def.kind_ident(&v.name)));
566+ } else {
567+ self.line(&format!("of {}:", def.kind_ident(&v.name)));
568+ self.indent += 1;
569+ for (f, t) in &v.fields {
570+ self.line(&format!("{}*: {}", ident(f), t.render()));
571+ }
572+ self.indent -= 1;
573+ }
574+ }
575+ self.indent -= 2;
576+ self.blank();
577+
578+ for v in &def.variants {
579+ let args: Vec<String> = v
580+ .fields
581+ .iter()
582+ .enumerate()
583+ .map(|(i, (_, t))| format!("a{}: {}", i, t.render()))
584+ .collect();
585+ let inits: Vec<String> = v
586+ .fields
587+ .iter()
588+ .enumerate()
589+ .map(|(i, (f, _))| format!("{}: a{}", ident(f), i))
590+ .collect();
591+ let mut all = vec![format!("kind: {}", def.kind_ident(&v.name))];
592+ all.extend(inits);
593+ self.line(&format!(
594+ "proc {}*({}): {} = {}({})",
595+ def.ctor_ident(&v.name),
596+ args.join(", "),
597+ name,
598+ name,
599+ all.join(", ")
600+ ));
601+ }
602+ self.blank();
603+
604+ self.line(&format!("proc rsDebug*(x: {name}): string ="));
605+ self.indent += 1;
606+ self.line("case x.kind");
607+ for v in &def.variants {
608+ if v.fields.is_empty() {
609+ self.line(&format!("of {}: \"{}\"", def.kind_ident(&v.name), v.name));
610+ } else {
611+ let parts: Vec<String> = v
612+ .fields
613+ .iter()
614+ .map(|(f, _)| format!("rsDebug(x.{})", ident(f)))
615+ .collect();
616+ self.line(&format!(
617+ "of {}: \"{}(\" & {} & \")\"",
618+ def.kind_ident(&v.name),
619+ v.name,
620+ parts.join(" & \", \" & ")
621+ ));
622+ }
623+ }
624+ self.indent -= 1;
625+ self.blank();
626+ }
627+
628+ /// Resolve a Rust path like `Error::InvalidLength` or a bare `InvalidLength`
629+ /// to the enum that declares it.
630+ fn resolve_variant(&self, path: &syn::Path) -> Option<(EnumDef, String)> {
631+ let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
632+ let last = segs.last()?.clone();
633+ if segs.len() >= 2 {
634+ if let Some(def) = self.enums.get(&segs[segs.len() - 2]) {
635+ if def.get(&last).is_some() {
636+ return Some((def.clone(), last));
637+ }
638+ }
639+ }
640+ // Unqualified: only unambiguous if exactly one enum declares it.
641+ match self.variant_owner.get(&last) {
642+ Some(owners) if owners.len() == 1 => {
643+ let def = self.enums.get(&owners[0])?;
644+ Some((def.clone(), last))
645+ }
646+ _ => None,
647+ }
648+ }
649+
277 fn func(650 fn func(
278 &mut self,651 &mut self,
279 sig: &syn::Signature,652 sig: &syn::Signature,
@@ -455,7 +828,7 @@ impl Lowerer {
455 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {828 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
456 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),829 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
457 Pat::Type(t) => match &*t.pat {830 Pat::Type(t) => match &*t.pat {
458- Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), Some(ty::map(&t.ty)?)),831+ Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), Some(self.map_ty(&t.ty)?)),
459 _ => return Err("only `let <ident>` bindings are supported".into()),832 _ => return Err("only `let <ident>` bindings are supported".into()),
460 },833 },
461 Pat::Wild(_) => ("_".into(), false, None),834 Pat::Wild(_) => ("_".into(), false, None),
@@ -534,7 +907,10 @@ impl Lowerer {
534 if w.label.is_some() {907 if w.label.is_some() {
535 return Err("loop labels are not implemented yet".into());908 return Err("loop labels are not implemented yet".into());
536 }909 }
537- let c = self.expr(&w.cond)?;910+ self.in_loop_cond = true;
911+ let c = self.expr(&w.cond);
912+ self.in_loop_cond = false;
913+ let c = c?;
538 self.line(&format!("while {}:", c.code));914 self.line(&format!("while {}:", c.code));
539 let saved = self.target.take();915 let saved = self.target.take();
540 self.nested_block(&w.body)?;916 self.nested_block(&w.body)?;
@@ -774,61 +1150,297 @@ impl Lowerer {
774 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {1150 fn match_stmt(&mut self, e: &Expr) -> Result<(), String> {
775 let Expr::Match(m) = e else { unreachable!() };1151 let Expr::Match(m) = e else { unreachable!() };
776 let scrut = self.expr(&m.expr)?;1152 let scrut = self.expr(&m.expr)?;
777- // A `match` whose arms are all literal or `_` patterns is a Nim `case`,
778- // which is exhaustiveness-checked the same way. Anything richer is
779- // rejected rather than flattened into an if-chain that loses the
780- // check.
781- let name = self.fresh("Match");
782 let t = scrut1153 let t = scrut
783 .ty1154 .ty
784 .clone()1155 .clone()
785 .ok_or("cannot infer the type of a `match` scrutinee")?;1156 .ok_or("cannot infer the type of a `match` scrutinee")?;
1157+ let name = self.fresh("Match");
786 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));1158 self.line(&format!("let {}: {} = {}", name, t.render(), scrut.code));
787- self.line(&format!("case {}", name));1159+
1160+ // A `match` whose arms neither bind nor guard is a Nim `case`, which
1161+ // is exhaustiveness-checked the way Rust's is. Anything richer becomes
1162+ // an if/elif chain, because Nim's `case` cannot destructure.
1163+ let plain = m.arms.iter().all(|a| {
1164+ !matches!(a.pat, Pat::Guard(_)) && !binds(&a.pat) && !destructures(&a.pat)
1165+ });
1166+ if plain {
1167+ self.match_case(m, &name, &t)
1168+ } else {
1169+ self.match_chain(m, &name, &t)
1170+ }
1171+ }
1172+
1173+ fn match_case(
1174+ &mut self,
1175+ m: &syn::ExprMatch,
1176+ name: &str,
1177+ t: &Nim,
1178+ ) -> Result<(), String> {
1179+ // A variant object is discriminated by its `kind` field.
1180+ let on_kind = matches!(t, Nim::Named(n, _) if self.enums.get(n).is_some_and(|d| !d.simple));
1181+ self.line(&format!("case {}{}", name, if on_kind { ".kind" } else { "" }));
788 1182
789 let mut saw_wild = false;1183 let mut saw_wild = false;
790 for arm in &m.arms {1184 for arm in &m.arms {
791 match &arm.pat {1185 match &arm.pat {
792- Pat::Guard(_) => {
793- return Err("`match` guards are not implemented yet".into())
794- }
795 Pat::Wild(_) => {1186 Pat::Wild(_) => {
796 saw_wild = true;1187 saw_wild = true;
797 self.line("else:");1188 self.line("else:");
798 }1189 }
799 p => {1190 p => {
800- let labels = self.pat_labels(p, Some(&t))?;1191+ let labels = self.pat_labels(p, Some(t))?;
801 self.line(&format!("of {}:", labels.join(", ")));1192 self.line(&format!("of {}:", labels.join(", ")));
802 }1193 }
803 }1194 }
804- self.indent += 1;1195+ self.arm_body(&arm.body)?;
805- let before = self.out.len();1196+ }
806- match &*arm.body {1197+ if !saw_wild && !self.case_is_total(t, m) {
807- Expr::Block(b) => {1198+ // Rust checked exhaustiveness already, but Nim cannot always see
808- self.indent -= 1;1199+ // it -- an integer `case` needs every value covered -- so make the
809- self.nested_block(&b.block)?;1200+ // unreachable arm explicit rather than leave a compile error.
810- self.indent += 1;1201+ self.line("else:");
1202+ self.line(" rsPanic(\"unreachable match arm\")");
1203+ }
1204+ Ok(())
1205+ }
1206+
1207+ /// Whether a Nim `case` over this type is already total, in which case
1208+ /// adding an `else` would be a compile error rather than a safety net.
1209+ fn case_is_total(&self, t: &Nim, m: &syn::ExprMatch) -> bool {
1210+ let Nim::Named(n, _) = t else { return false };
1211+ let Some(def) = self.enums.get(n) else { return false };
1212+ def.variants.len() == m.arms.len()
1213+ }
1214+
1215+ /// The if/elif form, for arms that bind or destructure.
1216+ fn match_chain(
1217+ &mut self,
1218+ m: &syn::ExprMatch,
1219+ name: &str,
1220+ t: &Nim,
1221+ ) -> Result<(), String> {
1222+ let mut first = true;
1223+ let mut closed = false;
1224+ for arm in &m.arms {
1225+ let (pat, guard) = match &arm.pat {
1226+ Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
1227+ p => (p, None),
1228+ };
1229+ if guard.is_some() && binds(pat) {
1230+ return Err("a `match` guard on a binding pattern is not \
1231+ implemented yet"
1232+ .into());
1233+ }
1234+ let test = self.pat_test(pat, name, t)?;
1235+ let test = match (test, guard) {
1236+ (Some(t), Some(g)) => {
1237+ let g = self.expr(g)?;
1238+ Some(format!("({}) and ({})", t, g.code))
811 }1239 }
812- other => {1240+ (None, Some(g)) => Some(self.expr(g)?.code),
813- let v = self.expr_stmt(other)?;1241+ (t, None) => t,
814- self.emit_tail(v);1242+ };
1243+ match test {
1244+ Some(test) => {
1245+ self.line(&format!(
1246+ "{} {}:",
1247+ if first { "if" } else { "elif" },
1248+ test
1249+ ));
1250+ first = false;
1251+ }
1252+ None => {
1253+ // An irrefutable pattern: everything left falls here.
1254+ if first {
1255+ self.line("block:");
1256+ } else {
1257+ self.line("else:");
1258+ }
1259+ closed = true;
815 }1260 }
816 }1261 }
817- if self.out.len() == before {1262+ self.indent += 1;
818- self.line("discard");1263+ self.push_scope();
819- }1264+ let before = self.out.len();
1265+ self.pat_bind(pat, name, t)?;
820 self.indent -= 1;1266 self.indent -= 1;
1267+ self.arm_body_at(&arm.body, before)?;
1268+ self.pop_scope();
1269+ if closed {
1270+ break;
1271+ }
821 }1272 }
822- if !saw_wild {1273+ if !closed {
823- // Rust checked exhaustiveness already, but Nim cannot always see1274+ // Rust proved this unreachable; Nim cannot see that, and leaving
824- // it (an integer `case` needs every value covered), so make the1275+ // the chain open would silently fall through instead.
825- // unreachable arm explicit rather than leaving a compile error.
826 self.line("else:");1276 self.line("else:");
827 self.line(" rsPanic(\"unreachable match arm\")");1277 self.line(" rsPanic(\"unreachable match arm\")");
828 }1278 }
829 Ok(())1279 Ok(())
830 }1280 }
831 1281
1282+ /// The condition that selects this arm, or `None` if it always matches.
1283+ fn pat_test(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<Option<String>, String> {
1284+ Ok(match p {
1285+ Pat::Wild(_) => None,
1286+ Pat::Ident(i) if i.subpat.is_none() => None,
1287+ Pat::Or(o) => {
1288+ let mut parts = Vec::new();
1289+ for c in &o.cases {
1290+ match self.pat_test(c, name, t)? {
1291+ Some(x) => parts.push(x),
1292+ None => return Ok(None),
1293+ }
1294+ }
1295+ Some(format!("({})", parts.join(" or ")))
1296+ }
1297+ Pat::Lit(_) | Pat::Range(_) => {
1298+ let labels = self.pat_labels(p, Some(t))?;
1299+ Some(match p {
1300+ Pat::Range(_) => format!("({} in {})", name, labels[0]),
1301+ _ => format!("({} == {})", name, labels[0]),
1302+ })
1303+ }
1304+ Pat::Path(pp) => Some(self.variant_test(&pp.path, name, t)?),
1305+ Pat::TupleStruct(ts) => Some(self.variant_test(&ts.path, name, t)?),
1306+ Pat::Struct(st) => Some(self.variant_test(&st.path, name, t)?),
1307+ Pat::Paren(pp) => return self.pat_test(&pp.pat, name, t),
1308+ Pat::Reference(r) => return self.pat_test(&r.pat, name, t),
1309+ _ => return Err("unsupported `match` pattern".into()),
1310+ })
1311+ }
1312+
1313+ /// The discriminant test for `Ok`/`Err`/`Some`/`None` or an enum variant.
1314+ fn variant_test(&self, path: &syn::Path, name: &str, t: &Nim) -> Result<String, String> {
1315+ let last = path_name(path);
1316+ match last.as_str() {
1317+ "Ok" => return Ok(format!("{name}.ok")),
1318+ "Err" => return Ok(format!("(not {name}.ok)")),
1319+ "Some" => return Ok(format!("{name}.has")),
1320+ "None" => return Ok(format!("(not {name}.has)")),
1321+ _ => {}
1322+ }
1323+ let Some((def, v)) = self.resolve_variant(path) else {
1324+ return Err(format!(
1325+ "`{last}` in a pattern is not a known enum variant; if it names \
1326+ an enum declared in another module, that is not implemented yet"
1327+ ));
1328+ };
1329+ if let Nim::Named(n, _) = t {
1330+ if *n != def.name {
1331+ return Err(format!(
1332+ "pattern `{}::{}` does not match the scrutinee type `{}`",
1333+ def.name, v, n
1334+ ));
1335+ }
1336+ }
1337+ Ok(if def.simple {
1338+ format!("({} == {}.{})", name, ident(&def.name), ident(&v))
1339+ } else {
1340+ format!("({}.kind == {})", name, def.kind_ident(&v))
1341+ })
1342+ }
1343+
1344+ /// Emit the `let`s that a pattern's bindings introduce.
1345+ fn pat_bind(&mut self, p: &Pat, name: &str, t: &Nim) -> Result<(), String> {
1346+ match p {
1347+ Pat::Wild(_) | Pat::Lit(_) | Pat::Range(_) | Pat::Path(_) | Pat::Or(_) => Ok(()),
1348+ Pat::Paren(pp) => self.pat_bind(&pp.pat, name, t),
1349+ Pat::Reference(r) => self.pat_bind(&r.pat, name, t),
1350+ Pat::Ident(i) if i.subpat.is_none() => {
1351+ let b = i.ident.to_string();
1352+ self.line(&format!("let {}: {} = {}", ident(&b), t.render(), name));
1353+ self.bind(&b, t.clone());
1354+ Ok(())
1355+ }
1356+ Pat::TupleStruct(ts) => {
1357+ let fields = self.variant_fields(&ts.path, t)?;
1358+ for (i, sub) in ts.elems.iter().enumerate() {
1359+ let Some((fname, fty)) = fields.get(i) else {
1360+ return Err(format!(
1361+ "pattern binds {} field(s) but the variant has {}",
1362+ ts.elems.len(),
1363+ fields.len()
1364+ ));
1365+ };
1366+ let access = format!("{}.{}", name, ident(fname));
1367+ self.pat_bind(sub, &access, fty)?;
1368+ }
1369+ Ok(())
1370+ }
1371+ Pat::Struct(st) => {
1372+ let fields = self.variant_fields(&st.path, t)?;
1373+ for f in &st.fields {
1374+ let syn::Member::Named(m) = &f.member else {
1375+ return Err("unsupported struct pattern field".into());
1376+ };
1377+ let m = m.to_string();
1378+ let Some((fname, fty)) = fields.iter().find(|(f, _)| f.ends_with(&m)) else {
1379+ return Err(format!("unknown field `{m}` in pattern"));
1380+ };
1381+ let access = format!("{}.{}", name, ident(fname));
1382+ self.pat_bind(&f.pat, &access, fty)?;
1383+ }
1384+ Ok(())
1385+ }
1386+ _ => Err("unsupported `match` pattern".into()),
1387+ }
1388+ }
1389+
1390+ /// The payload fields a variant pattern destructures.
1391+ fn variant_fields(
1392+ &self,
1393+ path: &syn::Path,
1394+ t: &Nim,
1395+ ) -> Result<Vec<(String, Nim)>, String> {
1396+ let last = path_name(path);
1397+ // `Ok`/`Err`/`Some` read the prelude's own field names.
1398+ if let Nim::Named(n, a) = t {
1399+ match (n.as_str(), last.as_str()) {
1400+ ("Result", "Ok") if a.len() == 2 => return Ok(vec![("val".into(), a[0].clone())]),
1401+ ("Result", "Err") if a.len() == 2 => return Ok(vec![("err".into(), a[1].clone())]),
1402+ ("Option", "Some") if a.len() == 1 => return Ok(vec![("val".into(), a[0].clone())]),
1403+ _ => {}
1404+ }
1405+ }
1406+ let Some((def, v)) = self.resolve_variant(path) else {
1407+ return Err(format!("`{last}` is not a known enum variant"));
1408+ };
1409+ Ok(def.get(&v).map(|v| v.fields.clone()).unwrap_or_default())
1410+ }
1411+
1412+ fn arm_body(&mut self, body: &Expr) -> Result<(), String> {
1413+ self.indent += 1;
1414+ let before = self.out.len();
1415+ self.indent -= 1;
1416+ self.arm_body_at(body, before)
1417+ }
1418+
1419+ fn arm_body_at(&mut self, body: &Expr, before: usize) -> Result<(), String> {
1420+ match body {
1421+ Expr::Block(b) => self.nested_block(&b.block)?,
1422+ other => {
1423+ self.indent += 1;
1424+ // An arm's value is the `match`'s value, so it is typed by
1425+ // whatever the `match` is being assigned to -- without which
1426+ // an `Ok(..)` arm has no way to know its `Result<T, E>`.
1427+ let want = self.target.clone().and_then(|(_, t)| t);
1428+ let v = match (want, expressible(other)) {
1429+ (Some(t), true) => Some(self.expr_at(other, Some(&t))?),
1430+ _ => self.expr_stmt(other)?,
1431+ };
1432+ self.emit_tail(v);
1433+ self.indent -= 1;
1434+ }
1435+ }
1436+ if self.out.len() == before {
1437+ self.indent += 1;
1438+ self.line("discard");
1439+ self.indent -= 1;
1440+ }
1441+ Ok(())
1442+ }
1443+
832 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {1444 fn pat_labels(&mut self, p: &Pat, expect: Option<&Nim>) -> Result<Vec<String>, String> {
833 match p {1445 match p {
834 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),1446 Pat::Lit(l) => Ok(vec![self.lit_at(&l.lit, expect)?.code]),
@@ -849,9 +1461,18 @@ impl Lowerer {
849 };1461 };
850 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])1462 Ok(vec![format!("{} {} {}", lo.code, op, hi.code)])
851 }1463 }
852- Pat::Path(p) => Ok(vec![ident(&path_name(&p.path))]),1464+ Pat::Path(pp) => {
1465+ if let Some((def, v)) = self.resolve_variant(&pp.path) {
1466+ return Ok(vec![if def.simple {
1467+ format!("{}.{}", ident(&def.name), ident(&v))
1468+ } else {
1469+ def.kind_ident(&v)
1470+ }]);
1471+ }
1472+ Ok(vec![ident(&path_name(&pp.path))])
1473+ }
853 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \1474 _ => Err("unsupported `match` pattern; only literals, ranges, `|` \
854- alternatives and `_` are implemented"1475+ alternatives, enum variants and `_` are implemented"
855 .into()),1476 .into()),
856 }1477 }
857 }1478 }
@@ -875,13 +1496,28 @@ impl Lowerer {
875 Expr::Lit(l) => self.lit_at(&l.lit, expect),1496 Expr::Lit(l) => self.lit_at(&l.lit, expect),
876 Expr::Path(p) => {1497 Expr::Path(p) => {
877 let name = path_name(&p.path);1498 let name = path_name(&p.path);
878- match name.as_str() {1499+ if name == "None" {
879- "None" => Ok(Val::untyped("rsNone()")),1500+ return Ok(Val::new(self.none_of(expect), expect.cloned()));
880- _ => {1501+ }
881- let t = self.lookup(&name);1502+ // A unit enum variant used as a value: `Error::InvalidLength`.
882- Ok(Val::new(ident(&name), t))1503+ if let Some((def, v)) = self.resolve_variant(&p.path) {
883- }1504+ let ty = Some(Nim::Named(def.name.clone(), vec![]));
1505+ return Ok(if def.simple {
1506+ Val::new(format!("{}.{}", ident(&def.name), ident(&v)), ty)
1507+ } else {
1508+ Val::new(format!("{}()", def.ctor_ident(&v)), ty)
1509+ });
1510+ }
1511+ if let Some(t) = self.lookup(&name) {
1512+ return Ok(Val::new(ident(&name), Some(t)));
1513+ }
1514+ // A top-level function used as a value, e.g. passed to a
1515+ // parameter of `impl Fn(..)` type.
1516+ if let Some(sig) = self.fns.get(&name) {
1517+ let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone()));
1518+ return Ok(Val::new(ident(&name), Some(t)));
884 }1519 }
1520+ Ok(Val::new(ident(&name), None))
885 }1521 }
886 Expr::Paren(p) => {1522 Expr::Paren(p) => {
887 let v = self.expr_at(&p.expr, expect)?;1523 let v = self.expr_at(&p.expr, expect)?;
@@ -926,13 +1562,44 @@ impl Lowerer {
926 };1562 };
927 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))1563 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
928 }1564 }
929- Expr::Call(c) => self.call(c),1565+ Expr::Try(t) => self.try_op(t),
1566+ Expr::Call(c) => self.call(c, expect),
930 Expr::MethodCall(m) => self.method(m),1567 Expr::MethodCall(m) => self.method(m),
931 Expr::Macro(m) => {1568 Expr::Macro(m) => {
932 let code = self.macro_call(&m.mac)?;1569 let code = self.macro_call(&m.mac)?;
933 Ok(Val::new(code, None))1570 Ok(Val::new(code, None))
934 }1571 }
935 Expr::Struct(s) => {1572 Expr::Struct(s) => {
1573+ if s.rest.is_some() {
1574+ return Err("struct update syntax `..rest` is not implemented yet".into());
1575+ }
1576+ // `Shape::Rect { w: 3, h: 5 }` is a struct-shaped *enum variant*,
1577+ // which is constructed positionally in Nim.
1578+ if let Some((def, v)) = self.resolve_variant(&s.path) {
1579+ let fields = def.get(&v).map(|v| v.fields.clone()).unwrap_or_default();
1580+ let mut args = vec![String::new(); fields.len()];
1581+ for f in &s.fields {
1582+ let syn::Member::Named(m) = &f.member else {
1583+ return Err("unsupported enum variant field".into());
1584+ };
1585+ let want = format!("{}_{}", v, m);
1586+ let i = fields
1587+ .iter()
1588+ .position(|(n, _)| *n == want)
1589+ .ok_or_else(|| format!("`{}::{}` has no field `{m}`", def.name, v))?;
1590+ args[i] = self.expr_at(&f.expr, Some(&fields[i].1))?.code;
1591+ }
1592+ if let Some(i) = args.iter().position(|a| a.is_empty()) {
1593+ return Err(format!(
1594+ "`{}::{}` is missing field `{}`",
1595+ def.name, v, fields[i].0
1596+ ));
1597+ }
1598+ return Ok(Val::new(
1599+ format!("{}({})", def.ctor_ident(&v), args.join(", ")),
1600+ Some(Nim::Named(def.name.clone(), vec![])),
1601+ ));
1602+ }
936 let name = path_name(&s.path);1603 let name = path_name(&s.path);
937 let mut parts = Vec::new();1604 let mut parts = Vec::new();
938 for f in &s.fields {1605 for f in &s.fields {
@@ -940,12 +1607,14 @@ impl Lowerer {
940 syn::Member::Named(n) => n.to_string(),1607 syn::Member::Named(n) => n.to_string(),
941 syn::Member::Unnamed(i) => format!("f{}", i.index),1608 syn::Member::Unnamed(i) => format!("f{}", i.index),
942 };1609 };
943- let v = self.expr(&f.expr)?;1610+ let want = self
1611+ .structs
1612+ .get(&name)
1613+ .and_then(|fs| fs.iter().find(|(n, _)| *n == fname))
1614+ .map(|(_, t)| t.clone());
1615+ let v = self.expr_at(&f.expr, want.as_ref())?;
944 parts.push(format!("{}: {}", ident(&fname), v.code));1616 parts.push(format!("{}: {}", ident(&fname), v.code));
945 }1617 }
946- if s.rest.is_some() {
947- return Err("struct update syntax `..rest` is not implemented yet".into());
948- }
949 Ok(Val::new(1618 Ok(Val::new(
950 format!("{}({})", ident(&name), parts.join(", ")),1619 format!("{}({})", ident(&name), parts.join(", ")),
951 Some(Nim::Named(name, vec![])),1620 Some(Nim::Named(name, vec![])),
@@ -1185,7 +1854,7 @@ impl Lowerer {
1185 1854
1186 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {1855 fn cast(&mut self, c: &syn::ExprCast) -> Result<Val, String> {
1187 let v = self.expr(&c.expr)?;1856 let v = self.expr(&c.expr)?;
1188- let to = ty::map(&c.ty)?;1857+ let to = self.map_ty(&c.ty)?;
1189 let from = v.ty.clone().ok_or_else(|| {1858 let from = v.ty.clone().ok_or_else(|| {
1190 format!(1859 format!(
1191 "cannot lower `as {}`: the source type is unknown, and `as` \1860 "cannot lower `as {}`: the source type is unknown, and `as` \
@@ -1235,7 +1904,71 @@ impl Lowerer {
1235 Ok(Val::new(code, Some(to)))1904 Ok(Val::new(code, Some(to)))
1236 }1905 }
1237 1906
1238- fn call(&mut self, c: &syn::ExprCall) -> Result<Val, String> {1907+ /// Rust's `?`: return early on the error branch, otherwise yield the value.
1908+ ///
1909+ /// The early return is statements, not an expression, so they are emitted
1910+ /// ahead of the line being built. Every caller lowers its sub-expressions
1911+ /// before emitting its own line, which is what makes that ordering hold.
1912+ fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
1913+ if self.in_loop_cond {
1914+ return Err("`?` in a loop condition is not implemented yet: the \
1915+ early-return it expands to would be evaluated once, \
1916+ before the loop, rather than on each iteration"
1917+ .into());
1918+ }
1919+ let v = self.expr(&t.expr)?;
1920+ let vt = v.ty.clone().ok_or(
1921+ "`?` needs a known `Result`/`Option` type; annotate the expression it applies to",
1922+ )?;
1923+ let ret = self
1924+ .ret
1925+ .clone()
1926+ .ok_or("`?` outside a function with a return type")?;
1927+ let tmp = self.fresh("Try");
1928+ self.line(&format!("let {}: {} = {}", tmp, vt.render(), v.code));
1929+
1930+ match (&vt, &ret) {
1931+ (Nim::Named(a, ai), Nim::Named(b, bi))
1932+ if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
1933+ {
1934+ // Rust inserts a `From::from` on the error here. We only accept
1935+ // the case where the error types already agree, rather than
1936+ // silently dropping a conversion that might not be the identity.
1937+ if ai[1] != bi[1] {
1938+ return Err(format!(
1939+ "`?` would need `From<{}> for {}`: an error-type conversion \
1940+ is not implemented, and assuming it is the identity would \
1941+ be a guess",
1942+ ai[1].render(),
1943+ bi[1].render()
1944+ ));
1945+ }
1946+ self.line(&format!("if not {}.ok:", tmp));
1947+ self.line(&format!(
1948+ " return rsErr[{}, {}]({}.err)",
1949+ bi[0].render(),
1950+ bi[1].render(),
1951+ tmp
1952+ ));
1953+ Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
1954+ }
1955+ (Nim::Named(a, ai), Nim::Named(b, bi))
1956+ if a == "Option" && b == "Option" && ai.len() == 1 && bi.len() == 1 =>
1957+ {
1958+ self.line(&format!("if not {}.has:", tmp));
1959+ self.line(&format!(" return rsNone[{}]()", bi[0].render()));
1960+ Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
1961+ }
1962+ _ => Err(format!(
1963+ "`?` on `{}` in a function returning `{}` is not a supported \
1964+ combination",
1965+ vt.render(),
1966+ ret.render()
1967+ )),
1968+ }
1969+ }
1970+
1971+ fn call(&mut self, c: &syn::ExprCall, expect: Option<&Nim>) -> Result<Val, String> {
1239 let Expr::Path(p) = &*c.func else {1972 let Expr::Path(p) = &*c.func else {
1240 return Err("only calls to named functions are supported".into());1973 return Err("only calls to named functions are supported".into());
1241 };1974 };
@@ -1253,20 +1986,65 @@ impl Lowerer {
1253 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();1986 let codes: Vec<String> = args.iter().map(|a| a.code.clone()).collect();
1254 1987
1255 // Constructors from the prelude.1988 // Constructors from the prelude.
1256- // Constructors that live in the prelude rather than in the input file.1989+ // `Ok`/`Err` must name the *whole* Result type, not just the half
1257- if let Some(ctor) = match name.as_str() {1990+ // being constructed: Nim cannot infer `E` from an `Ok(v)` alone.
1258- "Some" => Some("rsSome"),1991+ match name.as_str() {
1259- "Ok" => Some("rsOk"),1992+ "Some" => {
1260- "Err" => Some("rsErr"),1993+ let inner = match expect {
1261- _ => None,1994+ Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].render(),
1262- } {1995+ _ => {
1263- return Ok(Val::new(format!("{}({})", ctor, codes.join(", ")), None));1996+ return Err("`Some(..)` needs a known `Option<T>` type here; \
1997+ annotate the binding or the return type"
1998+ .into())
1999+ }
2000+ };
2001+ return Ok(Val::new(
2002+ format!("rsSome[{}]({})", inner, codes.join(", ")),
2003+ expect.cloned(),
2004+ ));
2005+ }
2006+ "Ok" | "Err" => {
2007+ let (t, e) = match expect {
2008+ Some(Nim::Named(n, a)) if n == "Result" && a.len() == 2 => {
2009+ (a[0].render(), a[1].render())
2010+ }
2011+ _ => {
2012+ return Err(format!(
2013+ "`{name}(..)` needs a known `Result<T, E>` type here; \
2014+ annotate the binding or the return type"
2015+ ))
2016+ }
2017+ };
2018+ let ctor = if name == "Ok" { "rsOk" } else { "rsErr" };
2019+ let arg = if codes.is_empty() { String::new() } else { codes.join(", ") };
2020+ return Ok(Val::new(
2021+ format!("{}[{}, {}]({})", ctor, t, e, arg),
2022+ expect.cloned(),
2023+ ));
2024+ }
2025+ _ => {}
2026+ }
2027+
2028+ // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
2029+ if let Some((def, v)) = self.resolve_variant(&p.path) {
2030+ return Ok(Val::new(
2031+ format!("{}({})", def.ctor_ident(&v), codes.join(", ")),
2032+ Some(Nim::Named(def.name.clone(), vec![])),
2033+ ));
1264 }2034 }
1265 2035
1266 // A bare path that names a primitive type is Rust's tuple-struct-like2036 // A bare path that names a primitive type is Rust's tuple-struct-like
1267 // conversion, e.g. `String::from(..)`; handled by the method path.2037 // conversion, e.g. `String::from(..)`; handled by the method path.
2038+ // Calling a proc-typed local, which is how an `impl Fn(..)` parameter
2039+ // is invoked.
2040+ if let Some(Nim::Proc(_, ret)) = self.lookup(&name) {
2041+ return Ok(Val::new(
2042+ format!("{}({})", ident(&name), codes.join(", ")),
2043+ Some((*ret).clone()),
2044+ ));
2045+ }
1268 let ret = self.fns.get(&name).map(|s| s.ret.clone());2046 let ret = self.fns.get(&name).map(|s| s.ret.clone());
1269- if ret.is_none() && !self.structs.contains_key(&name) {2047+ if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
1270 return Err(format!(2048 return Err(format!(
1271 "call to unknown function `{name}`; only functions defined in \2049 "call to unknown function `{name}`; only functions defined in \
1272 this file and the supported standard-library subset can be lowered"2050 this file and the supported standard-library subset can be lowered"
@@ -1311,6 +2089,39 @@ impl Lowerer {
1311 };2089 };
1312 (format!("unwrap({})", recv.code), inner)2090 (format!("unwrap({})", recv.code), inner)
1313 }2091 }
2092+ "ok_or" => {
2093+ let inner = match &rt {
2094+ Some(Nim::Named(n, a)) if n == "Option" && a.len() == 1 => a[0].clone(),
2095+ _ => return Err("`ok_or` needs a known `Option<T>` receiver".into()),
2096+ };
2097+ let e = args.first().ok_or("`ok_or` takes one argument")?;
2098+ let ety = e
2099+ .ty
2100+ .clone()
2101+ .ok_or("`ok_or` needs a known error type for its argument")?;
2102+ (
2103+ format!(
2104+ "rsOkOr[{}, {}]({}, {})",
2105+ inner.render(),
2106+ ety.render(),
2107+ recv.code,
2108+ e.code
2109+ ),
2110+ Some(Nim::Named("Result".into(), vec![inner, ety])),
2111+ )
2112+ }
2113+ "unwrap_or" => {
2114+ let inner = match &rt {
2115+ Some(Nim::Named(n, a)) if (n == "Option" && a.len() == 1) || (n == "Result" && a.len() == 2) => {
2116+ Some(a[0].clone())
2117+ }
2118+ _ => None,
2119+ };
2120+ (
2121+ format!("unwrapOr({}, {})", recv.code, a0.unwrap_or_default()),
2122+ inner,
2123+ )
2124+ }
1314 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),2125 "is_some" => (format!("{}.has", recv.code), Some(Nim::Prim("bool".into()))),
1315 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),2126 "is_none" => (format!("(not {}.has)", recv.code), Some(Nim::Prim("bool".into()))),
1316 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),2127 "is_ok" => (format!("{}.ok", recv.code), Some(Nim::Prim("bool".into()))),
@@ -1409,6 +2220,21 @@ impl Lowerer {
1409 if body.trim().is_empty() {2220 if body.trim().is_empty() {
1410 return Ok("@[]".into());2221 return Ok("@[]".into());
1411 }2222 }
2223+ // `vec![elem; n]` is the repeat form, not a list. The macro
2224+ // body has no brackets, so it is parsed directly.
2225+ if body.contains(';') {
2226+ let (v, n) = mac
2227+ .parse_body_with(|input: syn::parse::ParseStream| {
2228+ let v: Expr = input.parse()?;
2229+ input.parse::<syn::Token![;]>()?;
2230+ let n: Expr = input.parse()?;
2231+ Ok((v, n))
2232+ })
2233+ .map_err(|e| format!("vec![elem; n]: {e}"))?;
2234+ let v = self.expr(&v)?;
2235+ let n = self.expr(&n)?;
2236+ return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
2237+ }
1412 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac2238 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
1413 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)2239 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
1414 .map_err(|e| format!("vec!: {e}"))?;2240 .map_err(|e| format!("vec!: {e}"))?;
@@ -1482,6 +2308,30 @@ impl Lowerer {
1482 }2308 }
1483 }2309 }
1484 2310
2311+/// Whether a pattern introduces a binding.
2312+fn binds(p: &Pat) -> bool {
2313+ match p {
2314+ Pat::Ident(_) => true,
2315+ Pat::Guard(g) => binds(&g.pat),
2316+ Pat::Paren(x) => binds(&x.pat),
2317+ Pat::Reference(r) => binds(&r.pat),
2318+ Pat::Or(o) => o.cases.iter().any(binds),
2319+ Pat::TupleStruct(t) => t.elems.iter().any(|_| true),
2320+ Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_) => true,
2321+ _ => false,
2322+ }
2323+}
2324+
2325+/// Whether a pattern looks inside the value, which a Nim `case` cannot do.
2326+fn destructures(p: &Pat) -> bool {
2327+ matches!(
2328+ p,
2329+ Pat::TupleStruct(_) | Pat::Struct(_) | Pat::Tuple(_) | Pat::Slice(_)
2330+ ) || matches!(p, Pat::Guard(g) if destructures(&g.pat))
2331+ || matches!(p, Pat::Paren(x) if destructures(&x.pat))
2332+ || matches!(p, Pat::Reference(r) if destructures(&r.pat))
2333+}
2334+
1485 /// Whether an expression has a direct Nim expression form.2335 /// Whether an expression has a direct Nim expression form.
1486 ///2336 ///
1487 /// Nim's `if` is an expression only when every arm is a single expression, and2337 /// Nim's `if` is an expression only when every arm is a single expression, and
@@ -1516,6 +2366,60 @@ fn single_expr(b: &syn::Block) -> Option<&Expr> {
1516 }2366 }
1517 }2367 }
1518 2368
2369+/// Substitute `params[i] -> args[i]` through a type. Enough of the type
2370+/// grammar is covered to expand the aliases we accept; anything else is left
2371+/// alone and will be reported by `ty::map` if it is unsupported.
2372+fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type {
2373+ use syn::Type;
2374+ match t {
2375+ Type::Path(p) => {
2376+ if p.qself.is_none() && p.path.segments.len() == 1 {
2377+ let seg = &p.path.segments[0];
2378+ if seg.arguments.is_empty() {
2379+ let name = seg.ident.to_string();
2380+ if let Some(i) = params.iter().position(|x| *x == name) {
2381+ return args[i].clone();
2382+ }
2383+ }
2384+ }
2385+ let mut p = p.clone();
2386+ for seg in &mut p.path.segments {
2387+ if let syn::PathArguments::AngleBracketed(a) = &mut seg.arguments {
2388+ for g in &mut a.args {
2389+ if let syn::GenericArgument::Type(t) = g {
2390+ *t = substitute(t, params, args);
2391+ }
2392+ }
2393+ }
2394+ }
2395+ Type::Path(p)
2396+ }
2397+ Type::Reference(r) => {
2398+ let mut r = r.clone();
2399+ r.elem = Box::new(substitute(&r.elem, params, args));
2400+ Type::Reference(r)
2401+ }
2402+ Type::Slice(sl) => {
2403+ let mut sl = sl.clone();
2404+ sl.elem = Box::new(substitute(&sl.elem, params, args));
2405+ Type::Slice(sl)
2406+ }
2407+ Type::Array(a) => {
2408+ let mut a = a.clone();
2409+ a.elem = Box::new(substitute(&a.elem, params, args));
2410+ Type::Array(a)
2411+ }
2412+ Type::Tuple(tp) => {
2413+ let mut tp = tp.clone();
2414+ tp.elems = tp.elems.iter().map(|e| substitute(e, params, args)).collect();
2415+ Type::Tuple(tp)
2416+ }
2417+ Type::Paren(p) => substitute(&p.elem, params, args),
2418+ Type::Group(g) => substitute(&g.elem, params, args),
2419+ other => other.clone(),
2420+ }
2421+}
2422+
1519 // --------------------------------------------------------------- utilities2423 // --------------------------------------------------------------- utilities
1520 2424
1521 fn takes_self(sig: &syn::Signature) -> bool {2425 fn takes_self(sig: &syn::Signature) -> bool {
@@ -1580,15 +2484,36 @@ fn unsigned_peer(t: &Nim) -> Result<&'static str, String> {
1580 })2484 })
1581 }2485 }
1582 2486
2487+fn quote_meta(m: &syn::Meta) -> String {
2488+ match m {
2489+ syn::Meta::Path(p) => path_name(p),
2490+ syn::Meta::List(l) => format!("{}(..)", path_name(&l.path)),
2491+ syn::Meta::NameValue(nv) => format!("{} = ..", path_name(&nv.path)),
2492+ }
2493+}
2494+
2495+fn item_attrs(i: &Item) -> &[syn::Attribute] {
2496+ match i {
2497+ Item::Fn(f) => &f.attrs,
2498+ Item::Struct(s) => &s.attrs,
2499+ Item::Enum(e) => &e.attrs,
2500+ Item::Impl(x) => &x.attrs,
2501+ Item::Const(c) => &c.attrs,
2502+ Item::Type(t) => &t.attrs,
2503+ Item::Mod(m) => &m.attrs,
2504+ Item::Use(u) => &u.attrs,
2505+ Item::ExternCrate(e) => &e.attrs,
2506+ Item::Static(s) => &s.attrs,
2507+ _ => &[],
2508+ }
2509+}
2510+
1583 fn item_kind(i: &Item) -> &'static str {2511 fn item_kind(i: &Item) -> &'static str {
1584 match i {2512 match i {
1585 Item::Trait(_) => "`trait`",2513 Item::Trait(_) => "`trait`",
1586- Item::Enum(_) => "`enum`",
1587- Item::Type(_) => "`type` alias",
1588 Item::Static(_) => "`static`",2514 Item::Static(_) => "`static`",
1589 Item::Macro(_) => "macro definition",2515 Item::Macro(_) => "macro definition",
1590 Item::Union(_) => "`union`",2516 Item::Union(_) => "`union`",
1591- Item::ExternCrate(_) => "`extern crate`",
1592 Item::ForeignMod(_) => "`extern` block",2517 Item::ForeignMod(_) => "`extern` block",
1593 _ => "item",2518 _ => "item",
1594 }2519 }
modified src/main.rs +36 -14
@@ -24,8 +24,9 @@ fn main() -> ExitCode {
2424
2525 fn run() -> Result<(), String> {
2626 let mut args = std::env::args_os().skip(1);
27- let mut input: Option<PathBuf> = None;
27+ let mut inputs: Vec<PathBuf> = Vec::new();
2828 let mut output: Option<PathBuf> = None;
29+ let mut features: Vec<String> = Vec::new();
2930
3031 while let Some(a) = args.next() {
3132 match a.to_string_lossy().as_ref() {
@@ -36,28 +37,49 @@ fn run() -> Result<(), String> {
3637 .into(),
3738 );
3839 }
40+ "--cfg" => {
41+ let v = args.next().ok_or("`--cfg` needs an argument")?;
42+ let v = v.to_string_lossy().into_owned();
43+ let f = v
44+ .strip_prefix("feature=")
45+ .ok_or("only `--cfg feature=<name>` is supported")?;
46+ features.push(f.trim_matches('"').to_string());
47+ }
3948 "-h" | "--help" => {
40- println!("usage: rustnim <input.rs> [-o <output.nim>]");
49+ println!("usage: rustnim <input.rs>... [--cfg feature=<name>]... [-o <output.nim>]");
50+ println!();
51+ println!("Several inputs are concatenated into one Nim module, in the");
52+ println!("order given. That is how a multi-file crate is handled: Nim");
53+ println!("has no equivalent of Rust's per-file `mod`, so the items are");
54+ println!("flattened. Names must not collide across the files.");
4155 return Ok(());
4256 }
4357 s if s.starts_with('-') => return Err(format!("unknown flag `{s}`")),
44- _ => {
45- if input.is_some() {
46- return Err("more than one input file given".into());
47- }
48- input = Some(a.into());
49- }
58+ _ => inputs.push(a.into()),
5059 }
5160 }
5261
53- let input = input.ok_or("no input file; usage: rustnim <input.rs> [-o <output.nim>]")?;
54- let src = std::fs::read_to_string(&input)
55- .map_err(|e| format!("cannot read {}: {e}", input.display()))?;
62+ if inputs.is_empty() {
63+ return Err("no input file; usage: rustnim <input.rs>... [-o <output.nim>]".into());
64+ }
5665
57- let file: syn::File = syn::parse_file(&src)
58- .map_err(|e| format!("{}: parse error: {e}", input.display()))?;
66+ // Several files become one Nim module: Rust's `mod` has no Nim analogue
67+ // inside a single output file, so the items are flattened in argument
68+ // order. A name collision between two files is a Nim compile error, which
69+ // is a loud failure rather than a silently shadowed definition.
70+ let mut items = Vec::new();
71+ for input in &inputs {
72+ let src = std::fs::read_to_string(input)
73+ .map_err(|e| format!("cannot read {}: {e}", input.display()))?;
74+ let parsed: syn::File = syn::parse_file(&src)
75+ .map_err(|e| format!("{}: parse error: {e}", input.display()))?;
76+ items.extend(parsed.items);
77+ }
78+ let file = syn::File { shebang: None, frontmatter: None, attrs: Vec::new(), items };
5979
60- let nim = lower::Lowerer::new().lower_file(&file)?;
80+ let mut lowerer = lower::Lowerer::new();
81+ lowerer.features = features;
82+ let nim = lowerer.lower_file(&file)?;
6183
6284 match output {
6385 Some(p) => std::fs::write(&p, nim)
@@ -24,8 +24,9 @@ fn main() -> ExitCode {
24 24
25 fn run() -> Result<(), String> {25 fn run() -> Result<(), String> {
26 let mut args = std::env::args_os().skip(1);26 let mut args = std::env::args_os().skip(1);
27- let mut input: Option<PathBuf> = None;27+ let mut inputs: Vec<PathBuf> = Vec::new();
28 let mut output: Option<PathBuf> = None;28 let mut output: Option<PathBuf> = None;
29+ let mut features: Vec<String> = Vec::new();
29 30
30 while let Some(a) = args.next() {31 while let Some(a) = args.next() {
31 match a.to_string_lossy().as_ref() {32 match a.to_string_lossy().as_ref() {
@@ -36,28 +37,49 @@ fn run() -> Result<(), String> {
36 .into(),37 .into(),
37 );38 );
38 }39 }
40+ "--cfg" => {
41+ let v = args.next().ok_or("`--cfg` needs an argument")?;
42+ let v = v.to_string_lossy().into_owned();
43+ let f = v
44+ .strip_prefix("feature=")
45+ .ok_or("only `--cfg feature=<name>` is supported")?;
46+ features.push(f.trim_matches('"').to_string());
47+ }
39 "-h" | "--help" => {48 "-h" | "--help" => {
40- println!("usage: rustnim <input.rs> [-o <output.nim>]");49+ println!("usage: rustnim <input.rs>... [--cfg feature=<name>]... [-o <output.nim>]");
50+ println!();
51+ println!("Several inputs are concatenated into one Nim module, in the");
52+ println!("order given. That is how a multi-file crate is handled: Nim");
53+ println!("has no equivalent of Rust's per-file `mod`, so the items are");
54+ println!("flattened. Names must not collide across the files.");
41 return Ok(());55 return Ok(());
42 }56 }
43 s if s.starts_with('-') => return Err(format!("unknown flag `{s}`")),57 s if s.starts_with('-') => return Err(format!("unknown flag `{s}`")),
44- _ => {58+ _ => inputs.push(a.into()),
45- if input.is_some() {
46- return Err("more than one input file given".into());
47- }
48- input = Some(a.into());
49- }
50 }59 }
51 }60 }
52 61
53- let input = input.ok_or("no input file; usage: rustnim <input.rs> [-o <output.nim>]")?;62+ if inputs.is_empty() {
54- let src = std::fs::read_to_string(&input)63+ return Err("no input file; usage: rustnim <input.rs>... [-o <output.nim>]".into());
55- .map_err(|e| format!("cannot read {}: {e}", input.display()))?;64+ }
56 65
57- let file: syn::File = syn::parse_file(&src)66+ // Several files become one Nim module: Rust's `mod` has no Nim analogue
58- .map_err(|e| format!("{}: parse error: {e}", input.display()))?;67+ // inside a single output file, so the items are flattened in argument
68+ // order. A name collision between two files is a Nim compile error, which
69+ // is a loud failure rather than a silently shadowed definition.
70+ let mut items = Vec::new();
71+ for input in &inputs {
72+ let src = std::fs::read_to_string(input)
73+ .map_err(|e| format!("cannot read {}: {e}", input.display()))?;
74+ let parsed: syn::File = syn::parse_file(&src)
75+ .map_err(|e| format!("{}: parse error: {e}", input.display()))?;
76+ items.extend(parsed.items);
77+ }
78+ let file = syn::File { shebang: None, frontmatter: None, attrs: Vec::new(), items };
59 79
60- let nim = lower::Lowerer::new().lower_file(&file)?;80+ let mut lowerer = lower::Lowerer::new();
81+ lowerer.features = features;
82+ let nim = lowerer.lower_file(&file)?;
61 83
62 match output {84 match output {
63 Some(p) => std::fs::write(&p, nim)85 Some(p) => std::fs::write(&p, nim)
modified src/prelude.nim +8 -0
@@ -29,6 +29,14 @@ proc rsNone*[T](): Option[T] = Option[T](has: false)
2929 proc rsOk*[T, E](v: T): Result[T, E] = Result[T, E](ok: true, val: v)
3030 proc rsErr*[T, E](e: E): Result[T, E] = Result[T, E](ok: false, err: e)
3131
32+proc rsOkOr*[T, E](o: Option[T], e: E): Result[T, E] =
33+ if o.has: Result[T, E](ok: true, val: o.val) else: Result[T, E](ok: false, err: e)
34+
35+proc unwrapOr*[T](o: Option[T], d: T): T =
36+ if o.has: o.val else: d
37+proc unwrapOr*[T, E](r: Result[T, E], d: T): T =
38+ if r.ok: r.val else: d
39+
3240 proc unwrap*[T](o: Option[T]): T =
3341 if not o.has: rsPanic("called `Option::unwrap()` on a `None` value")
3442 o.val
@@ -29,6 +29,14 @@ proc rsNone*[T](): Option[T] = Option[T](has: false)
29 proc rsOk*[T, E](v: T): Result[T, E] = Result[T, E](ok: true, val: v)29 proc rsOk*[T, E](v: T): Result[T, E] = Result[T, E](ok: true, val: v)
30 proc rsErr*[T, E](e: E): Result[T, E] = Result[T, E](ok: false, err: e)30 proc rsErr*[T, E](e: E): Result[T, E] = Result[T, E](ok: false, err: e)
31 31
32+proc rsOkOr*[T, E](o: Option[T], e: E): Result[T, E] =
33+ if o.has: Result[T, E](ok: true, val: o.val) else: Result[T, E](ok: false, err: e)
34+
35+proc unwrapOr*[T](o: Option[T], d: T): T =
36+ if o.has: o.val else: d
37+proc unwrapOr*[T, E](r: Result[T, E], d: T): T =
38+ if r.ok: r.val else: d
39+
32 proc unwrap*[T](o: Option[T]): T =40 proc unwrap*[T](o: Option[T]): T =
33 if not o.has: rsPanic("called `Option::unwrap()` on a `None` value")41 if not o.has: rsPanic("called `Option::unwrap()` on a `None` value")
34 o.val42 o.val
modified src/ty.rs +37 -0
@@ -16,6 +16,9 @@ pub enum Nim {
1616 Tuple(Vec<Nim>),
1717 Named(String, Vec<Nim>),
1818 Var(Box<Nim>),
19+ /// `impl Fn(A) -> B` / `fn(A) -> B`. `nimcall` is the default calling
20+ /// convention for a top-level proc, which is what Rust passes here.
21+ Proc(Vec<Nim>, Box<Nim>),
1922 Unit,
2023 }
2124
@@ -36,6 +39,13 @@ impl Nim {
3639 format!("{}[{}]", n, inner.join(", "))
3740 }
3841 Nim::Var(t) => format!("var {}", t.render()),
42+ Nim::Proc(args, ret) => {
43+ let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
44+ match &**ret {
45+ Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")),
46+ r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()),
47+ }
48+ }
3949 Nim::Unit => "void".into(),
4050 }
4151 }
@@ -91,6 +101,13 @@ pub fn rejected(name: &str) -> Option<&'static str> {
91101 }
92102 }
93103
104+fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
105+ match r {
106+ syn::ReturnType::Default => Ok(Nim::Unit),
107+ syn::ReturnType::Type(_, t) => Ok(map(t)?.owned()),
108+ }
109+}
110+
94111 pub fn map(t: &Type) -> Result<Nim, String> {
95112 match t {
96113 Type::Path(p) => {
@@ -160,12 +177,32 @@ pub fn map(t: &Type) -> Result<Nim, String> {
160177 )),
161178 Type::Paren(p) => map(&p.elem),
162179 Type::Group(g) => map(&g.elem),
180+ Type::FnPtr(f) => {
181+ let args: Vec<Nim> = f
182+ .inputs
183+ .iter()
184+ .map(|a| map(&a.ty))
185+ .collect::<Result<_, _>>()?;
186+ Ok(Nim::Proc(args, Box::new(ret_ty(&f.output)?)))
187+ }
163188 Type::ImplTrait(i) => {
164189 // `impl AsRef<[u8]>` and friends: fall back to the bound's own
165190 // shape where we can recognise it, since Nim has no impl-trait.
166191 for b in &i.bounds {
167192 if let TypeParamBound::Trait(tb) = b {
168193 if let Some(seg) = tb.path.segments.last() {
194+ // `impl Fn(A) -> B` is a callable; Nim has a proc type
195+ // for exactly this.
196+ if seg.ident == "Fn" || seg.ident == "FnMut" || seg.ident == "FnOnce" {
197+ if let PathArguments::Parenthesized(a) = &seg.arguments {
198+ let args: Vec<Nim> = a
199+ .inputs
200+ .iter()
201+ .map(|a| map(&a.ty))
202+ .collect::<Result<_, _>>()?;
203+ return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
204+ }
205+ }
169206 if seg.ident == "AsRef" || seg.ident == "Into" {
170207 if let PathArguments::AngleBracketed(a) = &seg.arguments {
171208 for g in &a.args {
@@ -16,6 +16,9 @@ pub enum Nim {
16 Tuple(Vec<Nim>),16 Tuple(Vec<Nim>),
17 Named(String, Vec<Nim>),17 Named(String, Vec<Nim>),
18 Var(Box<Nim>),18 Var(Box<Nim>),
19+ /// `impl Fn(A) -> B` / `fn(A) -> B`. `nimcall` is the default calling
20+ /// convention for a top-level proc, which is what Rust passes here.
21+ Proc(Vec<Nim>, Box<Nim>),
19 Unit,22 Unit,
20 }23 }
21 24
@@ -36,6 +39,13 @@ impl Nim {
36 format!("{}[{}]", n, inner.join(", "))39 format!("{}[{}]", n, inner.join(", "))
37 }40 }
38 Nim::Var(t) => format!("var {}", t.render()),41 Nim::Var(t) => format!("var {}", t.render()),
42+ Nim::Proc(args, ret) => {
43+ let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
44+ match &**ret {
45+ Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")),
46+ r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()),
47+ }
48+ }
39 Nim::Unit => "void".into(),49 Nim::Unit => "void".into(),
40 }50 }
41 }51 }
@@ -91,6 +101,13 @@ pub fn rejected(name: &str) -> Option<&'static str> {
91 }101 }
92 }102 }
93 103
104+fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
105+ match r {
106+ syn::ReturnType::Default => Ok(Nim::Unit),
107+ syn::ReturnType::Type(_, t) => Ok(map(t)?.owned()),
108+ }
109+}
110+
94 pub fn map(t: &Type) -> Result<Nim, String> {111 pub fn map(t: &Type) -> Result<Nim, String> {
95 match t {112 match t {
96 Type::Path(p) => {113 Type::Path(p) => {
@@ -160,12 +177,32 @@ pub fn map(t: &Type) -> Result<Nim, String> {
160 )),177 )),
161 Type::Paren(p) => map(&p.elem),178 Type::Paren(p) => map(&p.elem),
162 Type::Group(g) => map(&g.elem),179 Type::Group(g) => map(&g.elem),
180+ Type::FnPtr(f) => {
181+ let args: Vec<Nim> = f
182+ .inputs
183+ .iter()
184+ .map(|a| map(&a.ty))
185+ .collect::<Result<_, _>>()?;
186+ Ok(Nim::Proc(args, Box::new(ret_ty(&f.output)?)))
187+ }
163 Type::ImplTrait(i) => {188 Type::ImplTrait(i) => {
164 // `impl AsRef<[u8]>` and friends: fall back to the bound's own189 // `impl AsRef<[u8]>` and friends: fall back to the bound's own
165 // shape where we can recognise it, since Nim has no impl-trait.190 // shape where we can recognise it, since Nim has no impl-trait.
166 for b in &i.bounds {191 for b in &i.bounds {
167 if let TypeParamBound::Trait(tb) = b {192 if let TypeParamBound::Trait(tb) = b {
168 if let Some(seg) = tb.path.segments.last() {193 if let Some(seg) = tb.path.segments.last() {
194+ // `impl Fn(A) -> B` is a callable; Nim has a proc type
195+ // for exactly this.
196+ if seg.ident == "Fn" || seg.ident == "FnMut" || seg.ident == "FnOnce" {
197+ if let PathArguments::Parenthesized(a) = &seg.arguments {
198+ let args: Vec<Nim> = a
199+ .inputs
200+ .iter()
201+ .map(|a| map(&a.ty))
202+ .collect::<Result<_, _>>()?;
203+ return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
204+ }
205+ }
169 if seg.ident == "AsRef" || seg.ident == "Into" {206 if seg.ident == "AsRef" || seg.ident == "Into" {
170 if let PathArguments::AngleBracketed(a) = &seg.arguments {207 if let PathArguments::AngleBracketed(a) = &seg.arguments {
171 for g in &a.args {208 for g in &a.args {
added tests/cases/017-enum-simple.rs +21 -0
new file mode 100644
@@ -0,0 +1,21 @@
1+// A C-like enum is a plain Nim enum: it compares and `case`-checks like Rust's.
2+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
3+enum Error {
4+ InvalidEncoding,
5+ InvalidLength,
6+}
7+
8+fn describe(e: Error) -> i32 {
9+ match e {
10+ Error::InvalidEncoding => 1,
11+ Error::InvalidLength => 2,
12+ }
13+}
14+
15+fn main() {
16+ let a = Error::InvalidEncoding;
17+ let b = Error::InvalidLength;
18+ println!("{} {}", describe(a), describe(b));
19+ println!("{:?} {:?}", a, b);
20+ println!("{} {}", a == b, a == Error::InvalidEncoding);
21+}
new file mode 100644
@@ -0,0 +1,21 @@
1+// A C-like enum is a plain Nim enum: it compares and `case`-checks like Rust's.
2+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
3+enum Error {
4+ InvalidEncoding,
5+ InvalidLength,
6+}
7+
8+fn describe(e: Error) -> i32 {
9+ match e {
10+ Error::InvalidEncoding => 1,
11+ Error::InvalidLength => 2,
12+ }
13+}
14+
15+fn main() {
16+ let a = Error::InvalidEncoding;
17+ let b = Error::InvalidLength;
18+ println!("{} {}", describe(a), describe(b));
19+ println!("{:?} {:?}", a, b);
20+ println!("{} {}", a == b, a == Error::InvalidEncoding);
21+}
added tests/cases/018-enum-payload.rs +24 -0
new file mode 100644
@@ -0,0 +1,24 @@
1+// A data-carrying enum becomes a Nim object variant, and a pattern that binds
2+// becomes an if/elif chain, because Nim's `case` cannot destructure.
3+#[derive(Debug)]
4+enum Shape {
5+ Empty,
6+ Square(i32),
7+ Rect { w: i32, h: i32 },
8+}
9+
10+fn area(s: Shape) -> i32 {
11+ match s {
12+ Shape::Empty => 0,
13+ Shape::Square(a) => a * a,
14+ Shape::Rect { w, h } => w * h,
15+ }
16+}
17+
18+fn main() {
19+ println!("{}", area(Shape::Empty));
20+ println!("{}", area(Shape::Square(4)));
21+ println!("{}", area(Shape::Rect { w: 3, h: 5 }));
22+ println!("{:?}", Shape::Square(4));
23+ println!("{:?}", Shape::Empty);
24+}
new file mode 100644
@@ -0,0 +1,24 @@
1+// A data-carrying enum becomes a Nim object variant, and a pattern that binds
2+// becomes an if/elif chain, because Nim's `case` cannot destructure.
3+#[derive(Debug)]
4+enum Shape {
5+ Empty,
6+ Square(i32),
7+ Rect { w: i32, h: i32 },
8+}
9+
10+fn area(s: Shape) -> i32 {
11+ match s {
12+ Shape::Empty => 0,
13+ Shape::Square(a) => a * a,
14+ Shape::Rect { w, h } => w * h,
15+ }
16+}
17+
18+fn main() {
19+ println!("{}", area(Shape::Empty));
20+ println!("{}", area(Shape::Square(4)));
21+ println!("{}", area(Shape::Rect { w: 3, h: 5 }));
22+ println!("{:?}", Shape::Square(4));
23+ println!("{:?}", Shape::Empty);
24+}
added tests/cases/019-result.rs +25 -0
new file mode 100644
@@ -0,0 +1,25 @@
1+// Result-returning functions, Ok/Err construction, and matching on both.
2+#[derive(Debug, PartialEq)]
3+enum Error {
4+ TooBig,
5+}
6+
7+fn halve(n: i32) -> Result<i32, Error> {
8+ if n > 100 {
9+ Err(Error::TooBig)
10+ } else {
11+ Ok(n / 2)
12+ }
13+}
14+
15+fn main() {
16+ for n in [10, 200] {
17+ match halve(n) {
18+ Ok(v) => println!("ok {}", v),
19+ Err(e) => println!("err {:?}", e),
20+ }
21+ }
22+ println!("{:?}", halve(8));
23+ println!("{}", halve(8).unwrap());
24+ println!("{} {}", halve(8).is_ok(), halve(500).is_err());
25+}
new file mode 100644
@@ -0,0 +1,25 @@
1+// Result-returning functions, Ok/Err construction, and matching on both.
2+#[derive(Debug, PartialEq)]
3+enum Error {
4+ TooBig,
5+}
6+
7+fn halve(n: i32) -> Result<i32, Error> {
8+ if n > 100 {
9+ Err(Error::TooBig)
10+ } else {
11+ Ok(n / 2)
12+ }
13+}
14+
15+fn main() {
16+ for n in [10, 200] {
17+ match halve(n) {
18+ Ok(v) => println!("ok {}", v),
19+ Err(e) => println!("err {:?}", e),
20+ }
21+ }
22+ println!("{:?}", halve(8));
23+ println!("{}", halve(8).unwrap());
24+ println!("{} {}", halve(8).is_ok(), halve(500).is_err());
25+}
added tests/cases/020-question-mark.rs +24 -0
new file mode 100644
@@ -0,0 +1,24 @@
1+// `?` expands to an early return on the error branch.
2+#[derive(Debug)]
3+enum Error {
4+ Odd,
5+}
6+
7+fn halve(n: i32) -> Result<i32, Error> {
8+ if n % 2 != 0 {
9+ return Err(Error::Odd);
10+ }
11+ Ok(n / 2)
12+}
13+
14+fn quarter(n: i32) -> Result<i32, Error> {
15+ let half = halve(n)?;
16+ let q = halve(half)?;
17+ Ok(q)
18+}
19+
20+fn main() {
21+ println!("{:?}", quarter(8));
22+ println!("{:?}", quarter(6));
23+ println!("{:?}", quarter(5));
24+}
new file mode 100644
@@ -0,0 +1,24 @@
1+// `?` expands to an early return on the error branch.
2+#[derive(Debug)]
3+enum Error {
4+ Odd,
5+}
6+
7+fn halve(n: i32) -> Result<i32, Error> {
8+ if n % 2 != 0 {
9+ return Err(Error::Odd);
10+ }
11+ Ok(n / 2)
12+}
13+
14+fn quarter(n: i32) -> Result<i32, Error> {
15+ let half = halve(n)?;
16+ let q = halve(half)?;
17+ Ok(q)
18+}
19+
20+fn main() {
21+ println!("{:?}", quarter(8));
22+ println!("{:?}", quarter(6));
23+ println!("{:?}", quarter(5));
24+}
added tests/cases/021-option.rs +25 -0
new file mode 100644
@@ -0,0 +1,25 @@
1+// Option, and the type alias expansion that `Result<T>` in base16ct needs.
2+type MyResult<T> = Result<T, i32>;
3+
4+fn first_even(a: i32, b: i32) -> Option<i32> {
5+ if a % 2 == 0 {
6+ Some(a)
7+ } else if b % 2 == 0 {
8+ Some(b)
9+ } else {
10+ None
11+ }
12+}
13+
14+fn checked(n: i32) -> MyResult<i32> {
15+ let v: Option<i32> = first_even(n, n + 1);
16+ let got: i32 = v.ok_or(-1)?;
17+ Ok(got * 10)
18+}
19+
20+fn main() {
21+ println!("{:?} {:?}", first_even(2, 3), first_even(1, 3));
22+ println!("{}", first_even(1, 4).unwrap());
23+ println!("{}", first_even(1, 3).unwrap_or(-7));
24+ println!("{:?} {:?}", checked(2), checked(1));
25+}
new file mode 100644
@@ -0,0 +1,25 @@
1+// Option, and the type alias expansion that `Result<T>` in base16ct needs.
2+type MyResult<T> = Result<T, i32>;
3+
4+fn first_even(a: i32, b: i32) -> Option<i32> {
5+ if a % 2 == 0 {
6+ Some(a)
7+ } else if b % 2 == 0 {
8+ Some(b)
9+ } else {
10+ None
11+ }
12+}
13+
14+fn checked(n: i32) -> MyResult<i32> {
15+ let v: Option<i32> = first_even(n, n + 1);
16+ let got: i32 = v.ok_or(-1)?;
17+ Ok(got * 10)
18+}
19+
20+fn main() {
21+ println!("{:?} {:?}", first_even(2, 3), first_even(1, 3));
22+ println!("{}", first_even(1, 4).unwrap());
23+ println!("{}", first_even(1, 3).unwrap_or(-7));
24+ println!("{:?} {:?}", checked(2), checked(1));
25+}
added tests/cases/022-base16ct-decode.rs +89 -0
new file mode 100644
@@ -0,0 +1,89 @@
1+// Milestone 1, as far as it currently reaches.
2+//
3+// `Error`, `decoded_len` and `decode_nibble` are base16ct 1.0.0 verbatim.
4+// `decode_into` is NOT: base16ct writes that loop as
5+// for (src, dst) in src.chunks_exact(2).zip(dst.iter_mut())
6+// and `chunks_exact`/`zip`/`iter_mut` are not implemented yet, so the loop is
7+// rewritten with indexing. The arithmetic under test -- the constant-time
8+// nibble decode, its i16 wrapping and arithmetic shift, and the `err` accumulator
9+// -- is unchanged, and that is the part the other transpiler could not represent.
10+
11+#[derive(Clone, Copy, Eq, PartialEq, Debug)]
12+pub enum Error {
13+ InvalidEncoding,
14+ InvalidLength,
15+}
16+
17+pub type Result<T> = core::result::Result<T, Error>;
18+
19+pub fn decoded_len(bytes: &[u8]) -> Result<usize> {
20+ if bytes.len() & 1 == 0 {
21+ Ok(bytes.len() / 2)
22+ } else {
23+ Err(Error::InvalidLength)
24+ }
25+}
26+
27+fn decode_nibble(src: u8) -> u16 {
28+ let byte = src as i16;
29+ let mut ret: i16 = -1;
30+
31+ ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
32+ ret += (((0x40i16 - byte) & (byte - 0x47)) >> 8) & (byte - 54);
33+ ret += (((0x60i16 - byte) & (byte - 0x67)) >> 8) & (byte - 86);
34+
35+ ret as u16
36+}
37+
38+fn decode_into(src: &[u8], dst: &mut [u8]) -> Result<usize> {
39+ let n: usize = decoded_len(src)?;
40+ if dst.len() < n {
41+ return Err(Error::InvalidLength);
42+ }
43+
44+ let mut err: u16 = 0;
45+ let mut i: usize = 0;
46+ while i < n {
47+ let byte = (decode_nibble(src[i * 2]) << 4) | decode_nibble(src[i * 2 + 1]);
48+ err |= byte >> 8;
49+ dst[i] = byte as u8;
50+ i += 1;
51+ }
52+
53+ match err {
54+ 0 => Ok(n),
55+ _ => Err(Error::InvalidEncoding),
56+ }
57+}
58+
59+fn show(hex: &[u8]) {
60+ let mut buf: Vec<u8> = vec![0u8; 16];
61+ match decode_into(hex, &mut buf) {
62+ Ok(n) => {
63+ let mut i: usize = 0;
64+ while i < n {
65+ print!("{:02x}", buf[i]);
66+ i += 1;
67+ }
68+ println!(" ({} bytes)", n);
69+ }
70+ Err(e) => println!("error {:?}", e),
71+ }
72+}
73+
74+fn main() {
75+ show(b"abcd1234");
76+ show(b"ABCD1234");
77+ show(b"abCD1234");
78+ show(b"00ff7f80");
79+ show(b"abc");
80+ show(b"zzzz");
81+ show(b"");
82+
83+ let mut c: u8 = 0;
84+ while c < 128 {
85+ print!("{} ", decode_nibble(c));
86+ c += 1;
87+ }
88+ println!("");
89+}
new file mode 100644
@@ -0,0 +1,89 @@
1+// Milestone 1, as far as it currently reaches.
2+//
3+// `Error`, `decoded_len` and `decode_nibble` are base16ct 1.0.0 verbatim.
4+// `decode_into` is NOT: base16ct writes that loop as
5+// for (src, dst) in src.chunks_exact(2).zip(dst.iter_mut())
6+// and `chunks_exact`/`zip`/`iter_mut` are not implemented yet, so the loop is
7+// rewritten with indexing. The arithmetic under test -- the constant-time
8+// nibble decode, its i16 wrapping and arithmetic shift, and the `err` accumulator
9+// -- is unchanged, and that is the part the other transpiler could not represent.
10+
11+#[derive(Clone, Copy, Eq, PartialEq, Debug)]
12+pub enum Error {
13+ InvalidEncoding,
14+ InvalidLength,
15+}
16+
17+pub type Result<T> = core::result::Result<T, Error>;
18+
19+pub fn decoded_len(bytes: &[u8]) -> Result<usize> {
20+ if bytes.len() & 1 == 0 {
21+ Ok(bytes.len() / 2)
22+ } else {
23+ Err(Error::InvalidLength)
24+ }
25+}
26+
27+fn decode_nibble(src: u8) -> u16 {
28+ let byte = src as i16;
29+ let mut ret: i16 = -1;
30+
31+ ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
32+ ret += (((0x40i16 - byte) & (byte - 0x47)) >> 8) & (byte - 54);
33+ ret += (((0x60i16 - byte) & (byte - 0x67)) >> 8) & (byte - 86);
34+
35+ ret as u16
36+}
37+
38+fn decode_into(src: &[u8], dst: &mut [u8]) -> Result<usize> {
39+ let n: usize = decoded_len(src)?;
40+ if dst.len() < n {
41+ return Err(Error::InvalidLength);
42+ }
43+
44+ let mut err: u16 = 0;
45+ let mut i: usize = 0;
46+ while i < n {
47+ let byte = (decode_nibble(src[i * 2]) << 4) | decode_nibble(src[i * 2 + 1]);
48+ err |= byte >> 8;
49+ dst[i] = byte as u8;
50+ i += 1;
51+ }
52+
53+ match err {
54+ 0 => Ok(n),
55+ _ => Err(Error::InvalidEncoding),
56+ }
57+}
58+
59+fn show(hex: &[u8]) {
60+ let mut buf: Vec<u8> = vec![0u8; 16];
61+ match decode_into(hex, &mut buf) {
62+ Ok(n) => {
63+ let mut i: usize = 0;
64+ while i < n {
65+ print!("{:02x}", buf[i]);
66+ i += 1;
67+ }
68+ println!(" ({} bytes)", n);
69+ }
70+ Err(e) => println!("error {:?}", e),
71+ }
72+}
73+
74+fn main() {
75+ show(b"abcd1234");
76+ show(b"ABCD1234");
77+ show(b"abCD1234");
78+ show(b"00ff7f80");
79+ show(b"abc");
80+ show(b"zzzz");
81+ show(b"");
82+
83+ let mut c: u8 = 0;
84+ while c < 128 {
85+ print!("{} ", decode_nibble(c));
86+ c += 1;
87+ }
88+ println!("");
89+}
added tests/multifile.rs +96 -0
new file mode 100644
@@ -0,0 +1,96 @@
1+//! Multi-file input and `#[cfg]` evaluation.
2+//!
3+//! The differential runner drives one `.rs` per case, so the multi-file path
4+//! and the feature-gating that goes with it are checked here instead. A Rust
5+//! crate splits across files with `mod`; Nim has no equivalent inside a single
6+//! output file, so the items are flattened in argument order.
7+
8+use std::fs;
9+use std::path::{Path, PathBuf};
10+use std::process::Command;
11+
12+const RUSTNIM: &str = env!("CARGO_BIN_EXE_rustnim");
13+
14+fn work(name: &str) -> PathBuf {
15+ let d = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/.work").join(name);
16+ let _ = fs::remove_dir_all(&d);
17+ fs::create_dir_all(&d).unwrap();
18+ d
19+}
20+
21+fn run(dir: &Path, args: &[&str]) -> (bool, String, String) {
22+ let out = Command::new(RUSTNIM)
23+ .args(args)
24+ .env("TMPDIR", dir)
25+ .output()
26+ .expect("spawn rustnim");
27+ (
28+ out.status.success(),
29+ String::from_utf8_lossy(&out.stdout).into_owned(),
30+ String::from_utf8_lossy(&out.stderr).into_owned(),
31+ )
32+}
33+
34+#[test]
35+fn files_are_flattened_into_one_module() {
36+ let d = work("multifile");
37+ let a = d.join("a.rs");
38+ let b = d.join("b.rs");
39+ fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap();
40+ fs::write(
41+ &b,
42+ "fn main() { println!(\"{}\", double(21)); }\n",
43+ )
44+ .unwrap();
45+
46+ // `double` is defined in a.rs and called from b.rs: it resolves only
47+ // because both were passed in.
48+ let (ok, out, err) = run(&d, &[a.to_str().unwrap(), b.to_str().unwrap()]);
49+ assert!(ok, "multi-file transpile failed: {err}");
50+ assert!(out.contains("proc double"), "missing `double`:\n{out}");
51+ assert!(out.contains("proc main"), "missing `main`:\n{out}");
52+
53+ // b.rs alone must fail rather than emit a call to something undefined.
54+ let (ok, _, err) = run(&d, &[b.to_str().unwrap()]);
55+ assert!(!ok, "b.rs alone should not transpile");
56+ assert!(err.contains("unknown function `double`"), "unexpected: {err}");
57+}
58+
59+#[test]
60+fn cfg_is_evaluated_against_the_feature_set() {
61+ let d = work("cfg");
62+ let f = d.join("c.rs");
63+ fs::write(
64+ &f,
65+ "#[cfg(feature = \"extra\")]\n\
66+ fn extra() -> i32 { 1 }\n\
67+ #[cfg(not(feature = \"extra\"))]\n\
68+ fn plain() -> i32 { 2 }\n\
69+ fn main() {}\n",
70+ )
71+ .unwrap();
72+
73+ let (ok, out, err) = run(&d, &[f.to_str().unwrap()]);
74+ assert!(ok, "{err}");
75+ assert!(!out.contains("proc extra"), "gated-off item was emitted:\n{out}");
76+ assert!(out.contains("proc plain"), "gated-on item missing:\n{out}");
77+
78+ let (ok, out, err) = run(&d, &[f.to_str().unwrap(), "--cfg", "feature=extra"]);
79+ assert!(ok, "{err}");
80+ assert!(out.contains("proc extra"), "feature item missing:\n{out}");
81+ assert!(!out.contains("proc plain"), "`not(feature)` item was emitted:\n{out}");
82+}
83+
84+#[test]
85+fn an_unevaluable_cfg_predicate_is_reported_not_assumed() {
86+ let d = work("cfg-unknown");
87+ let f = d.join("d.rs");
88+ fs::write(
89+ &f,
90+ "#[cfg(target_os = \"linux\")]\nfn only_linux() -> i32 { 1 }\nfn main() {}\n",
91+ )
92+ .unwrap();
93+ let (ok, _, err) = run(&d, &[f.to_str().unwrap()]);
94+ assert!(!ok, "an unevaluable cfg must not be silently resolved");
95+ assert!(err.contains("not a predicate rustnim can evaluate"), "unexpected: {err}");
96+}
new file mode 100644
@@ -0,0 +1,96 @@
1+//! Multi-file input and `#[cfg]` evaluation.
2+//!
3+//! The differential runner drives one `.rs` per case, so the multi-file path
4+//! and the feature-gating that goes with it are checked here instead. A Rust
5+//! crate splits across files with `mod`; Nim has no equivalent inside a single
6+//! output file, so the items are flattened in argument order.
7+
8+use std::fs;
9+use std::path::{Path, PathBuf};
10+use std::process::Command;
11+
12+const RUSTNIM: &str = env!("CARGO_BIN_EXE_rustnim");
13+
14+fn work(name: &str) -> PathBuf {
15+ let d = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/.work").join(name);
16+ let _ = fs::remove_dir_all(&d);
17+ fs::create_dir_all(&d).unwrap();
18+ d
19+}
20+
21+fn run(dir: &Path, args: &[&str]) -> (bool, String, String) {
22+ let out = Command::new(RUSTNIM)
23+ .args(args)
24+ .env("TMPDIR", dir)
25+ .output()
26+ .expect("spawn rustnim");
27+ (
28+ out.status.success(),
29+ String::from_utf8_lossy(&out.stdout).into_owned(),
30+ String::from_utf8_lossy(&out.stderr).into_owned(),
31+ )
32+}
33+
34+#[test]
35+fn files_are_flattened_into_one_module() {
36+ let d = work("multifile");
37+ let a = d.join("a.rs");
38+ let b = d.join("b.rs");
39+ fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap();
40+ fs::write(
41+ &b,
42+ "fn main() { println!(\"{}\", double(21)); }\n",
43+ )
44+ .unwrap();
45+
46+ // `double` is defined in a.rs and called from b.rs: it resolves only
47+ // because both were passed in.
48+ let (ok, out, err) = run(&d, &[a.to_str().unwrap(), b.to_str().unwrap()]);
49+ assert!(ok, "multi-file transpile failed: {err}");
50+ assert!(out.contains("proc double"), "missing `double`:\n{out}");
51+ assert!(out.contains("proc main"), "missing `main`:\n{out}");
52+
53+ // b.rs alone must fail rather than emit a call to something undefined.
54+ let (ok, _, err) = run(&d, &[b.to_str().unwrap()]);
55+ assert!(!ok, "b.rs alone should not transpile");
56+ assert!(err.contains("unknown function `double`"), "unexpected: {err}");
57+}
58+
59+#[test]
60+fn cfg_is_evaluated_against_the_feature_set() {
61+ let d = work("cfg");
62+ let f = d.join("c.rs");
63+ fs::write(
64+ &f,
65+ "#[cfg(feature = \"extra\")]\n\
66+ fn extra() -> i32 { 1 }\n\
67+ #[cfg(not(feature = \"extra\"))]\n\
68+ fn plain() -> i32 { 2 }\n\
69+ fn main() {}\n",
70+ )
71+ .unwrap();
72+
73+ let (ok, out, err) = run(&d, &[f.to_str().unwrap()]);
74+ assert!(ok, "{err}");
75+ assert!(!out.contains("proc extra"), "gated-off item was emitted:\n{out}");
76+ assert!(out.contains("proc plain"), "gated-on item missing:\n{out}");
77+
78+ let (ok, out, err) = run(&d, &[f.to_str().unwrap(), "--cfg", "feature=extra"]);
79+ assert!(ok, "{err}");
80+ assert!(out.contains("proc extra"), "feature item missing:\n{out}");
81+ assert!(!out.contains("proc plain"), "`not(feature)` item was emitted:\n{out}");
82+}
83+
84+#[test]
85+fn an_unevaluable_cfg_predicate_is_reported_not_assumed() {
86+ let d = work("cfg-unknown");
87+ let f = d.join("d.rs");
88+ fs::write(
89+ &f,
90+ "#[cfg(target_os = \"linux\")]\nfn only_linux() -> i32 { 1 }\nfn main() {}\n",
91+ )
92+ .unwrap();
93+ let (ok, _, err) = run(&d, &[f.to_str().unwrap()]);
94+ assert!(!ok, "an unevaluable cfg must not be silently resolved");
95+ assert!(err.contains("not a predicate rustnim can evaluate"), "unexpected: {err}");
96+}