nandi/rustnimpublic Fork 0
0a375d8
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 closures and unsafe; base16ct's lower.rs and upper.rs go through

`unsafe` is a permission marker, not a semantic change -- it does not alter
what the enclosed operations mean -- so the block is transparent and every
operation inside still goes through the ordinary lowering and is still
rejected if it has no faithful mapping. `unsafe fn` lowers like any proc.

A closure becomes a Nim anonymous proc. Nim's closures capture by reference,
as Rust's non-`move` closures do; a `move` closure captures by value, which
is a different thing, so it is rejected rather than lowered to the same
construct. `impl Fn(A) -> B` is left at Nim's default calling convention,
which accepts both a plain top-level proc and a capturing closure, just as
Rust's `impl Fn` does. `.map`/`.and_then` over an Option/Result are expanded
inline with the closure's parameter aliased to the payload, which keeps the
whole thing an expression and keeps a view a view.

`&str` is a borrowed view of someone else's bytes, so it now maps to
openArray[char] rather than an owned string; Nim accepts a string argument
for an openArray[char] parameter, so literals still pass through.
from_utf8_unchecked reinterprets a byte view as a character view over the
same memory -- probed first, including that writes through the original are
visible and that the empty case is safe.

Modules are now real. Rust keeps lower::decode and mixed::decode apart by
module, and flattening merged them -- they are different functions, so this
was a miscompile waiting to happen, not a cosmetic issue. The first input is
the crate root, each later one is a module named by its file stem, items are
emitted as <module>_<name>, and a call resolves through an explicit
qualifier, then the current module, then what `use` brought into scope, then
the root.

Three bugs found while getting there: a window alias re-evaluated its offset
and length expression at each use site, so a loop pattern that shadowed a
name silently changed what those bounds meant -- they are now hoisted and
computed once, as Rust computes them once; windows did not compose, so a
chunks_exact_mut over a get_mut view rebuilt the outer view instead of
indexing through it; and `?` now applies a crate's `impl From` to convert the
error type instead of refusing, while still never assuming the conversion is
the identity.

base16ct's lower.rs and upper.rs now go through byte-for-byte, which makes
four of its six modules. encode_str lowers as written --
`encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) })` --
returning a &str view of the bytes just written rather than a copy. Remaining
are display.rs, which needs the formatter to accumulate writes across a loop,
and the alloc half, which now reaches debug_assert_eq!.

32 differential cases and 6 integration tests, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-18T20:23:38-07:00 Browse files
0a375d8 parent: 428f374
modified DESIGN.md +70 -29
@@ -2,13 +2,13 @@
22
33 ## Status
44
5-**Milestone 1's decoder goal is reached.** 31 differential cases, 27
6-behavioural and 4 rejections, plus 5 unit/integration tests. All green. Run
7-`cargo test`.
5+**Milestone 1 is reached, and then some.** Four of base16ct's six modules go
6+through byte-for-byte. 32 differential cases, 28 behavioural and 4 rejections,
7+plus 6 unit/integration tests. All green. Run `cargo test`.
88
99 Passing today: functions, `impl` methods, trait impls (formatting traits and
1010 `From`), structs, enums (C-like and data-carrying), `Option`/`Result` with
11-`?`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/
11+`?`, closures, `unsafe`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/
1212 `chunks_exact_mut`/`windows`), borrowed slices as values and return types,
1313 `let`/`let mut`, the full integer
1414 and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`,
@@ -155,6 +155,39 @@ the view and its validity condition travel together through `ok_or` until a
155155 `?` or `unwrap` resolves them into a bounds check plus a binding. Keeping such
156156 an `Option` in a variable is rejected with a message saying so.
157157
158+### Closures and `unsafe`
159+
160+`unsafe` is a permission marker, not a semantic change: it does not alter what
161+the enclosed operations mean. So the block is transparent, and every operation
162+inside still goes through the ordinary lowering and is still rejected if it has
163+no faithful mapping. `unsafe fn` lowers like any other proc.
164+
165+A closure becomes a Nim anonymous proc. Nim's closures capture by reference, as
166+Rust's non-`move` closures do; a `move` closure captures by value, which is a
167+different thing, so it is rejected rather than lowered to the same construct.
168+`impl Fn(A) -> B` is left at Nim's default calling convention, which accepts
169+both a plain top-level proc and a capturing closure — as Rust's `impl Fn` does.
170+
171+`.map`/`.and_then` over an `Option`/`Result` are expanded inline with the
172+closure's parameter aliased to the payload, rather than handed to a generic
173+proc. That keeps the whole thing an expression and keeps a view a view.
174+
175+`&str` is a borrowed view of someone else's bytes, so it maps to
176+`openArray[char]`, not to an owned `string`. Nim accepts a `string` argument
177+for an `openArray[char]` parameter, so a literal still passes straight through.
178+`from_utf8_unchecked` reinterprets a byte view as a character view over the
179+same memory — no copy, no validation, and writes through the original are
180+visible, as in Rust.
181+
182+### Modules
183+
184+Rust keeps `lower::decode` and `mixed::decode` apart by module; flattening into
185+one Nim module would merge them — they are *different functions*. So the first
186+input is the crate root and each later one is a module named by its file stem,
187+items are emitted as `<module>_<name>`, and a call resolves through an explicit
188+qualifier, then the current module, then what `use` brought into scope, then
189+the root.
190+
158191 ### Declaration order
159192
160193 Rust has no declaration-before-use rule and Nim does, so every proc is
@@ -213,16 +246,17 @@ runner) rather than a wrong answer.
213246
214247 5. `checked_*` and `saturating_*` are not mapped yet; they are currently
215248 rejected as unsupported methods rather than approximated.
216-6. Generics (type and const parameters), closures, `unsafe`, and trait impls
217- other than the formatting traits and `From` are rejected with a reason.
249+6. Generics (type and const parameters), `move` closures, closure bodies with
250+ statements, and trait impls other than the formatting traits and `From` are
251+ rejected with a reason.
218252 Lifetime parameters are *not* a rejection: they carry no runtime meaning
219253 and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.
220254 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but
221255 the exponent-form thresholds have only been checked at `1e21`.
222-8. Flattening several files into one module can collide a crate's own
223- `type Result<T>` with the builtin `Result<T, E>`. Rust kept them apart by
224- module; we tell them apart by arity. That is a real difference from Rust's
225- resolution and would need proper module scoping to fix.
256+8. Functions are scoped by module now, but *types* are still global: two
257+ modules declaring the same type name would collide. Relatedly, a crate's
258+ own `type Result<T>` is told apart from the builtin `Result<T, E>` by
259+ arity, which is not how Rust resolves it.
226260
227261 ## Testing: differential, not golden
228262
@@ -278,21 +312,22 @@ or any parent, or via `RUSTNIM_NIM`.
278312 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and
279313 have its decoder produce byte-identical output to the Rust original.
280314
281-**The decoder is reached.** `tests/cases/026-base16ct-crate/` transpiles
282-base16ct's `error.rs` and `mixed.rs` **byte-for-byte as published on
315+**Reached, for four of the crate's six modules.**
316+`tests/cases/026-base16ct-crate/` transpiles base16ct's `error.rs`,
317+`lower.rs`, `upper.rs` and `mixed.rs` **byte-for-byte as published on
283318 crates.io** — verified with `cmp`, not by eye — together with `lib.rs`'s
284-`decoded_len`, `encoded_len` and `decode_inner` verbatim, and its output is
319+`decoded_len`, `encoded_len` and `decode_inner` verbatim. Output is
285320 byte-identical to rustc's:
286321
287322 ```
288-mixed-l ok abcd1234 len=4 lower, upper and mixed hex all decode
289-mixed-u ok abcd1234 len=4
323+lower ok abcd1234 len=4 decode: lower, upper, mixed
290324 mixed-m ok abcd1234 len=4
291-edge ok 00ff7f80 len=4
292-oddlen err InvalidLength / invalid Base16 length <- Debug and Display
293-bad err InvalidEncoding / invalid Base16 encoding
294-empty ok len=0
295-short-dst err InvalidLength / invalid Base16 length
325+upper ok abcd1234 len=4
326+upper-rej err InvalidEncoding ... upper correctly rejects lowercase
327+oddlen err InvalidLength / invalid Base16 length <- Debug and Display
328+encode ok 6162636431323334 len=8 encode, both cases
329+encode-up ok 4142434431323334 len=8
330+encode_str ok abcd1234 len=8 closure over unsafe, borrowed &str
296331 ```
297332
298333 `decode_inner` goes through as written: `dst.get_mut(..decoded_len(src)?)`,
@@ -300,14 +335,20 @@ short-dst err InvalidLength / invalid Base16 length
300335 returned `&'a [u8]` view into the caller's buffer. The `Display` line in that
301336 output comes from the crate's own `impl fmt::Display for Error`.
302337
338+`encode_str` goes through as written — `encode(src, dst).map(|r| unsafe {
339+core::str::from_utf8_unchecked(r) })` — returning a `&str` view of the bytes
340+just written, not a copy.
341+
303342 ### Still to do for the whole crate
304343
305-- `lower.rs` / `upper.rs`: `encode` itself lowers, but the same file defines
306- `encode_str`, whose body is a **closure** over an **`unsafe`** block. Both
307- are unimplemented, and a file is all-or-nothing, so neither module is in the
308- case yet.
309-- `display.rs`: `HexDisplay<'a>(pub &'a [u8])` is a tuple struct holding a
310- borrowed slice. A struct *field* of view type is what Nim's view types do
311- not allow, which is the same wall as `Option<&[T]>`.
312-- The `alloc` half (`decode_vec`, `encode_string`) needs `--cfg feature=alloc`
313- and then `String::from_utf8_unchecked`, i.e. `unsafe` again.
344+Two modules remain, and both are close:
345+
346+- `display.rs` needs the formatter to *accumulate* writes. Our model maps a
347+ formatting impl to a proc returning the string it wrote, which handles one
348+ write or a `match` over several; `UpperHex` here calls `f.write_str(..)?` in
349+ a loop, so the writes have to append instead of being the value. It also
350+ needs `HexDisplay<'a>(pub &'a [u8])` — a struct *field* of view type, the
351+ same wall as `Option<&[T]>`.
352+- The `alloc` half (`--cfg feature=alloc`) now gets as far as
353+ `debug_assert_eq!`, then needs `String::from_utf8_unchecked` to produce an
354+ owned `String` from a `Vec<u8>`.
@@ -2,13 +2,13 @@
2 2
3 ## Status3 ## Status
4 4
5-**Milestone 1's decoder goal is reached.** 31 differential cases, 275+**Milestone 1 is reached, and then some.** Four of base16ct's six modules go
6-behavioural and 4 rejections, plus 5 unit/integration tests. All green. Run6+through byte-for-byte. 32 differential cases, 28 behavioural and 4 rejections,
7-`cargo test`.7+plus 6 unit/integration tests. All green. Run `cargo test`.
8 8
9 Passing today: functions, `impl` methods, trait impls (formatting traits and9 Passing today: functions, `impl` methods, trait impls (formatting traits and
10 `From`), structs, enums (C-like and data-carrying), `Option`/`Result` with10 `From`), structs, enums (C-like and data-carrying), `Option`/`Result` with
11-`?`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/11+`?`, closures, `unsafe`, slice iterators (`iter`/`iter_mut`/`enumerate`/`zip`/`chunks_exact`/
12 `chunks_exact_mut`/`windows`), borrowed slices as values and return types,12 `chunks_exact_mut`/`windows`), borrowed slices as values and return types,
13 `let`/`let mut`, the full integer13 `let`/`let mut`, the full integer
14 and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`,14 and float operator set at exact widths, `as` casts, `if`/`while`/`loop`/`for`,
@@ -155,6 +155,39 @@ the view and its validity condition travel together through `ok_or` until a
155 `?` or `unwrap` resolves them into a bounds check plus a binding. Keeping such155 `?` or `unwrap` resolves them into a bounds check plus a binding. Keeping such
156 an `Option` in a variable is rejected with a message saying so.156 an `Option` in a variable is rejected with a message saying so.
157 157
158+### Closures and `unsafe`
159+
160+`unsafe` is a permission marker, not a semantic change: it does not alter what
161+the enclosed operations mean. So the block is transparent, and every operation
162+inside still goes through the ordinary lowering and is still rejected if it has
163+no faithful mapping. `unsafe fn` lowers like any other proc.
164+
165+A closure becomes a Nim anonymous proc. Nim's closures capture by reference, as
166+Rust's non-`move` closures do; a `move` closure captures by value, which is a
167+different thing, so it is rejected rather than lowered to the same construct.
168+`impl Fn(A) -> B` is left at Nim's default calling convention, which accepts
169+both a plain top-level proc and a capturing closure — as Rust's `impl Fn` does.
170+
171+`.map`/`.and_then` over an `Option`/`Result` are expanded inline with the
172+closure's parameter aliased to the payload, rather than handed to a generic
173+proc. That keeps the whole thing an expression and keeps a view a view.
174+
175+`&str` is a borrowed view of someone else's bytes, so it maps to
176+`openArray[char]`, not to an owned `string`. Nim accepts a `string` argument
177+for an `openArray[char]` parameter, so a literal still passes straight through.
178+`from_utf8_unchecked` reinterprets a byte view as a character view over the
179+same memory — no copy, no validation, and writes through the original are
180+visible, as in Rust.
181+
182+### Modules
183+
184+Rust keeps `lower::decode` and `mixed::decode` apart by module; flattening into
185+one Nim module would merge them — they are *different functions*. So the first
186+input is the crate root and each later one is a module named by its file stem,
187+items are emitted as `<module>_<name>`, and a call resolves through an explicit
188+qualifier, then the current module, then what `use` brought into scope, then
189+the root.
190+
158 ### Declaration order191 ### Declaration order
159 192
160 Rust has no declaration-before-use rule and Nim does, so every proc is193 Rust has no declaration-before-use rule and Nim does, so every proc is
@@ -213,16 +246,17 @@ runner) rather than a wrong answer.
213 246
214 5. `checked_*` and `saturating_*` are not mapped yet; they are currently247 5. `checked_*` and `saturating_*` are not mapped yet; they are currently
215 rejected as unsupported methods rather than approximated.248 rejected as unsupported methods rather than approximated.
216-6. Generics (type and const parameters), closures, `unsafe`, and trait impls249+6. Generics (type and const parameters), `move` closures, closure bodies with
217- other than the formatting traits and `From` are rejected with a reason.250+ statements, and trait impls other than the formatting traits and `From` are
251+ rejected with a reason.
218 Lifetime parameters are *not* a rejection: they carry no runtime meaning252 Lifetime parameters are *not* a rejection: they carry no runtime meaning
219 and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.253 and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.
220 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but254 7. Float formatting matches Rust for ordinary values and for `inf`/`NaN`, but
221 the exponent-form thresholds have only been checked at `1e21`.255 the exponent-form thresholds have only been checked at `1e21`.
222-8. Flattening several files into one module can collide a crate's own256+8. Functions are scoped by module now, but *types* are still global: two
223- `type Result<T>` with the builtin `Result<T, E>`. Rust kept them apart by257+ modules declaring the same type name would collide. Relatedly, a crate's
224- module; we tell them apart by arity. That is a real difference from Rust's258+ own `type Result<T>` is told apart from the builtin `Result<T, E>` by
225- resolution and would need proper module scoping to fix.259+ arity, which is not how Rust resolves it.
226 260
227 ## Testing: differential, not golden261 ## Testing: differential, not golden
228 262
@@ -278,21 +312,22 @@ or any parent, or via `RUSTNIM_NIM`.
278 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and312 Transpile `base16ct` 1.0.0 — the crate the other transpiler failed on — and
279 have its decoder produce byte-identical output to the Rust original.313 have its decoder produce byte-identical output to the Rust original.
280 314
281-**The decoder is reached.** `tests/cases/026-base16ct-crate/` transpiles315+**Reached, for four of the crate's six modules.**
282-base16ct's `error.rs` and `mixed.rs` **byte-for-byte as published on316+`tests/cases/026-base16ct-crate/` transpiles base16ct's `error.rs`,
317+`lower.rs`, `upper.rs` and `mixed.rs` **byte-for-byte as published on
283 crates.io** — verified with `cmp`, not by eye — together with `lib.rs`'s318 crates.io** — verified with `cmp`, not by eye — together with `lib.rs`'s
284-`decoded_len`, `encoded_len` and `decode_inner` verbatim, and its output is319+`decoded_len`, `encoded_len` and `decode_inner` verbatim. Output is
285 byte-identical to rustc's:320 byte-identical to rustc's:
286 321
287 ```322 ```
288-mixed-l ok abcd1234 len=4 lower, upper and mixed hex all decode323+lower ok abcd1234 len=4 decode: lower, upper, mixed
289-mixed-u ok abcd1234 len=4
290 mixed-m ok abcd1234 len=4324 mixed-m ok abcd1234 len=4
291-edge ok 00ff7f80 len=4325+upper ok abcd1234 len=4
292-oddlen err InvalidLength / invalid Base16 length <- Debug and Display326+upper-rej err InvalidEncoding ... upper correctly rejects lowercase
293-bad err InvalidEncoding / invalid Base16 encoding327+oddlen err InvalidLength / invalid Base16 length <- Debug and Display
294-empty ok len=0328+encode ok 6162636431323334 len=8 encode, both cases
295-short-dst err InvalidLength / invalid Base16 length329+encode-up ok 4142434431323334 len=8
330+encode_str ok abcd1234 len=8 closure over unsafe, borrowed &str
296 ```331 ```
297 332
298 `decode_inner` goes through as written: `dst.get_mut(..decoded_len(src)?)`,333 `decode_inner` goes through as written: `dst.get_mut(..decoded_len(src)?)`,
@@ -300,14 +335,20 @@ short-dst err InvalidLength / invalid Base16 length
300 returned `&'a [u8]` view into the caller's buffer. The `Display` line in that335 returned `&'a [u8]` view into the caller's buffer. The `Display` line in that
301 output comes from the crate's own `impl fmt::Display for Error`.336 output comes from the crate's own `impl fmt::Display for Error`.
302 337
338+`encode_str` goes through as written — `encode(src, dst).map(|r| unsafe {
339+core::str::from_utf8_unchecked(r) })` — returning a `&str` view of the bytes
340+just written, not a copy.
341+
303 ### Still to do for the whole crate342 ### Still to do for the whole crate
304 343
305-- `lower.rs` / `upper.rs`: `encode` itself lowers, but the same file defines344+Two modules remain, and both are close:
306- `encode_str`, whose body is a **closure** over an **`unsafe`** block. Both345+
307- are unimplemented, and a file is all-or-nothing, so neither module is in the346+- `display.rs` needs the formatter to *accumulate* writes. Our model maps a
308- case yet.347+ formatting impl to a proc returning the string it wrote, which handles one
309-- `display.rs`: `HexDisplay<'a>(pub &'a [u8])` is a tuple struct holding a348+ write or a `match` over several; `UpperHex` here calls `f.write_str(..)?` in
310- borrowed slice. A struct *field* of view type is what Nim's view types do349+ a loop, so the writes have to append instead of being the value. It also
311- not allow, which is the same wall as `Option<&[T]>`.350+ needs `HexDisplay<'a>(pub &'a [u8])` — a struct *field* of view type, the
312-- The `alloc` half (`decode_vec`, `encode_string`) needs `--cfg feature=alloc`351+ same wall as `Option<&[T]>`.
313- and then `String::from_utf8_unchecked`, i.e. `unsafe` again.352+- The `alloc` half (`--cfg feature=alloc`) now gets as far as
353+ `debug_assert_eq!`, then needs `String::from_utf8_unchecked` to produce an
354+ owned `String` from a `Vec<u8>`.
modified README.md +17 -11
@@ -44,22 +44,28 @@ into a single index loop where each binding is an lvalue into the original
4444 container, so `*d = v` through `iter_mut()` reaches the caller's slice.
4545 Borrowed slices are views, not copies, including as return types.
4646
47+Closures and `unsafe` blocks, and `&str` as a borrowed view rather than an
48+owned copy. Modules: pass the crate root first and each further file after it,
49+and items are scoped by module, so `lower::decode` and `mixed::decode` stay
50+distinct.
51+
4752 Rejected with a reason, rather than guessed at: `i128`/`u128`, generics,
48-closures, `unsafe`, trait impls other than the ones above, iterator adaptors
49-with no index-loop equivalent (`map`, `filter`, `take_while`), float→int casts,
50-and any standard-library method that isn't mapped.
53+`move` closures, trait impls other than the ones above, iterator adaptors with
54+no index-loop equivalent (`map`, `filter`, `take_while`), float→int casts, and
55+any standard-library method that isn't mapped.
5156
5257 ## base16ct
5358
54-`tests/cases/026-base16ct-crate/` transpiles base16ct 1.0.0's `error.rs` and
55-`mixed.rs` byte-for-byte as published on crates.io, plus `lib.rs`'s
56-`decoded_len`, `encoded_len` and `decode_inner` verbatim. Its decoder output is
57-byte-identical to rustc's — lower, upper and mixed hex, both error variants,
58-and the `Display` strings. That is the crate the transpiler in
59-[`findings/`](findings/) emitted an empty file for.
59+`tests/cases/026-base16ct-crate/` transpiles four of base16ct 1.0.0's six
60+modules — `error.rs`, `lower.rs`, `upper.rs`, `mixed.rs` — byte-for-byte as
61+published on crates.io, plus `lib.rs`'s `decoded_len`, `encoded_len` and
62+`decode_inner` verbatim. Decoding and encoding are byte-identical to rustc's,
63+including `encode_str`, which is a closure over an `unsafe` block returning a
64+borrowed `&str` view of the bytes just written. That is the crate the
65+transpiler in [`findings/`](findings/) emitted an empty file for.
6066
61-The rest of the crate needs closures, `unsafe`, and struct fields of view type;
62-[`DESIGN.md`](DESIGN.md) says which file needs which.
67+`display.rs` and the `alloc` half are not through yet;
68+[`DESIGN.md`](DESIGN.md) says exactly what each still needs.
6369
6470 ## Tests
6571
@@ -44,22 +44,28 @@ into a single index loop where each binding is an lvalue into the original
44 container, so `*d = v` through `iter_mut()` reaches the caller's slice.44 container, so `*d = v` through `iter_mut()` reaches the caller's slice.
45 Borrowed slices are views, not copies, including as return types.45 Borrowed slices are views, not copies, including as return types.
46 46
47+Closures and `unsafe` blocks, and `&str` as a borrowed view rather than an
48+owned copy. Modules: pass the crate root first and each further file after it,
49+and items are scoped by module, so `lower::decode` and `mixed::decode` stay
50+distinct.
51+
47 Rejected with a reason, rather than guessed at: `i128`/`u128`, generics,52 Rejected with a reason, rather than guessed at: `i128`/`u128`, generics,
48-closures, `unsafe`, trait impls other than the ones above, iterator adaptors53+`move` closures, trait impls other than the ones above, iterator adaptors with
49-with no index-loop equivalent (`map`, `filter`, `take_while`), float→int casts,54+no index-loop equivalent (`map`, `filter`, `take_while`), float→int casts, and
50-and any standard-library method that isn't mapped.55+any standard-library method that isn't mapped.
51 56
52 ## base16ct57 ## base16ct
53 58
54-`tests/cases/026-base16ct-crate/` transpiles base16ct 1.0.0's `error.rs` and59+`tests/cases/026-base16ct-crate/` transpiles four of base16ct 1.0.0's six
55-`mixed.rs` byte-for-byte as published on crates.io, plus `lib.rs`'s60+modules — `error.rs`, `lower.rs`, `upper.rs`, `mixed.rs` — byte-for-byte as
56-`decoded_len`, `encoded_len` and `decode_inner` verbatim. Its decoder output is61+published on crates.io, plus `lib.rs`'s `decoded_len`, `encoded_len` and
57-byte-identical to rustc's — lower, upper and mixed hex, both error variants,62+`decode_inner` verbatim. Decoding and encoding are byte-identical to rustc's,
58-and the `Display` strings. That is the crate the transpiler in63+including `encode_str`, which is a closure over an `unsafe` block returning a
59-[`findings/`](findings/) emitted an empty file for.64+borrowed `&str` view of the bytes just written. That is the crate the
65+transpiler in [`findings/`](findings/) emitted an empty file for.
60 66
61-The rest of the crate needs closures, `unsafe`, and struct fields of view type;67+`display.rs` and the `alloc` half are not through yet;
62-[`DESIGN.md`](DESIGN.md) says which file needs which.68+[`DESIGN.md`](DESIGN.md) says exactly what each still needs.
63 69
64 ## Tests70 ## Tests
65 71
modified src/lower.rs +478 -75
@@ -76,9 +76,9 @@ enum Iter {
7676 Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool },
7777 /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of
7878 /// `k` elements starting at `k * i`.
79- Chunks { code: String, k: String, elem: Option<Nim>, mutable: bool },
79+ Chunks { code: String, base: String, len: String, k: String, elem: Option<Nim>, mutable: bool },
8080 /// `a.windows(k)`: like `Chunks` but advancing one element at a time.
81- Windows { code: String, k: String, elem: Option<Nim> },
81+ Windows { code: String, base: String, len: String, k: String, elem: Option<Nim> },
8282 /// `.enumerate()` — the index is the first half of the pair.
8383 Enumerate(Box<Iter>),
8484 /// `.zip(other)` — stops at the shorter, as Rust's does.
@@ -95,10 +95,8 @@ impl Iter {
9595 if *closed { format!("({n} + 1)") } else { n }
9696 }
9797 Iter::Elems { len, .. } => len.clone(),
98- Iter::Chunks { code, k, .. } => format!("({}.len div int({}))", code, k),
99- Iter::Windows { code, k, .. } => {
100- format!("(max(0, {}.len - int({}) + 1))", code, k)
101- }
98+ Iter::Chunks { k, len, .. } => format!("({} div int({}))", len, k),
99+ Iter::Windows { len, k, .. } => format!("(max(0, {} - int({}) + 1))", len, k),
102100 Iter::Enumerate(i) => i.len(),
103101 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
104102 }
@@ -188,7 +186,16 @@ pub struct Lowerer {
188186 /// Names introduced by a `for` pattern that stand for an lvalue or a
189187 /// window into a container, rather than for a variable of their own.
190188 alias_scopes: Vec<HashMap<String, Alias>>,
191- fns: HashMap<String, Sig>,
189+ /// `(module, name) -> signature`. Rust keeps `lower::decode` and
190+ /// `mixed::decode` apart by module; flattening into one Nim module would
191+ /// merge them, so the module is part of the key and of the emitted name.
192+ fns: HashMap<(String, String), Sig>,
193+ /// Module being lowered: the file stem, or empty for the crate root.
194+ cur_mod: String,
195+ /// `use` brings a name into scope from another module. Flattening loses
196+ /// the module structure, so the mapping is recorded and consulted when a
197+ /// bare call is resolved.
198+ use_map: HashMap<String, String>,
192199 /// struct name -> (field, type)
193200 structs: HashMap<String, Vec<(String, Nim)>>,
194201 enums: HashMap<String, EnumDef>,
@@ -209,6 +216,8 @@ pub struct Lowerer {
209216 /// proc is declared up front rather than the input being reordered --
210217 /// which would not work for mutual recursion anyway.
211218 forwards: Vec<String>,
219+ /// Element type a `vec![..]` should build, from the binding's annotation.
220+ vec_expect: Option<Nim>,
212221 /// While lowering a formatting impl: the `Formatter` parameter's name.
213222 /// Writes through it produce the proc's string result.
214223 fmt_param: Option<String>,
@@ -242,6 +251,8 @@ impl Lowerer {
242251 scopes: vec![HashMap::new()],
243252 alias_scopes: vec![HashMap::new()],
244253 fns: HashMap::new(),
254+ cur_mod: String::new(),
255+ use_map: HashMap::new(),
245256 structs: HashMap::new(),
246257 enums: HashMap::new(),
247258 variant_owner: HashMap::new(),
@@ -249,6 +260,7 @@ impl Lowerer {
249260 fmt_impls: HashMap::new(),
250261 from_impls: HashMap::new(),
251262 fmt_param: None,
263+ vec_expect: None,
252264 forwards: Vec::new(),
253265 aliases: HashMap::new(),
254266 modules: Vec::new(),
@@ -311,25 +323,34 @@ impl Lowerer {
311323
312324 // ---------------------------------------------------------------- file
313325
314- pub fn lower_file(&mut self, file: &syn::File) -> Result<String, String> {
326+ pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {
315327 self.out.push_str(include_str!("prelude.nim"));
316328 self.blank();
317329
318330 // Pass 0: type aliases. A signature in one file may use an alias
319331 // declared in another, and inputs are given in whatever order suits
320332 // the caller, so aliases are registered before anything is mapped.
321- for item in &file.items {
322- self.collect_aliases(item)?;
333+ for (m, f) in files {
334+ self.cur_mod = m.clone();
335+ for item in &f.items {
336+ self.collect_aliases(item)?;
337+ }
323338 }
324339
325340 // Pass 1: signatures and struct shapes, so that a call can be typed
326341 // regardless of declaration order (Rust has no forward declarations).
327- for item in &file.items {
328- self.collect(item)?;
342+ for (m, f) in files {
343+ self.cur_mod = m.clone();
344+ for item in &f.items {
345+ self.collect(item)?;
346+ }
329347 }
330348 // Pass 2: type definitions, which every signature may mention.
331- for item in &file.items {
332- self.item_types(item)?;
349+ for (m, f) in files {
350+ self.cur_mod = m.clone();
351+ for item in &f.items {
352+ self.item_types(item)?;
353+ }
333354 }
334355
335356 // Pass 3: forward declarations. Rust imposes no declaration order and
@@ -343,11 +364,14 @@ impl Lowerer {
343364 }
344365
345366 // Pass 4: bodies.
346- for item in &file.items {
347- self.item(item)?;
367+ for (m, f) in files {
368+ self.cur_mod = m.clone();
369+ for item in &f.items {
370+ self.item(item)?;
371+ }
348372 }
349373
350- if self.fns.contains_key("main") {
374+ if self.fns.contains_key(&(String::new(), "main".to_string())) {
351375 self.blank();
352376 self.line("when isMainModule:");
353377 self.indent += 1;
@@ -372,6 +396,7 @@ impl Lowerer {
372396 return Ok(());
373397 }
374398 match item {
399+ Item::Use(u) => self.collect_use(&u.tree, &[]),
375400 Item::Type(t) => {
376401 let params: Vec<String> = t
377402 .generics
@@ -396,6 +421,34 @@ impl Lowerer {
396421 Ok(())
397422 }
398423
424+ /// Record what a `use` brings into scope, as `name -> module`.
425+ fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
426+ use syn::UseTree;
427+ match t {
428+ UseTree::Path(p) => {
429+ let mut pre = prefix.to_vec();
430+ pre.push(p.ident.to_string());
431+ self.collect_use(&p.tree, &pre);
432+ }
433+ UseTree::Group(g) => {
434+ for t in &g.items {
435+ self.collect_use(t, prefix);
436+ }
437+ }
438+ UseTree::Name(n) => {
439+ let m = module_of(prefix);
440+ self.use_map.insert(n.ident.to_string(), m);
441+ }
442+ UseTree::Rename(r) => {
443+ let m = module_of(prefix);
444+ self.use_map.insert(r.rename.to_string(), m);
445+ }
446+ // A glob brings in an unknown set of names; resolution falls back
447+ // to the current module and the root, as it would without it.
448+ UseTree::Glob(_) => {}
449+ }
450+ }
451+
399452 fn collect(&mut self, item: &Item) -> Result<(), String> {
400453 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
401454 // silently would change what the program does; picking a feature set
@@ -408,9 +461,11 @@ impl Lowerer {
408461 match item {
409462 Item::Fn(f) => {
410463 let (params, ret) = self.signature(&f.sig)?;
411- let head = self.head_of(&f.sig.ident.to_string(), &f.sig, None)?;
412- self.forwards.push(head);
413- self.fns.insert(f.sig.ident.to_string(), Sig { params, ret });
464+ let name = f.sig.ident.to_string();
465+ let nim = self.fn_name(&self.cur_mod, &name);
466+ self.forwards.push(self.head_of(&nim, &f.sig, None)?);
467+ self.fns
468+ .insert((self.cur_mod.clone(), name), Sig { params, ret });
414469 }
415470 Item::Struct(s) => {
416471 let mut fields = Vec::new();
@@ -637,6 +692,38 @@ impl Lowerer {
637692 self.expand(&substitute(target, params, &args), depth + 1)
638693 }
639694
695+ /// The Nim name for a function, qualified by its module.
696+ fn fn_name(&self, module: &str, name: &str) -> String {
697+ if module.is_empty() {
698+ ident(name)
699+ } else {
700+ format!("{}_{}", module, ident(name))
701+ }
702+ }
703+
704+ /// Resolve a call path to the module and name it refers to: an explicit
705+ /// `mixed::decode`, then the current module, then the crate root.
706+ fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
707+ let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
708+ let last = segs.last()?.clone();
709+ if segs.len() >= 2 {
710+ let q = &segs[segs.len() - 2];
711+ if self.fns.contains_key(&(q.clone(), last.clone())) {
712+ return Some((q.clone(), last));
713+ }
714+ }
715+ let imported = self.use_map.get(&last).cloned();
716+ for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
717+ .into_iter()
718+ .flatten()
719+ {
720+ if self.fns.contains_key(&(m.clone(), last.clone())) {
721+ return Some((m, last));
722+ }
723+ }
724+ None
725+ }
726+
640727 /// The Nim `proc` head for a Rust signature, used both for the forward
641728 /// declaration and for the definition, so the two cannot drift apart.
642729 fn head_of(
@@ -681,6 +768,8 @@ impl Lowerer {
681768 }
682769
683770 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
771+ // `unsafe fn` marks a contract for callers; it does not change what
772+ // the body means, so it lowers like any other proc.
684773 if sig.asyncness.is_some() {
685774 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
686775 }
@@ -749,7 +838,10 @@ impl Lowerer {
749838
750839 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
751840 match item {
752- Item::Fn(f) => self.func(&f.sig, &f.block, None),
841+ Item::Fn(f) => {
842+ let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
843+ self.func_named(&nim, &f.sig, &f.block, None)
844+ }
753845 Item::Struct(s) => {
754846 let name = s.ident.to_string();
755847 let fields = self.structs[&name].clone();
@@ -1384,6 +1476,11 @@ impl Lowerer {
13841476 self.nested_block(&b.block)?;
13851477 Ok(None)
13861478 }
1479+ Expr::Unsafe(u) => {
1480+ // Transparent in statement position too, for the same reason.
1481+ self.nested_block_flat(&u.block)?;
1482+ Ok(None)
1483+ }
13871484 Expr::Match(_) => {
13881485 self.match_stmt(e)?;
13891486 Ok(None)
@@ -1597,23 +1694,21 @@ impl Lowerer {
15971694 Ok(Iter::Zip(Box::new(a), Box::new(b)))
15981695 }
15991696 "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => {
1600- let recv = self.expr(&m.receiver)?;
1697+ let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
16011698 let k = self.expr(&m.args[0])?;
16021699 Ok(Iter::Chunks {
1603- code: recv.code,
1700+ code,
1701+ base,
1702+ len,
16041703 k: k.code,
1605- elem: elem_of(&recv.ty),
1704+ elem,
16061705 mutable: name.ends_with("_mut"),
16071706 })
16081707 }
16091708 "windows" if m.args.len() == 1 => {
1610- let recv = self.expr(&m.receiver)?;
1709+ let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
16111710 let k = self.expr(&m.args[0])?;
1612- Ok(Iter::Windows {
1613- code: recv.code,
1614- k: k.code,
1615- elem: elem_of(&recv.ty),
1616- })
1711+ Ok(Iter::Windows { code, base, len, k: k.code, elem })
16171712 }
16181713 other => Err(format!(
16191714 "iterator adaptor `.{other}()` is not implemented; it has \
@@ -1626,13 +1721,7 @@ impl Lowerer {
16261721 // A `for` binding that is itself a window iterates that window,
16271722 // not the whole container it points into.
16281723 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) {
1629- return Ok(Iter::Elems {
1630- code,
1631- off,
1632- len,
1633- elem,
1634- mutable: false,
1635- });
1724+ return Ok(Iter::Elems { code, off, len, elem, mutable: false });
16361725 }
16371726 let v = self.expr(other)?;
16381727 Ok(Iter::Elems {
@@ -1666,6 +1755,10 @@ impl Lowerer {
16661755 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
16671756 ),
16681757 (Pat::Wild(_), _) => Ok(()),
1758+ // `for &byte in xs` — the `&` destructures the reference, which in
1759+ // Nim is already the value.
1760+ (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
1761+ (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
16691762 (Pat::Ident(id), _) => {
16701763 let name = id.ident.to_string();
16711764 match it {
@@ -1711,24 +1804,24 @@ impl Lowerer {
17111804 }
17121805 Ok(())
17131806 }
1714- Iter::Chunks { code, k, elem, .. } => {
1807+ Iter::Chunks { code, base, k, elem, .. } => {
17151808 self.bind_alias(
17161809 &name,
17171810 Alias::Window {
17181811 code: code.clone(),
1719- off: format!("({} * int({}))", i, k),
1812+ off: format!("({} + {} * int({}))", base, i, k),
17201813 len: format!("int({})", k),
17211814 elem: elem.clone(),
17221815 },
17231816 );
17241817 Ok(())
17251818 }
1726- Iter::Windows { code, k, elem } => {
1819+ Iter::Windows { code, base, k, elem, .. } => {
17271820 self.bind_alias(
17281821 &name,
17291822 Alias::Window {
17301823 code: code.clone(),
1731- off: i.to_string(),
1824+ off: format!("({} + {})", base, i),
17321825 len: format!("int({})", k),
17331826 elem: elem.clone(),
17341827 },
@@ -2140,9 +2233,10 @@ impl Lowerer {
21402233 }
21412234 // A top-level function used as a value, e.g. passed to a
21422235 // parameter of `impl Fn(..)` type.
2143- if let Some(sig) = self.fns.get(&name) {
2236+ if let Some(k) = self.resolve_fn(&p.path) {
2237+ let sig = &self.fns[&k];
21442238 let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone()));
2145- return Ok(Val::new(ident(&name), Some(t)));
2239+ return Ok(Val::new(self.fn_name(&k.0, &k.1), Some(t)));
21462240 }
21472241 Ok(Val::new(ident(&name), None))
21482242 }
@@ -2220,9 +2314,38 @@ impl Lowerer {
22202314 };
22212315 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
22222316 }
2317+ // `unsafe` is a permission marker, not a semantic change: it does
2318+ // not alter what the enclosed operations mean. So the block is
2319+ // transparent here, and each operation inside still goes through
2320+ // the ordinary lowering -- and is still rejected if it has no
2321+ // faithful mapping.
2322+ Expr::Unsafe(u) => match single_expr(&u.block) {
2323+ Some(e) => self.expr_at(e, expect),
2324+ None => Err("an `unsafe` block used as a value must be a single \
2325+ expression"
2326+ .into()),
2327+ },
2328+ Expr::Closure(c) => self.closure(c, expect),
22232329 Expr::Try(t) => self.try_op(t),
22242330 Expr::Call(c) => self.call(c, expect),
22252331 Expr::MethodCall(m) => self.method(m, expect),
2332+ Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
2333+ // `vec![..]`'s elements take their type from the annotation on
2334+ // the binding, exactly as Rust's would.
2335+ let want = match expect {
2336+ Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
2337+ _ => None,
2338+ };
2339+ let saved = std::mem::replace(&mut self.vec_expect, want.clone());
2340+ let code = self.macro_call(&m.mac);
2341+ self.vec_expect = saved;
2342+ let code = code?;
2343+ let ty = match want {
2344+ Some(e) => Some(Nim::Seq(Box::new(e))),
2345+ None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
2346+ };
2347+ Ok(Val::new(code, ty))
2348+ }
22262349 Expr::Macro(m) => {
22272350 let code = self.macro_call(&m.mac)?;
22282351 Ok(Val::new(code, None))
@@ -2567,6 +2690,190 @@ impl Lowerer {
25672690 /// The early return is statements, not an expression, so they are emitted
25682691 /// ahead of the line being built. Every caller lowers its sub-expressions
25692692 /// before emitting its own line, which is what makes that ordering hold.
2693+ /// The container, start offset, length and element type an expression
2694+ /// denotes as a slice. A window alias contributes its own offset, so
2695+ /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
2696+ /// into the original buffer rather than through a rebuilt view.
2697+ fn slice_parts(
2698+ &mut self,
2699+ e: &Expr,
2700+ ) -> Result<(String, String, String, Option<Nim>), String> {
2701+ if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
2702+ return Ok((code, off, len, elem));
2703+ }
2704+ let v = self.expr(e)?;
2705+ let len = format!("{}.len", v.code);
2706+ Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
2707+ }
2708+
2709+ /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
2710+ fn map_closure(
2711+ &mut self,
2712+ what: &str,
2713+ recv: &Val,
2714+ kind: &str,
2715+ targs: &[Nim],
2716+ c: &syn::ExprClosure,
2717+ ) -> Result<Val, String> {
2718+ if c.capture.is_some() {
2719+ return Err("a `move` closure captures by value; Nim's closures \
2720+ capture by reference, and the two are not the same"
2721+ .into());
2722+ }
2723+ if c.inputs.len() != 1 {
2724+ return Err(format!("`.{what}()` takes a one-argument closure"));
2725+ }
2726+ let pname = match &c.inputs[0] {
2727+ Pat::Ident(i) => i.ident.to_string(),
2728+ Pat::Wild(_) => "unused0".into(),
2729+ _ => return Err("only plain identifier closure parameters are supported".into()),
2730+ };
2731+
2732+ let is_opt = kind == "Option";
2733+ let tmp = self.fresh("Map");
2734+ let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
2735+ self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
2736+
2737+ let body = match &*c.body {
2738+ Expr::Block(b) => single_expr(&b.block)
2739+ .ok_or("a closure body with statements is not implemented yet")?,
2740+ other => other,
2741+ };
2742+ self.push_scope();
2743+ // The parameter names the payload itself, so a view stays a view.
2744+ self.bind_alias(
2745+ &pname,
2746+ Alias::Value {
2747+ code: format!("{}.val", tmp),
2748+ ty: Some(targs[0].clone()),
2749+ },
2750+ );
2751+ let v = self.expr(body)?;
2752+ self.pop_scope();
2753+
2754+ let inner = v
2755+ .ty
2756+ .clone()
2757+ .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
2758+ // `and_then`'s closure already returns the wrapped type; `map`'s does
2759+ // not and has to be re-wrapped.
2760+ let (test, some_branch, none_branch, out_ty) = if is_opt {
2761+ let out = if what == "map" {
2762+ Nim::Named("Option".into(), vec![inner.clone()])
2763+ } else {
2764+ inner.clone()
2765+ };
2766+ let body_code = if what == "map" {
2767+ format!("rsSome[{}]({})", inner.render(), v.code)
2768+ } else {
2769+ v.code.clone()
2770+ };
2771+ (
2772+ format!("{}.has", tmp),
2773+ body_code,
2774+ format!("rsNone[{}]()", elem_arg(&out).render()),
2775+ out,
2776+ )
2777+ } else {
2778+ let e = targs[1].clone();
2779+ let out = if what == "map" {
2780+ Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
2781+ } else {
2782+ inner.clone()
2783+ };
2784+ let ok_ty = elem_arg(&out);
2785+ let body_code = if what == "map" {
2786+ format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
2787+ } else {
2788+ v.code.clone()
2789+ };
2790+ (
2791+ format!("{}.ok", tmp),
2792+ body_code,
2793+ format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
2794+ out,
2795+ )
2796+ };
2797+ Ok(Val::new(
2798+ format!("(if {}: {} else: {})", test, some_branch, none_branch),
2799+ Some(out_ty),
2800+ ))
2801+ }
2802+
2803+ /// `|x| x + 1` -> a Nim anonymous proc.
2804+ ///
2805+ /// Nim's closures capture by reference, as Rust's non-`move` closures do.
2806+ /// A `move` closure captures by value, which is a different thing, so it
2807+ /// is rejected rather than lowered to the same construct.
2808+ fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
2809+ if c.capture.is_some() {
2810+ return Err("a `move` closure captures by value; Nim's closures \
2811+ capture by reference, and the two are not the same"
2812+ .into());
2813+ }
2814+ let want: Option<&Vec<Nim>> = match expect {
2815+ Some(Nim::Proc(a, _)) => Some(a),
2816+ _ => None,
2817+ };
2818+
2819+ self.push_scope();
2820+ let mut parts = Vec::new();
2821+ let mut ptys = Vec::new();
2822+ for (i, p) in c.inputs.iter().enumerate() {
2823+ let (name, ann) = match p {
2824+ Pat::Ident(id) => (id.ident.to_string(), None),
2825+ Pat::Type(t) => match &*t.pat {
2826+ Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
2827+ _ => return Err("only plain identifier closure parameters are supported".into()),
2828+ },
2829+ Pat::Wild(_) => (format!("unused{i}"), None),
2830+ _ => return Err("only plain identifier closure parameters are supported".into()),
2831+ };
2832+ let t = ann
2833+ .or_else(|| want.and_then(|w| w.get(i).cloned()))
2834+ .ok_or_else(|| {
2835+ format!(
2836+ "cannot infer the type of closure parameter `{name}`; \
2837+ annotate it"
2838+ )
2839+ })?;
2840+ parts.push(format!("{}: {}", ident(&name), t.render()));
2841+ self.bind(&name, t.clone());
2842+ ptys.push(t);
2843+ }
2844+
2845+ let ret_ann = match &c.output {
2846+ ReturnType::Default => None,
2847+ ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
2848+ };
2849+ let body = match &*c.body {
2850+ Expr::Block(b) => single_expr(&b.block)
2851+ .ok_or("a closure body with statements is not implemented yet")?,
2852+ other => other,
2853+ };
2854+ let v = self.expr_at(body, ret_ann.as_ref())?;
2855+ self.pop_scope();
2856+
2857+ let ret = ret_ann
2858+ .or_else(|| v.ty.clone())
2859+ .ok_or("cannot infer a closure's return type; annotate it")?;
2860+ Ok(Val::new(
2861+ format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
2862+ Some(Nim::Proc(ptys, Box::new(ret))),
2863+ ))
2864+ }
2865+
2866+ /// Lower a block's statements at the current indentation, without opening
2867+ /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
2868+ /// of its own in the generated code.
2869+ fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
2870+ self.push_scope();
2871+ let tail = self.block_body(b)?;
2872+ self.emit_tail(tail);
2873+ self.pop_scope();
2874+ Ok(())
2875+ }
2876+
25702877 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
25712878 if self.in_loop_cond {
25722879 return Err("`?` in a loop condition is not implemented yet: the \
@@ -2613,24 +2920,29 @@ impl Lowerer {
26132920 (Nim::Named(a, ai), Nim::Named(b, bi))
26142921 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
26152922 {
2616- // Rust inserts a `From::from` on the error here. We only accept
2617- // the case where the error types already agree, rather than
2618- // silently dropping a conversion that might not be the identity.
2619- if ai[1] != bi[1] {
2620- return Err(format!(
2621- "`?` would need `From<{}> for {}`: an error-type conversion \
2622- is not implemented, and assuming it is the identity would \
2623- be a guess",
2624- ai[1].render(),
2625- bi[1].render()
2626- ));
2627- }
2923+ // Rust inserts a `From::from` on the error here. Where the
2924+ // types differ we call the crate's own `impl From`; we never
2925+ // assume the conversion is the identity.
2926+ let err = if ai[1] == bi[1] {
2927+ format!("{}.err", tmp)
2928+ } else {
2929+ let key = (type_name(&ai[1]), type_name(&bi[1]));
2930+ let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
2931+ format!(
2932+ "`?` needs `From<{}> for {}` to convert the error, and \
2933+ no such `impl` is in scope; assuming the conversion is \
2934+ the identity would be a guess",
2935+ key.0, key.1
2936+ )
2937+ })?;
2938+ format!("{}({}.err)", f, tmp)
2939+ };
26282940 self.line(&format!("if not {}.ok:", tmp));
26292941 self.line(&format!(
2630- " return rsErr[{}, {}]({}.err)",
2942+ " return rsErr[{}, {}]({})",
26312943 bi[0].render(),
26322944 bi[1].render(),
2633- tmp
2945+ err
26342946 ));
26352947 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
26362948 }
@@ -2655,9 +2967,10 @@ impl Lowerer {
26552967 return Err("only calls to named functions are supported".into());
26562968 };
26572969 let name = path_name(&p.path);
2658- let ptys: Vec<Nim> = self
2659- .fns
2660- .get(&name)
2970+ let target = self.resolve_fn(&p.path);
2971+ let ptys: Vec<Nim> = target
2972+ .as_ref()
2973+ .and_then(|k| self.fns.get(k))
26612974 .map(|s| s.params.clone())
26622975 .unwrap_or_default();
26632976 let mut args = Vec::new();
@@ -2707,6 +3020,15 @@ impl Lowerer {
27073020 _ => {}
27083021 }
27093022
3023+ // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
3024+ // string view; no copy, no validation, same memory.
3025+ if name == "from_utf8_unchecked" && codes.len() == 1 {
3026+ return Ok(Val::new(
3027+ format!("rsStrView({})", codes[0]),
3028+ Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
3029+ ));
3030+ }
3031+
27103032 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
27113033 if let Some((def, v)) = self.resolve_variant(&p.path) {
27123034 return Ok(Val::new(
@@ -2725,17 +3047,18 @@ impl Lowerer {
27253047 Some((*ret).clone()),
27263048 ));
27273049 }
2728- let ret = self.fns.get(&name).map(|s| s.ret.clone());
3050+ let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone());
27293051 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
27303052 return Err(format!(
27313053 "call to unknown function `{name}`; only functions defined in \
27323054 this file and the supported standard-library subset can be lowered"
27333055 ));
27343056 }
2735- Ok(Val::new(
2736- format!("{}({})", ident(&name), codes.join(", ")),
2737- ret,
2738- ))
3057+ let nim = match &target {
3058+ Some((m, n)) => self.fn_name(m, n),
3059+ None => ident(&name),
3060+ };
3061+ Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
27393062 }
27403063
27413064 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
@@ -2768,6 +3091,7 @@ impl Lowerer {
27683091 && matches!(m.args.first(), Some(Expr::Range(_)))
27693092 {
27703093 let Some(Expr::Range(r)) = m.args.first() else { unreachable!() };
3094+ let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
27713095 let lo = match &r.start {
27723096 Some(e) => format!("int({})", self.expr(e)?.code),
27733097 None => "0".into(),
@@ -2779,9 +3103,18 @@ impl Lowerer {
27793103 (Some(e), syn::RangeLimits::Closed(_)) => {
27803104 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
27813105 }
2782- (None, _) => format!("({}.len - {})", recv.code, lo),
3106+ (None, _) => format!("({} - {})", blen, lo),
27833107 };
2784- let elem = elem_of(&rt0).ok_or("cannot infer the element type of this slice")?;
3108+ // Hoisted, so the bounds are computed once -- as Rust computes
3109+ // them once -- and cannot be re-evaluated later in a scope where
3110+ // the names they mention have been shadowed by a loop pattern.
3111+ let off_t = self.fresh("Off");
3112+ let len_t = self.fresh("Len");
3113+ self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
3114+ self.line(&format!("let {}: int = {}", len_t, len));
3115+ let elem = belem
3116+ .or_else(|| elem_of(&rt0))
3117+ .ok_or("cannot infer the element type of this slice")?;
27853118 let mut v = Val::new(
27863119 String::new(),
27873120 Some(Nim::Named(
@@ -2789,16 +3122,32 @@ impl Lowerer {
27893122 vec![Nim::OpenArray(Box::new(elem.clone()))],
27903123 )),
27913124 );
2792- v.guard = Some(format!("({} + {} <= {}.len)", lo, len, recv.code));
3125+ v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
27933126 v.window = Some(Alias::Window {
2794- code: recv.code.clone(),
2795- off: lo,
2796- len,
3127+ code,
3128+ off: off_t,
3129+ len: len_t,
27973130 elem: Some(elem),
27983131 });
27993132 return Ok(v);
28003133 }
28013134
3135+ // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
3136+ // parameter type comes from the receiver, so they are handled before
3137+ // the arguments are lowered. The closure is expanded inline, with its
3138+ // parameter aliased to the payload: that keeps the whole thing an
3139+ // expression and avoids handing a view to a generic proc.
3140+ if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
3141+ if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
3142+ (recv.ty.clone(), &m.args[0])
3143+ {
3144+ if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
3145+ {
3146+ return self.map_closure(&name, &recv, &kind, &targs, c);
3147+ }
3148+ }
3149+ }
3150+
28023151 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
28033152 // own type; `v.push(e)` takes the element type.
28043153 let arg_want = match (name.as_str(), &recv.ty) {
@@ -2967,6 +3316,35 @@ impl Lowerer {
29673316
29683317 // -------------------------------------------------------------- macros
29693318
3319+ /// The element type of a `vec![..]`, from its first element.
3320+ fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
3321+ let body = mac.tokens.to_string();
3322+ if body.trim().is_empty() {
3323+ return Ok(None);
3324+ }
3325+ let first: Option<Expr> = if body.contains(';') {
3326+ // The whole body must be consumed or the parse fails, so the
3327+ // length is parsed too even though only the element is wanted.
3328+ mac.parse_body_with(|input: syn::parse::ParseStream| {
3329+ let v: Expr = input.parse()?;
3330+ input.parse::<syn::Token![;]>()?;
3331+ let _len: Expr = input.parse()?;
3332+ Ok(v)
3333+ })
3334+ .ok()
3335+ } else {
3336+ mac.parse_body_with(
3337+ syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3338+ )
3339+ .ok()
3340+ .and_then(|p| p.into_iter().next())
3341+ };
3342+ match first {
3343+ Some(e) => Ok(self.expr(&e)?.ty),
3344+ None => Ok(None),
3345+ }
3346+ }
3347+
29703348 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
29713349 let name = path_name(&mac.path);
29723350 match name.as_str() {
@@ -3030,16 +3408,18 @@ impl Lowerer {
30303408 Ok((v, n))
30313409 })
30323410 .map_err(|e| format!("vec![elem; n]: {e}"))?;
3033- let v = self.expr(&v)?;
3411+ let want = self.vec_expect.clone();
3412+ let v = self.expr_at(&v, want.as_ref())?;
30343413 let n = self.expr(&n)?;
30353414 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
30363415 }
30373416 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
30383417 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
30393418 .map_err(|e| format!("vec!: {e}"))?;
3419+ let want = self.vec_expect.clone();
30403420 let mut parts = Vec::new();
30413421 for e in &elems {
3042- parts.push(self.expr(e)?.code);
3422+ parts.push(self.expr_at(e, want.as_ref())?.code);
30433423 }
30443424 Ok(format!("@[{}]", parts.join(", ")))
30453425 }
@@ -3231,13 +3611,37 @@ fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type
32313611 /// models with a view rather than with an owned copy.
32323612 fn returns_borrow(t: &syn::Type) -> bool {
32333613 match t {
3234- syn::Type::Reference(r) => matches!(&*r.elem, syn::Type::Slice(_)),
3614+ syn::Type::Reference(r) => match &*r.elem {
3615+ syn::Type::Slice(_) => true,
3616+ // `&str` is a borrow of someone else's bytes too, and returning it
3617+ // means returning a view, not an owned string.
3618+ syn::Type::Path(p) => p.path.is_ident("str"),
3619+ _ => false,
3620+ },
32353621 syn::Type::Paren(p) => returns_borrow(&p.elem),
32363622 syn::Type::Group(g) => returns_borrow(&g.elem),
32373623 _ => false,
32383624 }
32393625 }
32403626
3627+/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
3628+/// to the crate root, which is where a flattened module's items live unless
3629+/// they came from one of the extra input files.
3630+fn module_of(prefix: &[String]) -> String {
3631+ match prefix.last() {
3632+ Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
3633+ _ => String::new(),
3634+ }
3635+}
3636+
3637+/// The first type argument of an `Option[T]` / `Result[T, E]`.
3638+fn elem_arg(t: &Nim) -> Nim {
3639+ match t {
3640+ Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
3641+ other => other.clone(),
3642+ }
3643+}
3644+
32413645 /// The element type of a sequence-like Nim type.
32423646 fn elem_of(t: &Option<Nim>) -> Option<Nim> {
32433647 match t {
@@ -3371,7 +3775,6 @@ fn item_kind(i: &Item) -> &'static str {
33713775
33723776 fn expr_kind(e: &Expr) -> &'static str {
33733777 match e {
3374- Expr::Closure(_) => "closure",
33753778 Expr::Async(_) => "`async` block",
33763779 Expr::Await(_) => "`.await`",
33773780 Expr::Try(_) => "`?`",
@@ -76,9 +76,9 @@ enum Iter {
76 Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool },76 Elems { code: String, off: String, len: String, elem: Option<Nim>, mutable: bool },
77 /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of77 /// `a.chunks_exact(k)` / `chunks_exact_mut(k)`: the binding is a window of
78 /// `k` elements starting at `k * i`.78 /// `k` elements starting at `k * i`.
79- Chunks { code: String, k: String, elem: Option<Nim>, mutable: bool },79+ Chunks { code: String, base: String, len: String, k: String, elem: Option<Nim>, mutable: bool },
80 /// `a.windows(k)`: like `Chunks` but advancing one element at a time.80 /// `a.windows(k)`: like `Chunks` but advancing one element at a time.
81- Windows { code: String, k: String, elem: Option<Nim> },81+ Windows { code: String, base: String, len: String, k: String, elem: Option<Nim> },
82 /// `.enumerate()` — the index is the first half of the pair.82 /// `.enumerate()` — the index is the first half of the pair.
83 Enumerate(Box<Iter>),83 Enumerate(Box<Iter>),
84 /// `.zip(other)` — stops at the shorter, as Rust's does.84 /// `.zip(other)` — stops at the shorter, as Rust's does.
@@ -95,10 +95,8 @@ impl Iter {
95 if *closed { format!("({n} + 1)") } else { n }95 if *closed { format!("({n} + 1)") } else { n }
96 }96 }
97 Iter::Elems { len, .. } => len.clone(),97 Iter::Elems { len, .. } => len.clone(),
98- Iter::Chunks { code, k, .. } => format!("({}.len div int({}))", code, k),98+ Iter::Chunks { k, len, .. } => format!("({} div int({}))", len, k),
99- Iter::Windows { code, k, .. } => {99+ Iter::Windows { len, k, .. } => format!("(max(0, {} - int({}) + 1))", len, k),
100- format!("(max(0, {}.len - int({}) + 1))", code, k)
101- }
102 Iter::Enumerate(i) => i.len(),100 Iter::Enumerate(i) => i.len(),
103 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),101 Iter::Zip(a, b) => format!("min({}, {})", a.len(), b.len()),
104 }102 }
@@ -188,7 +186,16 @@ pub struct Lowerer {
188 /// Names introduced by a `for` pattern that stand for an lvalue or a186 /// Names introduced by a `for` pattern that stand for an lvalue or a
189 /// window into a container, rather than for a variable of their own.187 /// window into a container, rather than for a variable of their own.
190 alias_scopes: Vec<HashMap<String, Alias>>,188 alias_scopes: Vec<HashMap<String, Alias>>,
191- fns: HashMap<String, Sig>,189+ /// `(module, name) -> signature`. Rust keeps `lower::decode` and
190+ /// `mixed::decode` apart by module; flattening into one Nim module would
191+ /// merge them, so the module is part of the key and of the emitted name.
192+ fns: HashMap<(String, String), Sig>,
193+ /// Module being lowered: the file stem, or empty for the crate root.
194+ cur_mod: String,
195+ /// `use` brings a name into scope from another module. Flattening loses
196+ /// the module structure, so the mapping is recorded and consulted when a
197+ /// bare call is resolved.
198+ use_map: HashMap<String, String>,
192 /// struct name -> (field, type)199 /// struct name -> (field, type)
193 structs: HashMap<String, Vec<(String, Nim)>>,200 structs: HashMap<String, Vec<(String, Nim)>>,
194 enums: HashMap<String, EnumDef>,201 enums: HashMap<String, EnumDef>,
@@ -209,6 +216,8 @@ pub struct Lowerer {
209 /// proc is declared up front rather than the input being reordered --216 /// proc is declared up front rather than the input being reordered --
210 /// which would not work for mutual recursion anyway.217 /// which would not work for mutual recursion anyway.
211 forwards: Vec<String>,218 forwards: Vec<String>,
219+ /// Element type a `vec![..]` should build, from the binding's annotation.
220+ vec_expect: Option<Nim>,
212 /// While lowering a formatting impl: the `Formatter` parameter's name.221 /// While lowering a formatting impl: the `Formatter` parameter's name.
213 /// Writes through it produce the proc's string result.222 /// Writes through it produce the proc's string result.
214 fmt_param: Option<String>,223 fmt_param: Option<String>,
@@ -242,6 +251,8 @@ impl Lowerer {
242 scopes: vec![HashMap::new()],251 scopes: vec![HashMap::new()],
243 alias_scopes: vec![HashMap::new()],252 alias_scopes: vec![HashMap::new()],
244 fns: HashMap::new(),253 fns: HashMap::new(),
254+ cur_mod: String::new(),
255+ use_map: HashMap::new(),
245 structs: HashMap::new(),256 structs: HashMap::new(),
246 enums: HashMap::new(),257 enums: HashMap::new(),
247 variant_owner: HashMap::new(),258 variant_owner: HashMap::new(),
@@ -249,6 +260,7 @@ impl Lowerer {
249 fmt_impls: HashMap::new(),260 fmt_impls: HashMap::new(),
250 from_impls: HashMap::new(),261 from_impls: HashMap::new(),
251 fmt_param: None,262 fmt_param: None,
263+ vec_expect: None,
252 forwards: Vec::new(),264 forwards: Vec::new(),
253 aliases: HashMap::new(),265 aliases: HashMap::new(),
254 modules: Vec::new(),266 modules: Vec::new(),
@@ -311,25 +323,34 @@ impl Lowerer {
311 323
312 // ---------------------------------------------------------------- file324 // ---------------------------------------------------------------- file
313 325
314- pub fn lower_file(&mut self, file: &syn::File) -> Result<String, String> {326+ pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {
315 self.out.push_str(include_str!("prelude.nim"));327 self.out.push_str(include_str!("prelude.nim"));
316 self.blank();328 self.blank();
317 329
318 // Pass 0: type aliases. A signature in one file may use an alias330 // Pass 0: type aliases. A signature in one file may use an alias
319 // declared in another, and inputs are given in whatever order suits331 // declared in another, and inputs are given in whatever order suits
320 // the caller, so aliases are registered before anything is mapped.332 // the caller, so aliases are registered before anything is mapped.
321- for item in &file.items {333+ for (m, f) in files {
322- self.collect_aliases(item)?;334+ self.cur_mod = m.clone();
335+ for item in &f.items {
336+ self.collect_aliases(item)?;
337+ }
323 }338 }
324 339
325 // Pass 1: signatures and struct shapes, so that a call can be typed340 // Pass 1: signatures and struct shapes, so that a call can be typed
326 // regardless of declaration order (Rust has no forward declarations).341 // regardless of declaration order (Rust has no forward declarations).
327- for item in &file.items {342+ for (m, f) in files {
328- self.collect(item)?;343+ self.cur_mod = m.clone();
344+ for item in &f.items {
345+ self.collect(item)?;
346+ }
329 }347 }
330 // Pass 2: type definitions, which every signature may mention.348 // Pass 2: type definitions, which every signature may mention.
331- for item in &file.items {349+ for (m, f) in files {
332- self.item_types(item)?;350+ self.cur_mod = m.clone();
351+ for item in &f.items {
352+ self.item_types(item)?;
353+ }
333 }354 }
334 355
335 // Pass 3: forward declarations. Rust imposes no declaration order and356 // Pass 3: forward declarations. Rust imposes no declaration order and
@@ -343,11 +364,14 @@ impl Lowerer {
343 }364 }
344 365
345 // Pass 4: bodies.366 // Pass 4: bodies.
346- for item in &file.items {367+ for (m, f) in files {
347- self.item(item)?;368+ self.cur_mod = m.clone();
369+ for item in &f.items {
370+ self.item(item)?;
371+ }
348 }372 }
349 373
350- if self.fns.contains_key("main") {374+ if self.fns.contains_key(&(String::new(), "main".to_string())) {
351 self.blank();375 self.blank();
352 self.line("when isMainModule:");376 self.line("when isMainModule:");
353 self.indent += 1;377 self.indent += 1;
@@ -372,6 +396,7 @@ impl Lowerer {
372 return Ok(());396 return Ok(());
373 }397 }
374 match item {398 match item {
399+ Item::Use(u) => self.collect_use(&u.tree, &[]),
375 Item::Type(t) => {400 Item::Type(t) => {
376 let params: Vec<String> = t401 let params: Vec<String> = t
377 .generics402 .generics
@@ -396,6 +421,34 @@ impl Lowerer {
396 Ok(())421 Ok(())
397 }422 }
398 423
424+ /// Record what a `use` brings into scope, as `name -> module`.
425+ fn collect_use(&mut self, t: &syn::UseTree, prefix: &[String]) {
426+ use syn::UseTree;
427+ match t {
428+ UseTree::Path(p) => {
429+ let mut pre = prefix.to_vec();
430+ pre.push(p.ident.to_string());
431+ self.collect_use(&p.tree, &pre);
432+ }
433+ UseTree::Group(g) => {
434+ for t in &g.items {
435+ self.collect_use(t, prefix);
436+ }
437+ }
438+ UseTree::Name(n) => {
439+ let m = module_of(prefix);
440+ self.use_map.insert(n.ident.to_string(), m);
441+ }
442+ UseTree::Rename(r) => {
443+ let m = module_of(prefix);
444+ self.use_map.insert(r.rename.to_string(), m);
445+ }
446+ // A glob brings in an unknown set of names; resolution falls back
447+ // to the current module and the root, as it would without it.
448+ UseTree::Glob(_) => {}
449+ }
450+ }
451+
399 fn collect(&mut self, item: &Item) -> Result<(), String> {452 fn collect(&mut self, item: &Item) -> Result<(), String> {
400 // A `#[cfg(..)]` item exists only under some feature set. Dropping it453 // A `#[cfg(..)]` item exists only under some feature set. Dropping it
401 // silently would change what the program does; picking a feature set454 // silently would change what the program does; picking a feature set
@@ -408,9 +461,11 @@ impl Lowerer {
408 match item {461 match item {
409 Item::Fn(f) => {462 Item::Fn(f) => {
410 let (params, ret) = self.signature(&f.sig)?;463 let (params, ret) = self.signature(&f.sig)?;
411- let head = self.head_of(&f.sig.ident.to_string(), &f.sig, None)?;464+ let name = f.sig.ident.to_string();
412- self.forwards.push(head);465+ let nim = self.fn_name(&self.cur_mod, &name);
413- self.fns.insert(f.sig.ident.to_string(), Sig { params, ret });466+ self.forwards.push(self.head_of(&nim, &f.sig, None)?);
467+ self.fns
468+ .insert((self.cur_mod.clone(), name), Sig { params, ret });
414 }469 }
415 Item::Struct(s) => {470 Item::Struct(s) => {
416 let mut fields = Vec::new();471 let mut fields = Vec::new();
@@ -637,6 +692,38 @@ impl Lowerer {
637 self.expand(&substitute(target, params, &args), depth + 1)692 self.expand(&substitute(target, params, &args), depth + 1)
638 }693 }
639 694
695+ /// The Nim name for a function, qualified by its module.
696+ fn fn_name(&self, module: &str, name: &str) -> String {
697+ if module.is_empty() {
698+ ident(name)
699+ } else {
700+ format!("{}_{}", module, ident(name))
701+ }
702+ }
703+
704+ /// Resolve a call path to the module and name it refers to: an explicit
705+ /// `mixed::decode`, then the current module, then the crate root.
706+ fn resolve_fn(&self, path: &syn::Path) -> Option<(String, String)> {
707+ let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
708+ let last = segs.last()?.clone();
709+ if segs.len() >= 2 {
710+ let q = &segs[segs.len() - 2];
711+ if self.fns.contains_key(&(q.clone(), last.clone())) {
712+ return Some((q.clone(), last));
713+ }
714+ }
715+ let imported = self.use_map.get(&last).cloned();
716+ for m in [Some(self.cur_mod.clone()), imported, Some(String::new())]
717+ .into_iter()
718+ .flatten()
719+ {
720+ if self.fns.contains_key(&(m.clone(), last.clone())) {
721+ return Some((m, last));
722+ }
723+ }
724+ None
725+ }
726+
640 /// The Nim `proc` head for a Rust signature, used both for the forward727 /// The Nim `proc` head for a Rust signature, used both for the forward
641 /// declaration and for the definition, so the two cannot drift apart.728 /// declaration and for the definition, so the two cannot drift apart.
642 fn head_of(729 fn head_of(
@@ -681,6 +768,8 @@ impl Lowerer {
681 }768 }
682 769
683 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {770 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
771+ // `unsafe fn` marks a contract for callers; it does not change what
772+ // the body means, so it lowers like any other proc.
684 if sig.asyncness.is_some() {773 if sig.asyncness.is_some() {
685 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));774 return Err(format!("`async fn {}`: Nim has no equivalent", sig.ident));
686 }775 }
@@ -749,7 +838,10 @@ impl Lowerer {
749 838
750 fn item_inner(&mut self, item: &Item) -> Result<(), String> {839 fn item_inner(&mut self, item: &Item) -> Result<(), String> {
751 match item {840 match item {
752- Item::Fn(f) => self.func(&f.sig, &f.block, None),841+ Item::Fn(f) => {
842+ let nim = self.fn_name(&self.cur_mod, &f.sig.ident.to_string());
843+ self.func_named(&nim, &f.sig, &f.block, None)
844+ }
753 Item::Struct(s) => {845 Item::Struct(s) => {
754 let name = s.ident.to_string();846 let name = s.ident.to_string();
755 let fields = self.structs[&name].clone();847 let fields = self.structs[&name].clone();
@@ -1384,6 +1476,11 @@ impl Lowerer {
1384 self.nested_block(&b.block)?;1476 self.nested_block(&b.block)?;
1385 Ok(None)1477 Ok(None)
1386 }1478 }
1479+ Expr::Unsafe(u) => {
1480+ // Transparent in statement position too, for the same reason.
1481+ self.nested_block_flat(&u.block)?;
1482+ Ok(None)
1483+ }
1387 Expr::Match(_) => {1484 Expr::Match(_) => {
1388 self.match_stmt(e)?;1485 self.match_stmt(e)?;
1389 Ok(None)1486 Ok(None)
@@ -1597,23 +1694,21 @@ impl Lowerer {
1597 Ok(Iter::Zip(Box::new(a), Box::new(b)))1694 Ok(Iter::Zip(Box::new(a), Box::new(b)))
1598 }1695 }
1599 "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => {1696 "chunks_exact" | "chunks_exact_mut" if m.args.len() == 1 => {
1600- let recv = self.expr(&m.receiver)?;1697+ let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
1601 let k = self.expr(&m.args[0])?;1698 let k = self.expr(&m.args[0])?;
1602 Ok(Iter::Chunks {1699 Ok(Iter::Chunks {
1603- code: recv.code,1700+ code,
1701+ base,
1702+ len,
1604 k: k.code,1703 k: k.code,
1605- elem: elem_of(&recv.ty),1704+ elem,
1606 mutable: name.ends_with("_mut"),1705 mutable: name.ends_with("_mut"),
1607 })1706 })
1608 }1707 }
1609 "windows" if m.args.len() == 1 => {1708 "windows" if m.args.len() == 1 => {
1610- let recv = self.expr(&m.receiver)?;1709+ let (code, base, len, elem) = self.slice_parts(&m.receiver)?;
1611 let k = self.expr(&m.args[0])?;1710 let k = self.expr(&m.args[0])?;
1612- Ok(Iter::Windows {1711+ Ok(Iter::Windows { code, base, len, k: k.code, elem })
1613- code: recv.code,
1614- k: k.code,
1615- elem: elem_of(&recv.ty),
1616- })
1617 }1712 }
1618 other => Err(format!(1713 other => Err(format!(
1619 "iterator adaptor `.{other}()` is not implemented; it has \1714 "iterator adaptor `.{other}()` is not implemented; it has \
@@ -1626,13 +1721,7 @@ impl Lowerer {
1626 // A `for` binding that is itself a window iterates that window,1721 // A `for` binding that is itself a window iterates that window,
1627 // not the whole container it points into.1722 // not the whole container it points into.
1628 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) {1723 if let Some(Alias::Window { code, off, len, elem }) = self.window_of(other) {
1629- return Ok(Iter::Elems {1724+ return Ok(Iter::Elems { code, off, len, elem, mutable: false });
1630- code,
1631- off,
1632- len,
1633- elem,
1634- mutable: false,
1635- });
1636 }1725 }
1637 let v = self.expr(other)?;1726 let v = self.expr(other)?;
1638 Ok(Iter::Elems {1727 Ok(Iter::Elems {
@@ -1666,6 +1755,10 @@ impl Lowerer {
1666 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),1755 "a `zip`/`enumerate` loop needs a two-element tuple pattern".into(),
1667 ),1756 ),
1668 (Pat::Wild(_), _) => Ok(()),1757 (Pat::Wild(_), _) => Ok(()),
1758+ // `for &byte in xs` — the `&` destructures the reference, which in
1759+ // Nim is already the value.
1760+ (Pat::Reference(r), _) => self.bind_pattern(&r.pat, it, i),
1761+ (Pat::Paren(p), _) => self.bind_pattern(&p.pat, it, i),
1669 (Pat::Ident(id), _) => {1762 (Pat::Ident(id), _) => {
1670 let name = id.ident.to_string();1763 let name = id.ident.to_string();
1671 match it {1764 match it {
@@ -1711,24 +1804,24 @@ impl Lowerer {
1711 }1804 }
1712 Ok(())1805 Ok(())
1713 }1806 }
1714- Iter::Chunks { code, k, elem, .. } => {1807+ Iter::Chunks { code, base, k, elem, .. } => {
1715 self.bind_alias(1808 self.bind_alias(
1716 &name,1809 &name,
1717 Alias::Window {1810 Alias::Window {
1718 code: code.clone(),1811 code: code.clone(),
1719- off: format!("({} * int({}))", i, k),1812+ off: format!("({} + {} * int({}))", base, i, k),
1720 len: format!("int({})", k),1813 len: format!("int({})", k),
1721 elem: elem.clone(),1814 elem: elem.clone(),
1722 },1815 },
1723 );1816 );
1724 Ok(())1817 Ok(())
1725 }1818 }
1726- Iter::Windows { code, k, elem } => {1819+ Iter::Windows { code, base, k, elem, .. } => {
1727 self.bind_alias(1820 self.bind_alias(
1728 &name,1821 &name,
1729 Alias::Window {1822 Alias::Window {
1730 code: code.clone(),1823 code: code.clone(),
1731- off: i.to_string(),1824+ off: format!("({} + {})", base, i),
1732 len: format!("int({})", k),1825 len: format!("int({})", k),
1733 elem: elem.clone(),1826 elem: elem.clone(),
1734 },1827 },
@@ -2140,9 +2233,10 @@ impl Lowerer {
2140 }2233 }
2141 // A top-level function used as a value, e.g. passed to a2234 // A top-level function used as a value, e.g. passed to a
2142 // parameter of `impl Fn(..)` type.2235 // parameter of `impl Fn(..)` type.
2143- if let Some(sig) = self.fns.get(&name) {2236+ if let Some(k) = self.resolve_fn(&p.path) {
2237+ let sig = &self.fns[&k];
2144 let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone()));2238 let t = Nim::Proc(sig.params.clone(), Box::new(sig.ret.clone()));
2145- return Ok(Val::new(ident(&name), Some(t)));2239+ return Ok(Val::new(self.fn_name(&k.0, &k.1), Some(t)));
2146 }2240 }
2147 Ok(Val::new(ident(&name), None))2241 Ok(Val::new(ident(&name), None))
2148 }2242 }
@@ -2220,9 +2314,38 @@ impl Lowerer {
2220 };2314 };
2221 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))2315 Ok(Val::new(format!("{}.{}", base.code, ident(&name)), t))
2222 }2316 }
2317+ // `unsafe` is a permission marker, not a semantic change: it does
2318+ // not alter what the enclosed operations mean. So the block is
2319+ // transparent here, and each operation inside still goes through
2320+ // the ordinary lowering -- and is still rejected if it has no
2321+ // faithful mapping.
2322+ Expr::Unsafe(u) => match single_expr(&u.block) {
2323+ Some(e) => self.expr_at(e, expect),
2324+ None => Err("an `unsafe` block used as a value must be a single \
2325+ expression"
2326+ .into()),
2327+ },
2328+ Expr::Closure(c) => self.closure(c, expect),
2223 Expr::Try(t) => self.try_op(t),2329 Expr::Try(t) => self.try_op(t),
2224 Expr::Call(c) => self.call(c, expect),2330 Expr::Call(c) => self.call(c, expect),
2225 Expr::MethodCall(m) => self.method(m, expect),2331 Expr::MethodCall(m) => self.method(m, expect),
2332+ Expr::Macro(m) if path_name(&m.mac.path) == "vec" => {
2333+ // `vec![..]`'s elements take their type from the annotation on
2334+ // the binding, exactly as Rust's would.
2335+ let want = match expect {
2336+ Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()),
2337+ _ => None,
2338+ };
2339+ let saved = std::mem::replace(&mut self.vec_expect, want.clone());
2340+ let code = self.macro_call(&m.mac);
2341+ self.vec_expect = saved;
2342+ let code = code?;
2343+ let ty = match want {
2344+ Some(e) => Some(Nim::Seq(Box::new(e))),
2345+ None => self.vec_elem(&m.mac)?.map(|e| Nim::Seq(Box::new(e))),
2346+ };
2347+ Ok(Val::new(code, ty))
2348+ }
2226 Expr::Macro(m) => {2349 Expr::Macro(m) => {
2227 let code = self.macro_call(&m.mac)?;2350 let code = self.macro_call(&m.mac)?;
2228 Ok(Val::new(code, None))2351 Ok(Val::new(code, None))
@@ -2567,6 +2690,190 @@ impl Lowerer {
2567 /// The early return is statements, not an expression, so they are emitted2690 /// The early return is statements, not an expression, so they are emitted
2568 /// ahead of the line being built. Every caller lowers its sub-expressions2691 /// ahead of the line being built. Every caller lowers its sub-expressions
2569 /// before emitting its own line, which is what makes that ordering hold.2692 /// before emitting its own line, which is what makes that ordering hold.
2693+ /// The container, start offset, length and element type an expression
2694+ /// denotes as a slice. A window alias contributes its own offset, so
2695+ /// `dst.get_mut(..n)` followed by `.chunks_exact_mut(2)` indexes straight
2696+ /// into the original buffer rather than through a rebuilt view.
2697+ fn slice_parts(
2698+ &mut self,
2699+ e: &Expr,
2700+ ) -> Result<(String, String, String, Option<Nim>), String> {
2701+ if let Some(Alias::Window { code, off, len, elem }) = self.window_of(e) {
2702+ return Ok((code, off, len, elem));
2703+ }
2704+ let v = self.expr(e)?;
2705+ let len = format!("{}.len", v.code);
2706+ Ok((v.code, "0".to_string(), len, elem_of(&v.ty)))
2707+ }
2708+
2709+ /// Expand `opt.map(|x| body)` / `res.and_then(|x| body)` inline.
2710+ fn map_closure(
2711+ &mut self,
2712+ what: &str,
2713+ recv: &Val,
2714+ kind: &str,
2715+ targs: &[Nim],
2716+ c: &syn::ExprClosure,
2717+ ) -> Result<Val, String> {
2718+ if c.capture.is_some() {
2719+ return Err("a `move` closure captures by value; Nim's closures \
2720+ capture by reference, and the two are not the same"
2721+ .into());
2722+ }
2723+ if c.inputs.len() != 1 {
2724+ return Err(format!("`.{what}()` takes a one-argument closure"));
2725+ }
2726+ let pname = match &c.inputs[0] {
2727+ Pat::Ident(i) => i.ident.to_string(),
2728+ Pat::Wild(_) => "unused0".into(),
2729+ _ => return Err("only plain identifier closure parameters are supported".into()),
2730+ };
2731+
2732+ let is_opt = kind == "Option";
2733+ let tmp = self.fresh("Map");
2734+ let recv_ty = Nim::Named(kind.to_string(), targs.to_vec());
2735+ self.line(&format!("let {}: {} = {}", tmp, recv_ty.render(), recv.code));
2736+
2737+ let body = match &*c.body {
2738+ Expr::Block(b) => single_expr(&b.block)
2739+ .ok_or("a closure body with statements is not implemented yet")?,
2740+ other => other,
2741+ };
2742+ self.push_scope();
2743+ // The parameter names the payload itself, so a view stays a view.
2744+ self.bind_alias(
2745+ &pname,
2746+ Alias::Value {
2747+ code: format!("{}.val", tmp),
2748+ ty: Some(targs[0].clone()),
2749+ },
2750+ );
2751+ let v = self.expr(body)?;
2752+ self.pop_scope();
2753+
2754+ let inner = v
2755+ .ty
2756+ .clone()
2757+ .ok_or_else(|| format!("cannot infer the result type of `.{what}()`"))?;
2758+ // `and_then`'s closure already returns the wrapped type; `map`'s does
2759+ // not and has to be re-wrapped.
2760+ let (test, some_branch, none_branch, out_ty) = if is_opt {
2761+ let out = if what == "map" {
2762+ Nim::Named("Option".into(), vec![inner.clone()])
2763+ } else {
2764+ inner.clone()
2765+ };
2766+ let body_code = if what == "map" {
2767+ format!("rsSome[{}]({})", inner.render(), v.code)
2768+ } else {
2769+ v.code.clone()
2770+ };
2771+ (
2772+ format!("{}.has", tmp),
2773+ body_code,
2774+ format!("rsNone[{}]()", elem_arg(&out).render()),
2775+ out,
2776+ )
2777+ } else {
2778+ let e = targs[1].clone();
2779+ let out = if what == "map" {
2780+ Nim::Named("Result".into(), vec![inner.clone(), e.clone()])
2781+ } else {
2782+ inner.clone()
2783+ };
2784+ let ok_ty = elem_arg(&out);
2785+ let body_code = if what == "map" {
2786+ format!("rsOk[{}, {}]({})", inner.render(), e.render(), v.code)
2787+ } else {
2788+ v.code.clone()
2789+ };
2790+ (
2791+ format!("{}.ok", tmp),
2792+ body_code,
2793+ format!("rsErr[{}, {}]({}.err)", ok_ty.render(), e.render(), tmp),
2794+ out,
2795+ )
2796+ };
2797+ Ok(Val::new(
2798+ format!("(if {}: {} else: {})", test, some_branch, none_branch),
2799+ Some(out_ty),
2800+ ))
2801+ }
2802+
2803+ /// `|x| x + 1` -> a Nim anonymous proc.
2804+ ///
2805+ /// Nim's closures capture by reference, as Rust's non-`move` closures do.
2806+ /// A `move` closure captures by value, which is a different thing, so it
2807+ /// is rejected rather than lowered to the same construct.
2808+ fn closure(&mut self, c: &syn::ExprClosure, expect: Option<&Nim>) -> Result<Val, String> {
2809+ if c.capture.is_some() {
2810+ return Err("a `move` closure captures by value; Nim's closures \
2811+ capture by reference, and the two are not the same"
2812+ .into());
2813+ }
2814+ let want: Option<&Vec<Nim>> = match expect {
2815+ Some(Nim::Proc(a, _)) => Some(a),
2816+ _ => None,
2817+ };
2818+
2819+ self.push_scope();
2820+ let mut parts = Vec::new();
2821+ let mut ptys = Vec::new();
2822+ for (i, p) in c.inputs.iter().enumerate() {
2823+ let (name, ann) = match p {
2824+ Pat::Ident(id) => (id.ident.to_string(), None),
2825+ Pat::Type(t) => match &*t.pat {
2826+ Pat::Ident(id) => (id.ident.to_string(), Some(self.map_ty(&t.ty)?)),
2827+ _ => return Err("only plain identifier closure parameters are supported".into()),
2828+ },
2829+ Pat::Wild(_) => (format!("unused{i}"), None),
2830+ _ => return Err("only plain identifier closure parameters are supported".into()),
2831+ };
2832+ let t = ann
2833+ .or_else(|| want.and_then(|w| w.get(i).cloned()))
2834+ .ok_or_else(|| {
2835+ format!(
2836+ "cannot infer the type of closure parameter `{name}`; \
2837+ annotate it"
2838+ )
2839+ })?;
2840+ parts.push(format!("{}: {}", ident(&name), t.render()));
2841+ self.bind(&name, t.clone());
2842+ ptys.push(t);
2843+ }
2844+
2845+ let ret_ann = match &c.output {
2846+ ReturnType::Default => None,
2847+ ReturnType::Type(_, t) => Some(self.map_ty(t)?.owned()),
2848+ };
2849+ let body = match &*c.body {
2850+ Expr::Block(b) => single_expr(&b.block)
2851+ .ok_or("a closure body with statements is not implemented yet")?,
2852+ other => other,
2853+ };
2854+ let v = self.expr_at(body, ret_ann.as_ref())?;
2855+ self.pop_scope();
2856+
2857+ let ret = ret_ann
2858+ .or_else(|| v.ty.clone())
2859+ .ok_or("cannot infer a closure's return type; annotate it")?;
2860+ Ok(Val::new(
2861+ format!("(proc ({}): {} = {})", parts.join(", "), ret.render(), v.code),
2862+ Some(Nim::Proc(ptys, Box::new(ret))),
2863+ ))
2864+ }
2865+
2866+ /// Lower a block's statements at the current indentation, without opening
2867+ /// a Nim `block:` -- used for `unsafe { .. }`, which introduces no scope
2868+ /// of its own in the generated code.
2869+ fn nested_block_flat(&mut self, b: &syn::Block) -> Result<(), String> {
2870+ self.push_scope();
2871+ let tail = self.block_body(b)?;
2872+ self.emit_tail(tail);
2873+ self.pop_scope();
2874+ Ok(())
2875+ }
2876+
2570 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {2877 fn try_op(&mut self, t: &syn::ExprTry) -> Result<Val, String> {
2571 if self.in_loop_cond {2878 if self.in_loop_cond {
2572 return Err("`?` in a loop condition is not implemented yet: the \2879 return Err("`?` in a loop condition is not implemented yet: the \
@@ -2613,24 +2920,29 @@ impl Lowerer {
2613 (Nim::Named(a, ai), Nim::Named(b, bi))2920 (Nim::Named(a, ai), Nim::Named(b, bi))
2614 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>2921 if a == "Result" && b == "Result" && ai.len() == 2 && bi.len() == 2 =>
2615 {2922 {
2616- // Rust inserts a `From::from` on the error here. We only accept2923+ // Rust inserts a `From::from` on the error here. Where the
2617- // the case where the error types already agree, rather than2924+ // types differ we call the crate's own `impl From`; we never
2618- // silently dropping a conversion that might not be the identity.2925+ // assume the conversion is the identity.
2619- if ai[1] != bi[1] {2926+ let err = if ai[1] == bi[1] {
2620- return Err(format!(2927+ format!("{}.err", tmp)
2621- "`?` would need `From<{}> for {}`: an error-type conversion \2928+ } else {
2622- is not implemented, and assuming it is the identity would \2929+ let key = (type_name(&ai[1]), type_name(&bi[1]));
2623- be a guess",2930+ let f = self.from_impls.get(&key).cloned().ok_or_else(|| {
2624- ai[1].render(),2931+ format!(
2625- bi[1].render()2932+ "`?` needs `From<{}> for {}` to convert the error, and \
2626- ));2933+ no such `impl` is in scope; assuming the conversion is \
2627- }2934+ the identity would be a guess",
2935+ key.0, key.1
2936+ )
2937+ })?;
2938+ format!("{}({}.err)", f, tmp)
2939+ };
2628 self.line(&format!("if not {}.ok:", tmp));2940 self.line(&format!("if not {}.ok:", tmp));
2629 self.line(&format!(2941 self.line(&format!(
2630- " return rsErr[{}, {}]({}.err)",2942+ " return rsErr[{}, {}]({})",
2631 bi[0].render(),2943 bi[0].render(),
2632 bi[1].render(),2944 bi[1].render(),
2633- tmp2945+ err
2634 ));2946 ));
2635 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))2947 Ok(Val::new(format!("{}.val", tmp), Some(ai[0].clone())))
2636 }2948 }
@@ -2655,9 +2967,10 @@ impl Lowerer {
2655 return Err("only calls to named functions are supported".into());2967 return Err("only calls to named functions are supported".into());
2656 };2968 };
2657 let name = path_name(&p.path);2969 let name = path_name(&p.path);
2658- let ptys: Vec<Nim> = self2970+ let target = self.resolve_fn(&p.path);
2659- .fns2971+ let ptys: Vec<Nim> = target
2660- .get(&name)2972+ .as_ref()
2973+ .and_then(|k| self.fns.get(k))
2661 .map(|s| s.params.clone())2974 .map(|s| s.params.clone())
2662 .unwrap_or_default();2975 .unwrap_or_default();
2663 let mut args = Vec::new();2976 let mut args = Vec::new();
@@ -2707,6 +3020,15 @@ impl Lowerer {
2707 _ => {}3020 _ => {}
2708 }3021 }
2709 3022
3023+ // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
3024+ // string view; no copy, no validation, same memory.
3025+ if name == "from_utf8_unchecked" && codes.len() == 1 {
3026+ return Ok(Val::new(
3027+ format!("rsStrView({})", codes[0]),
3028+ Some(Nim::OpenArray(Box::new(Nim::Prim("char".into())))),
3029+ ));
3030+ }
3031+
2710 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.3032 // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`.
2711 if let Some((def, v)) = self.resolve_variant(&p.path) {3033 if let Some((def, v)) = self.resolve_variant(&p.path) {
2712 return Ok(Val::new(3034 return Ok(Val::new(
@@ -2725,17 +3047,18 @@ impl Lowerer {
2725 Some((*ret).clone()),3047 Some((*ret).clone()),
2726 ));3048 ));
2727 }3049 }
2728- let ret = self.fns.get(&name).map(|s| s.ret.clone());3050+ let ret = target.as_ref().and_then(|k| self.fns.get(k)).map(|s| s.ret.clone());
2729 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {3051 if ret.is_none() && !self.structs.contains_key(&name) && !self.enums.contains_key(&name) {
2730 return Err(format!(3052 return Err(format!(
2731 "call to unknown function `{name}`; only functions defined in \3053 "call to unknown function `{name}`; only functions defined in \
2732 this file and the supported standard-library subset can be lowered"3054 this file and the supported standard-library subset can be lowered"
2733 ));3055 ));
2734 }3056 }
2735- Ok(Val::new(3057+ let nim = match &target {
2736- format!("{}({})", ident(&name), codes.join(", ")),3058+ Some((m, n)) => self.fn_name(m, n),
2737- ret,3059+ None => ident(&name),
2738- ))3060+ };
3061+ Ok(Val::new(format!("{}({})", nim, codes.join(", ")), ret))
2739 }3062 }
2740 3063
2741 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {3064 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
@@ -2768,6 +3091,7 @@ impl Lowerer {
2768 && matches!(m.args.first(), Some(Expr::Range(_)))3091 && matches!(m.args.first(), Some(Expr::Range(_)))
2769 {3092 {
2770 let Some(Expr::Range(r)) = m.args.first() else { unreachable!() };3093 let Some(Expr::Range(r)) = m.args.first() else { unreachable!() };
3094+ let (code, base, blen, belem) = self.slice_parts(&m.receiver)?;
2771 let lo = match &r.start {3095 let lo = match &r.start {
2772 Some(e) => format!("int({})", self.expr(e)?.code),3096 Some(e) => format!("int({})", self.expr(e)?.code),
2773 None => "0".into(),3097 None => "0".into(),
@@ -2779,9 +3103,18 @@ impl Lowerer {
2779 (Some(e), syn::RangeLimits::Closed(_)) => {3103 (Some(e), syn::RangeLimits::Closed(_)) => {
2780 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)3104 format!("(int({}) - {} + 1)", self.expr(e)?.code, lo)
2781 }3105 }
2782- (None, _) => format!("({}.len - {})", recv.code, lo),3106+ (None, _) => format!("({} - {})", blen, lo),
2783 };3107 };
2784- let elem = elem_of(&rt0).ok_or("cannot infer the element type of this slice")?;3108+ // Hoisted, so the bounds are computed once -- as Rust computes
3109+ // them once -- and cannot be re-evaluated later in a scope where
3110+ // the names they mention have been shadowed by a loop pattern.
3111+ let off_t = self.fresh("Off");
3112+ let len_t = self.fresh("Len");
3113+ self.line(&format!("let {}: int = {} + {}", off_t, base, lo));
3114+ self.line(&format!("let {}: int = {}", len_t, len));
3115+ let elem = belem
3116+ .or_else(|| elem_of(&rt0))
3117+ .ok_or("cannot infer the element type of this slice")?;
2785 let mut v = Val::new(3118 let mut v = Val::new(
2786 String::new(),3119 String::new(),
2787 Some(Nim::Named(3120 Some(Nim::Named(
@@ -2789,16 +3122,32 @@ impl Lowerer {
2789 vec![Nim::OpenArray(Box::new(elem.clone()))],3122 vec![Nim::OpenArray(Box::new(elem.clone()))],
2790 )),3123 )),
2791 );3124 );
2792- v.guard = Some(format!("({} + {} <= {}.len)", lo, len, recv.code));3125+ v.guard = Some(format!("({} + {} <= {})", off_t, len_t, blen));
2793 v.window = Some(Alias::Window {3126 v.window = Some(Alias::Window {
2794- code: recv.code.clone(),3127+ code,
2795- off: lo,3128+ off: off_t,
2796- len,3129+ len: len_t,
2797 elem: Some(elem),3130 elem: Some(elem),
2798 });3131 });
2799 return Ok(v);3132 return Ok(v);
2800 }3133 }
2801 3134
3135+ // `.map`/`.and_then` over an `Option`/`Result` take a closure whose
3136+ // parameter type comes from the receiver, so they are handled before
3137+ // the arguments are lowered. The closure is expanded inline, with its
3138+ // parameter aliased to the payload: that keeps the whole thing an
3139+ // expression and avoids handing a view to a generic proc.
3140+ if matches!(name.as_str(), "map" | "and_then") && m.args.len() == 1 {
3141+ if let (Some(Nim::Named(kind, targs)), Expr::Closure(c)) =
3142+ (recv.ty.clone(), &m.args[0])
3143+ {
3144+ if (kind == "Option" && targs.len() == 1) || (kind == "Result" && targs.len() == 2)
3145+ {
3146+ return self.map_closure(&name, &recv, &kind, &targs, c);
3147+ }
3148+ }
3149+ }
3150+
2802 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's3151 // `x.wrapping_add(1)` and `x.min(3)` take an argument of the receiver's
2803 // own type; `v.push(e)` takes the element type.3152 // own type; `v.push(e)` takes the element type.
2804 let arg_want = match (name.as_str(), &recv.ty) {3153 let arg_want = match (name.as_str(), &recv.ty) {
@@ -2967,6 +3316,35 @@ impl Lowerer {
2967 3316
2968 // -------------------------------------------------------------- macros3317 // -------------------------------------------------------------- macros
2969 3318
3319+ /// The element type of a `vec![..]`, from its first element.
3320+ fn vec_elem(&mut self, mac: &syn::Macro) -> Result<Option<Nim>, String> {
3321+ let body = mac.tokens.to_string();
3322+ if body.trim().is_empty() {
3323+ return Ok(None);
3324+ }
3325+ let first: Option<Expr> = if body.contains(';') {
3326+ // The whole body must be consumed or the parse fails, so the
3327+ // length is parsed too even though only the element is wanted.
3328+ mac.parse_body_with(|input: syn::parse::ParseStream| {
3329+ let v: Expr = input.parse()?;
3330+ input.parse::<syn::Token![;]>()?;
3331+ let _len: Expr = input.parse()?;
3332+ Ok(v)
3333+ })
3334+ .ok()
3335+ } else {
3336+ mac.parse_body_with(
3337+ syn::punctuated::Punctuated::<Expr, syn::Token![,]>::parse_terminated,
3338+ )
3339+ .ok()
3340+ .and_then(|p| p.into_iter().next())
3341+ };
3342+ match first {
3343+ Some(e) => Ok(self.expr(&e)?.ty),
3344+ None => Ok(None),
3345+ }
3346+ }
3347+
2970 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {3348 fn macro_call(&mut self, mac: &syn::Macro) -> Result<String, String> {
2971 let name = path_name(&mac.path);3349 let name = path_name(&mac.path);
2972 match name.as_str() {3350 match name.as_str() {
@@ -3030,16 +3408,18 @@ impl Lowerer {
3030 Ok((v, n))3408 Ok((v, n))
3031 })3409 })
3032 .map_err(|e| format!("vec![elem; n]: {e}"))?;3410 .map_err(|e| format!("vec![elem; n]: {e}"))?;
3033- let v = self.expr(&v)?;3411+ let want = self.vec_expect.clone();
3412+ let v = self.expr_at(&v, want.as_ref())?;
3034 let n = self.expr(&n)?;3413 let n = self.expr(&n)?;
3035 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));3414 return Ok(format!("newSeqWith(int({}), {})", n.code, v.code));
3036 }3415 }
3037 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac3416 let elems: syn::punctuated::Punctuated<Expr, syn::Token![,]> = mac
3038 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)3417 .parse_body_with(syn::punctuated::Punctuated::parse_terminated)
3039 .map_err(|e| format!("vec!: {e}"))?;3418 .map_err(|e| format!("vec!: {e}"))?;
3419+ let want = self.vec_expect.clone();
3040 let mut parts = Vec::new();3420 let mut parts = Vec::new();
3041 for e in &elems {3421 for e in &elems {
3042- parts.push(self.expr(e)?.code);3422+ parts.push(self.expr_at(e, want.as_ref())?.code);
3043 }3423 }
3044 Ok(format!("@[{}]", parts.join(", ")))3424 Ok(format!("@[{}]", parts.join(", ")))
3045 }3425 }
@@ -3231,13 +3611,37 @@ fn substitute(t: &syn::Type, params: &[String], args: &[syn::Type]) -> syn::Type
3231 /// models with a view rather than with an owned copy.3611 /// models with a view rather than with an owned copy.
3232 fn returns_borrow(t: &syn::Type) -> bool {3612 fn returns_borrow(t: &syn::Type) -> bool {
3233 match t {3613 match t {
3234- syn::Type::Reference(r) => matches!(&*r.elem, syn::Type::Slice(_)),3614+ syn::Type::Reference(r) => match &*r.elem {
3615+ syn::Type::Slice(_) => true,
3616+ // `&str` is a borrow of someone else's bytes too, and returning it
3617+ // means returning a view, not an owned string.
3618+ syn::Type::Path(p) => p.path.is_ident("str"),
3619+ _ => false,
3620+ },
3235 syn::Type::Paren(p) => returns_borrow(&p.elem),3621 syn::Type::Paren(p) => returns_borrow(&p.elem),
3236 syn::Type::Group(g) => returns_borrow(&g.elem),3622 syn::Type::Group(g) => returns_borrow(&g.elem),
3237 _ => false,3623 _ => false,
3238 }3624 }
3239 }3625 }
3240 3626
3627+/// The module a `use` prefix names. `crate`, `self` and `super` all resolve
3628+/// to the crate root, which is where a flattened module's items live unless
3629+/// they came from one of the extra input files.
3630+fn module_of(prefix: &[String]) -> String {
3631+ match prefix.last() {
3632+ Some(m) if m != "crate" && m != "self" && m != "super" => m.clone(),
3633+ _ => String::new(),
3634+ }
3635+}
3636+
3637+/// The first type argument of an `Option[T]` / `Result[T, E]`.
3638+fn elem_arg(t: &Nim) -> Nim {
3639+ match t {
3640+ Nim::Named(_, a) if !a.is_empty() => a[0].clone(),
3641+ other => other.clone(),
3642+ }
3643+}
3644+
3241 /// The element type of a sequence-like Nim type.3645 /// The element type of a sequence-like Nim type.
3242 fn elem_of(t: &Option<Nim>) -> Option<Nim> {3646 fn elem_of(t: &Option<Nim>) -> Option<Nim> {
3243 match t {3647 match t {
@@ -3371,7 +3775,6 @@ fn item_kind(i: &Item) -> &'static str {
3371 3775
3372 fn expr_kind(e: &Expr) -> &'static str {3776 fn expr_kind(e: &Expr) -> &'static str {
3373 match e {3777 match e {
3374- Expr::Closure(_) => "closure",
3375 Expr::Async(_) => "`async` block",3778 Expr::Async(_) => "`async` block",
3376 Expr::Await(_) => "`.await`",3779 Expr::Await(_) => "`.await`",
3377 Expr::Try(_) => "`?`",3780 Expr::Try(_) => "`?`",
modified src/main.rs +15 -5
@@ -67,15 +67,25 @@ fn run() -> Result<(), String> {
6767 // inside a single output file, so the items are flattened in argument
6868 // order. A name collision between two files is a Nim compile error, which
6969 // is a loud failure rather than a silently shadowed definition.
70- let mut items = Vec::new();
71- for input in &inputs {
70+ // The first input is the crate root; each later one is a module named by
71+ // its file stem. That is what keeps `lower::decode` and `mixed::decode`
72+ // apart once everything is flattened into a single Nim module.
73+ let mut files = Vec::new();
74+ for (i, input) in inputs.iter().enumerate() {
7275 let src = std::fs::read_to_string(input)
7376 .map_err(|e| format!("cannot read {}: {e}", input.display()))?;
7477 let parsed: syn::File = syn::parse_file(&src)
7578 .map_err(|e| format!("{}: parse error: {e}", input.display()))?;
76- items.extend(parsed.items);
79+ let module = if i == 0 {
80+ String::new()
81+ } else {
82+ input
83+ .file_stem()
84+ .map(|s| s.to_string_lossy().into_owned())
85+ .unwrap_or_default()
86+ };
87+ files.push((module, parsed));
7788 }
78- let file = syn::File { shebang: None, frontmatter: None, attrs: Vec::new(), items };
7989
8090 let mut lowerer = lower::Lowerer::new();
8191 lowerer.features = features;
@@ -84,7 +94,7 @@ fn run() -> Result<(), String> {
8494 .iter()
8595 .filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned()))
8696 .collect();
87- let nim = lowerer.lower_file(&file)?;
97+ let nim = lowerer.lower_file(&files)?;
8898
8999 match output {
90100 Some(p) => std::fs::write(&p, nim)
@@ -67,15 +67,25 @@ fn run() -> Result<(), String> {
67 // inside a single output file, so the items are flattened in argument67 // 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, which68 // order. A name collision between two files is a Nim compile error, which
69 // is a loud failure rather than a silently shadowed definition.69 // is a loud failure rather than a silently shadowed definition.
70- let mut items = Vec::new();70+ // The first input is the crate root; each later one is a module named by
71- for input in &inputs {71+ // its file stem. That is what keeps `lower::decode` and `mixed::decode`
72+ // apart once everything is flattened into a single Nim module.
73+ let mut files = Vec::new();
74+ for (i, input) in inputs.iter().enumerate() {
72 let src = std::fs::read_to_string(input)75 let src = std::fs::read_to_string(input)
73 .map_err(|e| format!("cannot read {}: {e}", input.display()))?;76 .map_err(|e| format!("cannot read {}: {e}", input.display()))?;
74 let parsed: syn::File = syn::parse_file(&src)77 let parsed: syn::File = syn::parse_file(&src)
75 .map_err(|e| format!("{}: parse error: {e}", input.display()))?;78 .map_err(|e| format!("{}: parse error: {e}", input.display()))?;
76- items.extend(parsed.items);79+ let module = if i == 0 {
80+ String::new()
81+ } else {
82+ input
83+ .file_stem()
84+ .map(|s| s.to_string_lossy().into_owned())
85+ .unwrap_or_default()
86+ };
87+ files.push((module, parsed));
77 }88 }
78- let file = syn::File { shebang: None, frontmatter: None, attrs: Vec::new(), items };
79 89
80 let mut lowerer = lower::Lowerer::new();90 let mut lowerer = lower::Lowerer::new();
81 lowerer.features = features;91 lowerer.features = features;
@@ -84,7 +94,7 @@ fn run() -> Result<(), String> {
84 .iter()94 .iter()
85 .filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned()))95 .filter_map(|p| p.file_stem().map(|s| s.to_string_lossy().into_owned()))
86 .collect();96 .collect();
87- let nim = lowerer.lower_file(&file)?;97+ let nim = lowerer.lower_file(&files)?;
88 98
89 match output {99 match output {
90 Some(p) => std::fs::write(&p, nim)100 Some(p) => std::fs::write(&p, nim)
modified src/prelude.nim +16 -0
@@ -152,6 +152,22 @@ proc rsPad*(s: string, width: int, zero: bool): string =
152152 else:
153153 repeat(' ', fill) & s
154154
155+proc rsStrView*(b: openArray[uint8]): openArray[char] =
156+ ## Rust's `str::from_utf8_unchecked` reinterprets a byte slice as a string
157+ ## slice without copying or validating. Nim's `char` and `uint8` are both one
158+ ## byte, so the same view is handed back over the same memory -- writes
159+ ## through the original are visible here, as they are in Rust.
160+ if b.len == 0:
161+ result = toOpenArray(cast[ptr UncheckedArray[char]](nil), 0, -1)
162+ else:
163+ result = toOpenArray(cast[ptr UncheckedArray[char]](unsafeAddr b[0]), 0, b.len - 1)
164+
165+proc rsDisplay*(x: openArray[char]): string =
166+ result = newStringOfCap(x.len)
167+ for c in x: result.add(c)
168+
169+proc rsDebug*(x: openArray[char]): string = rsDebug(rsDisplay(x))
170+
155171 proc rsBytes*(s: string): seq[uint8] =
156172 ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is
157173 ## already those bytes, so this is a reinterpretation, not a conversion.
@@ -152,6 +152,22 @@ proc rsPad*(s: string, width: int, zero: bool): string =
152 else:152 else:
153 repeat(' ', fill) & s153 repeat(' ', fill) & s
154 154
155+proc rsStrView*(b: openArray[uint8]): openArray[char] =
156+ ## Rust's `str::from_utf8_unchecked` reinterprets a byte slice as a string
157+ ## slice without copying or validating. Nim's `char` and `uint8` are both one
158+ ## byte, so the same view is handed back over the same memory -- writes
159+ ## through the original are visible here, as they are in Rust.
160+ if b.len == 0:
161+ result = toOpenArray(cast[ptr UncheckedArray[char]](nil), 0, -1)
162+ else:
163+ result = toOpenArray(cast[ptr UncheckedArray[char]](unsafeAddr b[0]), 0, b.len - 1)
164+
165+proc rsDisplay*(x: openArray[char]): string =
166+ result = newStringOfCap(x.len)
167+ for c in x: result.add(c)
168+
169+proc rsDebug*(x: openArray[char]): string = rsDebug(rsDisplay(x))
170+
155 proc rsBytes*(s: string): seq[uint8] =171 proc rsBytes*(s: string): seq[uint8] =
156 ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is172 ## Rust's `str::as_bytes` is a view of the UTF-8 encoding; Nim's `string` is
157 ## already those bytes, so this is a reinterpretation, not a conversion.173 ## already those bytes, so this is a reinterpretation, not a conversion.
modified src/ty.rs +13 -4
@@ -16,8 +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.
19+ /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling
20+ /// convention (`closure`), which accepts both a plain top-level proc and
21+ /// a closure that captures -- and Rust's `impl Fn` accepts both too.
2122 Proc(Vec<Nim>, Box<Nim>),
2223 Unit,
2324 }
@@ -48,8 +49,8 @@ impl Nim {
4849 .map(|(i, t)| format!("a{}: {}", i, t.render()))
4950 .collect();
5051 match &**ret {
51- Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")),
52- r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()),
52+ Nim::Unit => format!("proc ({})", inner.join(", ")),
53+ r => format!("proc ({}): {}", inner.join(", "), r.render()),
5354 }
5455 }
5556 Nim::Unit => "void".into(),
@@ -187,6 +188,14 @@ pub fn map(t: &Type) -> Result<Nim, String> {
187188 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller
188189 // decides whether a `var` is legal in the position it is used.
189190 Type::Reference(r) => {
191+ // `&str` is a borrowed view of characters, not an owned string.
192+ // Nim accepts a `string` argument for an `openArray[char]`
193+ // parameter, so a literal still passes straight through.
194+ if let Type::Path(p) = &*r.elem {
195+ if p.path.is_ident("str") {
196+ return Ok(Nim::OpenArray(Box::new(Nim::Prim("char".into()))));
197+ }
198+ }
190199 let inner = map(&r.elem)?;
191200 if r.mutability.is_some() {
192201 Ok(Nim::Var(Box::new(inner)))
@@ -16,8 +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 calling19+ /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling
20- /// convention for a top-level proc, which is what Rust passes here.20+ /// convention (`closure`), which accepts both a plain top-level proc and
21+ /// a closure that captures -- and Rust's `impl Fn` accepts both too.
21 Proc(Vec<Nim>, Box<Nim>),22 Proc(Vec<Nim>, Box<Nim>),
22 Unit,23 Unit,
23 }24 }
@@ -48,8 +49,8 @@ impl Nim {
48 .map(|(i, t)| format!("a{}: {}", i, t.render()))49 .map(|(i, t)| format!("a{}: {}", i, t.render()))
49 .collect();50 .collect();
50 match &**ret {51 match &**ret {
51- Nim::Unit => format!("proc ({}) {{.nimcall.}}", inner.join(", ")),52+ Nim::Unit => format!("proc ({})", inner.join(", ")),
52- r => format!("proc ({}): {} {{.nimcall.}}", inner.join(", "), r.render()),53+ r => format!("proc ({}): {}", inner.join(", "), r.render()),
53 }54 }
54 }55 }
55 Nim::Unit => "void".into(),56 Nim::Unit => "void".into(),
@@ -187,6 +188,14 @@ pub fn map(t: &Type) -> Result<Nim, String> {
187 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller188 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller
188 // decides whether a `var` is legal in the position it is used.189 // decides whether a `var` is legal in the position it is used.
189 Type::Reference(r) => {190 Type::Reference(r) => {
191+ // `&str` is a borrowed view of characters, not an owned string.
192+ // Nim accepts a `string` argument for an `openArray[char]`
193+ // parameter, so a literal still passes straight through.
194+ if let Type::Path(p) = &*r.elem {
195+ if p.path.is_ident("str") {
196+ return Ok(Nim::OpenArray(Box::new(Nim::Prim("char".into()))));
197+ }
198+ }
190 let inner = map(&r.elem)?;199 let inner = map(&r.elem)?;
191 if r.mutability.is_some() {200 if r.mutability.is_some() {
192 Ok(Nim::Var(Box::new(inner)))201 Ok(Nim::Var(Box::new(inner)))
added tests/cases/026-base16ct-crate/lower.rs +78 -0
new file mode 100644
@@ -0,0 +1,78 @@
1+use crate::{Error, decode_inner, encoded_len};
2+#[cfg(feature = "alloc")]
3+use crate::{String, Vec, decoded_len};
4+
5+/// Decode a lower Base16 (hex) string into the provided destination buffer.
6+pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
7+ decode_inner(src.as_ref(), dst, decode_nibble)
8+}
9+
10+/// Decode a lower Base16 (hex) string into a byte vector.
11+#[cfg(feature = "alloc")]
12+pub fn decode_vec(input: impl AsRef<[u8]>) -> Result<Vec<u8>, Error> {
13+ let mut output = vec![0u8; decoded_len(input.as_ref())?];
14+ decode(input, &mut output)?;
15+ Ok(output)
16+}
17+
18+/// Encode the input byte slice as lower Base16.
19+///
20+/// Writes the result into the provided destination slice, returning an
21+/// ASCII-encoded lower Base16 (hex) string value.
22+pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a [u8], Error> {
23+ let dst = dst
24+ .get_mut(..encoded_len(src))
25+ .ok_or(Error::InvalidLength)?;
26+ for (src, dst) in src.iter().zip(dst.chunks_exact_mut(2)) {
27+ dst[0] = encode_nibble(src >> 4);
28+ dst[1] = encode_nibble(src & 0x0f);
29+ }
30+ Ok(dst)
31+}
32+
33+/// Encode input byte slice into a [`&str`] containing lower Base16 (hex).
34+pub fn encode_str<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, Error> {
35+ encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) })
36+}
37+
38+/// Encode input byte slice into a [`String`] containing lower Base16 (hex).
39+///
40+/// # Panics
41+/// If `input` length is greater than `usize::MAX/2`.
42+#[cfg(feature = "alloc")]
43+pub fn encode_string(input: &[u8]) -> String {
44+ let elen = encoded_len(input);
45+ let mut dst = vec![0u8; elen];
46+ let res = encode(input, &mut dst).expect("dst length is correct");
47+
48+ debug_assert_eq!(elen, res.len());
49+ unsafe { String::from_utf8_unchecked(dst) }
50+}
51+
52+/// Decode a single nibble of lower hex
53+#[inline(always)]
54+fn decode_nibble(src: u8) -> u16 {
55+ // 0-9 0x30-0x39
56+ // A-F 0x41-0x46 or a-f 0x61-0x66
57+ let byte = src as i16;
58+ let mut ret: i16 = -1;
59+
60+ // 0-9 0x30-0x39
61+ // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47
62+ ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
63+ // a-f 0x61-0x66
64+ // if (byte > 0x60 && byte < 0x67) ret += byte - 0x61 + 10 + 1; // -86
65+ ret += (((0x60i16 - byte) & (byte - 0x67)) >> 8) & (byte - 86);
66+
67+ ret as u16
68+}
69+
70+/// Encode a single nibble of hex
71+#[inline(always)]
72+fn encode_nibble(src: u8) -> u8 {
73+ let mut ret = src as i16 + 0x30;
74+ // 0-9 0x30-0x39
75+ // a-f 0x61-0x66
76+ ret += ((0x39i16 - ret) >> 8) & (0x61i16 - 0x3a);
77+ ret as u8
78+}
new file mode 100644
@@ -0,0 +1,78 @@
1+use crate::{Error, decode_inner, encoded_len};
2+#[cfg(feature = "alloc")]
3+use crate::{String, Vec, decoded_len};
4+
5+/// Decode a lower Base16 (hex) string into the provided destination buffer.
6+pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
7+ decode_inner(src.as_ref(), dst, decode_nibble)
8+}
9+
10+/// Decode a lower Base16 (hex) string into a byte vector.
11+#[cfg(feature = "alloc")]
12+pub fn decode_vec(input: impl AsRef<[u8]>) -> Result<Vec<u8>, Error> {
13+ let mut output = vec![0u8; decoded_len(input.as_ref())?];
14+ decode(input, &mut output)?;
15+ Ok(output)
16+}
17+
18+/// Encode the input byte slice as lower Base16.
19+///
20+/// Writes the result into the provided destination slice, returning an
21+/// ASCII-encoded lower Base16 (hex) string value.
22+pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a [u8], Error> {
23+ let dst = dst
24+ .get_mut(..encoded_len(src))
25+ .ok_or(Error::InvalidLength)?;
26+ for (src, dst) in src.iter().zip(dst.chunks_exact_mut(2)) {
27+ dst[0] = encode_nibble(src >> 4);
28+ dst[1] = encode_nibble(src & 0x0f);
29+ }
30+ Ok(dst)
31+}
32+
33+/// Encode input byte slice into a [`&str`] containing lower Base16 (hex).
34+pub fn encode_str<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, Error> {
35+ encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) })
36+}
37+
38+/// Encode input byte slice into a [`String`] containing lower Base16 (hex).
39+///
40+/// # Panics
41+/// If `input` length is greater than `usize::MAX/2`.
42+#[cfg(feature = "alloc")]
43+pub fn encode_string(input: &[u8]) -> String {
44+ let elen = encoded_len(input);
45+ let mut dst = vec![0u8; elen];
46+ let res = encode(input, &mut dst).expect("dst length is correct");
47+
48+ debug_assert_eq!(elen, res.len());
49+ unsafe { String::from_utf8_unchecked(dst) }
50+}
51+
52+/// Decode a single nibble of lower hex
53+#[inline(always)]
54+fn decode_nibble(src: u8) -> u16 {
55+ // 0-9 0x30-0x39
56+ // A-F 0x41-0x46 or a-f 0x61-0x66
57+ let byte = src as i16;
58+ let mut ret: i16 = -1;
59+
60+ // 0-9 0x30-0x39
61+ // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47
62+ ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
63+ // a-f 0x61-0x66
64+ // if (byte > 0x60 && byte < 0x67) ret += byte - 0x61 + 10 + 1; // -86
65+ ret += (((0x60i16 - byte) & (byte - 0x67)) >> 8) & (byte - 86);
66+
67+ ret as u16
68+}
69+
70+/// Encode a single nibble of hex
71+#[inline(always)]
72+fn encode_nibble(src: u8) -> u8 {
73+ let mut ret = src as i16 + 0x30;
74+ // 0-9 0x30-0x39
75+ // a-f 0x61-0x66
76+ ret += ((0x39i16 - ret) >> 8) & (0x61i16 - 0x3a);
77+ ret as u8
78+}
modified tests/cases/026-base16ct-crate/main.rs +25 -3
@@ -1,13 +1,16 @@
11 //@ args: run
22 // base16ct 1.0.0, transpiled as a multi-file crate.
33 //
4-// `error.rs` and `mixed.rs` are the crate's own files, byte-for-byte.
4+// `error.rs`, `lower.rs`, `upper.rs` and `mixed.rs` are the crate's own
5+// files, byte-for-byte.
56 // This file carries `lib.rs`'s core -- `decoded_len`, `encoded_len` and
67 // `decode_inner` verbatim -- plus a driver, because the runner needs a `main`.
78 // The `alloc`-gated items are off, as they are by default in the crate.
89
910 mod error;
11+mod lower;
1012 mod mixed;
13+mod upper;
1114
1215 pub use crate::error::{Error, Result};
1316
@@ -65,6 +68,7 @@ fn show(tag: &str, r: Result<&[u8]>) {
6568 fn main() {
6669 let mut buf = [0u8; 16];
6770
71+ show("lower", lower::decode(b"abcd1234", &mut buf));
6872 show("mixed-l", mixed::decode(b"abcd1234", &mut buf));
6973 show("mixed-u", mixed::decode(b"ABCD1234", &mut buf));
7074 show("mixed-m", mixed::decode(b"abCD1234", &mut buf));
@@ -76,8 +80,26 @@ fn main() {
7680 let mut small = [0u8; 2];
7781 show("short-dst", mixed::decode(b"abcd1234", &mut small));
7882
79- // `lower::encode` is not here: `lower.rs` also defines `encode_str`, whose
80- // body is a closure over an `unsafe` block, and neither is implemented.
83+ show("upper", upper::decode(b"ABCD1234", &mut buf));
84+ show("upper-rej", upper::decode(b"abcd1234", &mut buf));
85+
86+ let mut enc = [0u8; 8];
87+ show("encode", lower::encode(b"\xab\xcd\x12\x34", &mut enc));
88+ let mut encu = [0u8; 8];
89+ show("encode-up", upper::encode(b"\xab\xcd\x12\x34", &mut encu));
90+
91+ // `encode_str` is a closure over an `unsafe` block returning a borrowed
92+ // `&str` -- a view of the bytes just written, not a copy.
93+ let mut enc2 = [0u8; 8];
94+ match lower::encode_str(b"\xab\xcd\x12\x34", &mut enc2) {
95+ Ok(s) => println!("encode_str ok {} len={}", s, s.len()),
96+ Err(e) => println!("encode_str err {:?}", e),
97+ }
98+ let mut tiny = [0u8; 2];
99+ match lower::encode_str(b"\xab\xcd", &mut tiny) {
100+ Ok(s) => println!("tiny ok {}", s),
101+ Err(e) => println!("tiny err {:?} / {}", e, e),
102+ }
81103
82104 println!("{} {}", decoded_len(b"abcd").unwrap(), encoded_len(b"\xab\xcd"));
83105 }
@@ -1,13 +1,16 @@
1 //@ args: run1 //@ args: run
2 // base16ct 1.0.0, transpiled as a multi-file crate.2 // base16ct 1.0.0, transpiled as a multi-file crate.
3 //3 //
4-// `error.rs` and `mixed.rs` are the crate's own files, byte-for-byte.4+// `error.rs`, `lower.rs`, `upper.rs` and `mixed.rs` are the crate's own
5+// files, byte-for-byte.
5 // This file carries `lib.rs`'s core -- `decoded_len`, `encoded_len` and6 // This file carries `lib.rs`'s core -- `decoded_len`, `encoded_len` and
6 // `decode_inner` verbatim -- plus a driver, because the runner needs a `main`.7 // `decode_inner` verbatim -- plus a driver, because the runner needs a `main`.
7 // The `alloc`-gated items are off, as they are by default in the crate.8 // The `alloc`-gated items are off, as they are by default in the crate.
8 9
9 mod error;10 mod error;
11+mod lower;
10 mod mixed;12 mod mixed;
13+mod upper;
11 14
12 pub use crate::error::{Error, Result};15 pub use crate::error::{Error, Result};
13 16
@@ -65,6 +68,7 @@ fn show(tag: &str, r: Result<&[u8]>) {
65 fn main() {68 fn main() {
66 let mut buf = [0u8; 16];69 let mut buf = [0u8; 16];
67 70
71+ show("lower", lower::decode(b"abcd1234", &mut buf));
68 show("mixed-l", mixed::decode(b"abcd1234", &mut buf));72 show("mixed-l", mixed::decode(b"abcd1234", &mut buf));
69 show("mixed-u", mixed::decode(b"ABCD1234", &mut buf));73 show("mixed-u", mixed::decode(b"ABCD1234", &mut buf));
70 show("mixed-m", mixed::decode(b"abCD1234", &mut buf));74 show("mixed-m", mixed::decode(b"abCD1234", &mut buf));
@@ -76,8 +80,26 @@ fn main() {
76 let mut small = [0u8; 2];80 let mut small = [0u8; 2];
77 show("short-dst", mixed::decode(b"abcd1234", &mut small));81 show("short-dst", mixed::decode(b"abcd1234", &mut small));
78 82
79- // `lower::encode` is not here: `lower.rs` also defines `encode_str`, whose83+ show("upper", upper::decode(b"ABCD1234", &mut buf));
80- // body is a closure over an `unsafe` block, and neither is implemented.84+ show("upper-rej", upper::decode(b"abcd1234", &mut buf));
85+
86+ let mut enc = [0u8; 8];
87+ show("encode", lower::encode(b"\xab\xcd\x12\x34", &mut enc));
88+ let mut encu = [0u8; 8];
89+ show("encode-up", upper::encode(b"\xab\xcd\x12\x34", &mut encu));
90+
91+ // `encode_str` is a closure over an `unsafe` block returning a borrowed
92+ // `&str` -- a view of the bytes just written, not a copy.
93+ let mut enc2 = [0u8; 8];
94+ match lower::encode_str(b"\xab\xcd\x12\x34", &mut enc2) {
95+ Ok(s) => println!("encode_str ok {} len={}", s, s.len()),
96+ Err(e) => println!("encode_str err {:?}", e),
97+ }
98+ let mut tiny = [0u8; 2];
99+ match lower::encode_str(b"\xab\xcd", &mut tiny) {
100+ Ok(s) => println!("tiny ok {}", s),
101+ Err(e) => println!("tiny err {:?} / {}", e, e),
102+ }
81 103
82 println!("{} {}", decoded_len(b"abcd").unwrap(), encoded_len(b"\xab\xcd"));104 println!("{} {}", decoded_len(b"abcd").unwrap(), encoded_len(b"\xab\xcd"));
83 }105 }
added tests/cases/026-base16ct-crate/upper.rs +78 -0
new file mode 100644
@@ -0,0 +1,78 @@
1+use crate::{Error, decode_inner, encoded_len};
2+#[cfg(feature = "alloc")]
3+use crate::{String, Vec, decoded_len};
4+
5+/// Decode an upper Base16 (hex) string into the provided destination buffer.
6+pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
7+ decode_inner(src.as_ref(), dst, decode_nibble)
8+}
9+
10+/// Decode an upper Base16 (hex) string into a byte vector.
11+#[cfg(feature = "alloc")]
12+pub fn decode_vec(input: impl AsRef<[u8]>) -> Result<Vec<u8>, Error> {
13+ let mut output = vec![0u8; decoded_len(input.as_ref())?];
14+ decode(input, &mut output)?;
15+ Ok(output)
16+}
17+
18+/// Encode the input byte slice as upper Base16.
19+///
20+/// Writes the result into the provided destination slice, returning an
21+/// ASCII-encoded upper Base16 (hex) string value.
22+pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a [u8], Error> {
23+ let dst = dst
24+ .get_mut(..encoded_len(src))
25+ .ok_or(Error::InvalidLength)?;
26+ for (src, dst) in src.iter().zip(dst.chunks_exact_mut(2)) {
27+ dst[0] = encode_nibble(src >> 4);
28+ dst[1] = encode_nibble(src & 0x0f);
29+ }
30+ Ok(dst)
31+}
32+
33+/// Encode input byte slice into a [`&str`] containing upper Base16 (hex).
34+pub fn encode_str<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, Error> {
35+ encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) })
36+}
37+
38+/// Encode input byte slice into a [`String`] containing upper Base16 (hex).
39+///
40+/// # Panics
41+/// If `input` length is greater than `usize::MAX/2`.
42+#[cfg(feature = "alloc")]
43+pub fn encode_string(input: &[u8]) -> String {
44+ let elen = encoded_len(input);
45+ let mut dst = vec![0u8; elen];
46+ let res = encode(input, &mut dst).expect("dst length is correct");
47+
48+ debug_assert_eq!(elen, res.len());
49+ unsafe { String::from_utf8_unchecked(dst) }
50+}
51+
52+/// Decode a single nibble of upper hex
53+#[inline(always)]
54+fn decode_nibble(src: u8) -> u16 {
55+ // 0-9 0x30-0x39
56+ // A-F 0x41-0x46 or a-f 0x61-0x66
57+ let byte = src as i16;
58+ let mut ret: i16 = -1;
59+
60+ // 0-9 0x30-0x39
61+ // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47
62+ ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
63+ // A-F 0x41-0x46
64+ // if (byte > 0x40 && byte < 0x47) ret += byte - 0x41 + 10 + 1; // -54
65+ ret += (((0x40i16 - byte) & (byte - 0x47)) >> 8) & (byte - 54);
66+
67+ ret as u16
68+}
69+
70+/// Encode a single nibble of hex
71+#[inline(always)]
72+fn encode_nibble(src: u8) -> u8 {
73+ let mut ret = src as i16 + 0x30;
74+ // 0-9 0x30-0x39
75+ // A-F 0x41-0x46
76+ ret += ((0x39i16 - ret) >> 8) & (0x41i16 - 0x3a);
77+ ret as u8
78+}
new file mode 100644
@@ -0,0 +1,78 @@
1+use crate::{Error, decode_inner, encoded_len};
2+#[cfg(feature = "alloc")]
3+use crate::{String, Vec, decoded_len};
4+
5+/// Decode an upper Base16 (hex) string into the provided destination buffer.
6+pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
7+ decode_inner(src.as_ref(), dst, decode_nibble)
8+}
9+
10+/// Decode an upper Base16 (hex) string into a byte vector.
11+#[cfg(feature = "alloc")]
12+pub fn decode_vec(input: impl AsRef<[u8]>) -> Result<Vec<u8>, Error> {
13+ let mut output = vec![0u8; decoded_len(input.as_ref())?];
14+ decode(input, &mut output)?;
15+ Ok(output)
16+}
17+
18+/// Encode the input byte slice as upper Base16.
19+///
20+/// Writes the result into the provided destination slice, returning an
21+/// ASCII-encoded upper Base16 (hex) string value.
22+pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a [u8], Error> {
23+ let dst = dst
24+ .get_mut(..encoded_len(src))
25+ .ok_or(Error::InvalidLength)?;
26+ for (src, dst) in src.iter().zip(dst.chunks_exact_mut(2)) {
27+ dst[0] = encode_nibble(src >> 4);
28+ dst[1] = encode_nibble(src & 0x0f);
29+ }
30+ Ok(dst)
31+}
32+
33+/// Encode input byte slice into a [`&str`] containing upper Base16 (hex).
34+pub fn encode_str<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, Error> {
35+ encode(src, dst).map(|r| unsafe { core::str::from_utf8_unchecked(r) })
36+}
37+
38+/// Encode input byte slice into a [`String`] containing upper Base16 (hex).
39+///
40+/// # Panics
41+/// If `input` length is greater than `usize::MAX/2`.
42+#[cfg(feature = "alloc")]
43+pub fn encode_string(input: &[u8]) -> String {
44+ let elen = encoded_len(input);
45+ let mut dst = vec![0u8; elen];
46+ let res = encode(input, &mut dst).expect("dst length is correct");
47+
48+ debug_assert_eq!(elen, res.len());
49+ unsafe { String::from_utf8_unchecked(dst) }
50+}
51+
52+/// Decode a single nibble of upper hex
53+#[inline(always)]
54+fn decode_nibble(src: u8) -> u16 {
55+ // 0-9 0x30-0x39
56+ // A-F 0x41-0x46 or a-f 0x61-0x66
57+ let byte = src as i16;
58+ let mut ret: i16 = -1;
59+
60+ // 0-9 0x30-0x39
61+ // if (byte > 0x2f && byte < 0x3a) ret += byte - 0x30 + 1; // -47
62+ ret += (((0x2fi16 - byte) & (byte - 0x3a)) >> 8) & (byte - 47);
63+ // A-F 0x41-0x46
64+ // if (byte > 0x40 && byte < 0x47) ret += byte - 0x41 + 10 + 1; // -54
65+ ret += (((0x40i16 - byte) & (byte - 0x47)) >> 8) & (byte - 54);
66+
67+ ret as u16
68+}
69+
70+/// Encode a single nibble of hex
71+#[inline(always)]
72+fn encode_nibble(src: u8) -> u8 {
73+ let mut ret = src as i16 + 0x30;
74+ // 0-9 0x30-0x39
75+ // A-F 0x41-0x46
76+ ret += ((0x39i16 - ret) >> 8) & (0x41i16 - 0x3a);
77+ ret as u8
78+}
added tests/cases/027-closures-and-unsafe.rs +45 -0
new file mode 100644
@@ -0,0 +1,45 @@
1+// `unsafe` is a permission marker, not a semantic change: it does not alter
2+// what the enclosed operations mean, so the block is transparent and each
3+// operation inside still goes through the ordinary lowering.
4+//
5+// Nim's closures capture by reference, as Rust's non-`move` closures do.
6+
7+fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 {
8+ f(x)
9+}
10+
11+fn twice(x: i32) -> i32 {
12+ x * 2
13+}
14+
15+fn halve(n: i32) -> Result<i32, i32> {
16+ if n % 2 == 0 { Ok(n / 2) } else { Err(n) }
17+}
18+
19+fn as_str(b: &[u8]) -> &str {
20+ unsafe { core::str::from_utf8_unchecked(b) }
21+}
22+
23+fn main() {
24+ let add_one = |x: i32| x + 1;
25+ println!("{}", apply(add_one, 10));
26+ println!("{}", apply(twice, 10));
27+ println!("{}", apply(|x: i32| x * x, 7));
28+
29+ // A closure capturing an enclosing binding, by reference.
30+ let base: i32 = 100;
31+ let shift = |x: i32| x + base;
32+ println!("{}", apply(shift, 5));
33+
34+ // `.map` over a Result: the closure's parameter type comes from the
35+ // receiver, and the error branch is carried through untouched.
36+ println!("{:?}", halve(8).map(|v| v * 10));
37+ println!("{:?}", halve(7).map(|v| v * 10));
38+
39+ // `&str` is a view of someone else's bytes, not a copy.
40+ let bytes: Vec<u8> = vec![104, 105];
41+ println!("{} {}", as_str(&bytes), as_str(&bytes).len());
42+
43+ let n: i32 = unsafe { twice(21) };
44+ println!("{}", n);
45+}
new file mode 100644
@@ -0,0 +1,45 @@
1+// `unsafe` is a permission marker, not a semantic change: it does not alter
2+// what the enclosed operations mean, so the block is transparent and each
3+// operation inside still goes through the ordinary lowering.
4+//
5+// Nim's closures capture by reference, as Rust's non-`move` closures do.
6+
7+fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 {
8+ f(x)
9+}
10+
11+fn twice(x: i32) -> i32 {
12+ x * 2
13+}
14+
15+fn halve(n: i32) -> Result<i32, i32> {
16+ if n % 2 == 0 { Ok(n / 2) } else { Err(n) }
17+}
18+
19+fn as_str(b: &[u8]) -> &str {
20+ unsafe { core::str::from_utf8_unchecked(b) }
21+}
22+
23+fn main() {
24+ let add_one = |x: i32| x + 1;
25+ println!("{}", apply(add_one, 10));
26+ println!("{}", apply(twice, 10));
27+ println!("{}", apply(|x: i32| x * x, 7));
28+
29+ // A closure capturing an enclosing binding, by reference.
30+ let base: i32 = 100;
31+ let shift = |x: i32| x + base;
32+ println!("{}", apply(shift, 5));
33+
34+ // `.map` over a Result: the closure's parameter type comes from the
35+ // receiver, and the error branch is carried through untouched.
36+ println!("{:?}", halve(8).map(|v| v * 10));
37+ println!("{:?}", halve(7).map(|v| v * 10));
38+
39+ // `&str` is a view of someone else's bytes, not a copy.
40+ let bytes: Vec<u8> = vec![104, 105];
41+ println!("{} {}", as_str(&bytes), as_str(&bytes).len());
42+
43+ let n: i32 = unsafe { twice(21) };
44+ println!("{}", n);
45+}
deleted tests/cases/904-reject-closure.rs +0 -5
deleted file mode 100644
@@ -1,5 +0,0 @@
1-//@ reject: unsupported expression in value position: closure
2-fn main() {
3- let f = |x: i32| x + 1;
4- println!("{}", f(1));
5-}
deleted file mode 100644
@@ -1,5 +0,0 @@
1-//@ reject: unsupported expression in value position: closure
2-fn main() {
3- let f = |x: i32| x + 1;
4- println!("{}", f(1));
5-}
added tests/cases/904-reject-move-closure.rs +9 -0
new file mode 100644
@@ -0,0 +1,9 @@
1+//@ reject: captures by value
2+// Nim's closures capture by reference, as Rust's non-`move` closures do. A
3+// `move` closure is a different thing, so it is not lowered to the same
4+// construct.
5+fn main() {
6+ let v: Vec<i32> = vec![1, 2, 3];
7+ let f = move || v.len();
8+ println!("{}", f());
9+}
new file mode 100644
@@ -0,0 +1,9 @@
1+//@ reject: captures by value
2+// Nim's closures capture by reference, as Rust's non-`move` closures do. A
3+// `move` closure is a different thing, so it is not lowered to the same
4+// construct.
5+fn main() {
6+ let v: Vec<i32> = vec![1, 2, 3];
7+ let f = move || v.len();
8+ println!("{}", f());
9+}
modified tests/multifile.rs +36 -11
@@ -34,26 +34,51 @@ fn run(dir: &Path, args: &[&str]) -> (bool, String, String) {
3434 #[test]
3535 fn files_are_flattened_into_one_module() {
3636 let d = work("multifile");
37+ let root = d.join("root.rs");
3738 let a = d.join("a.rs");
38- let b = d.join("b.rs");
3939 fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap();
4040 fs::write(
41- &b,
42- "fn main() { println!(\"{}\", double(21)); }\n",
41+ &root,
42+ "mod a;\nuse crate::a::double;\nfn main() { println!(\"{}\", double(21)); }\n",
4343 )
4444 .unwrap();
4545
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()]);
46+ // The first input is the crate root; `a.rs` becomes module `a`, and its
47+ // items are emitted with that prefix so two modules may define the same
48+ // name.
49+ let (ok, out, err) = run(&d, &[root.to_str().unwrap(), a.to_str().unwrap()]);
4950 assert!(ok, "multi-file transpile failed: {err}");
50- assert!(out.contains("proc double"), "missing `double`:\n{out}");
51+ assert!(out.contains("proc a_double"), "missing `a_double`:\n{out}");
5152 assert!(out.contains("proc main"), "missing `main`:\n{out}");
53+ assert!(out.contains("a_double(21"), "call not qualified:\n{out}");
5254
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}");
55+ // The root alone must fail rather than emit a call to something undefined.
56+ let (ok, _, err) = run(&d, &[root.to_str().unwrap()]);
57+ assert!(!ok, "the root alone should not transpile");
58+ assert!(err.contains("mod a;"), "unexpected: {err}");
59+}
60+
61+#[test]
62+fn two_modules_may_define_the_same_name() {
63+ let d = work("modcollide");
64+ let root = d.join("root.rs");
65+ let x = d.join("x.rs");
66+ let y = d.join("y.rs");
67+ fs::write(&x, "pub fn go() -> i32 { 1 }\n").unwrap();
68+ fs::write(&y, "pub fn go() -> i32 { 2 }\n").unwrap();
69+ fs::write(
70+ &root,
71+ "mod x;\nmod y;\nfn main() { println!(\"{} {}\", x::go(), y::go()); }\n",
72+ )
73+ .unwrap();
74+
75+ let (ok, out, err) = run(
76+ &d,
77+ &[root.to_str().unwrap(), x.to_str().unwrap(), y.to_str().unwrap()],
78+ );
79+ assert!(ok, "{err}");
80+ assert!(out.contains("proc x_go") && out.contains("proc y_go"), "{out}");
81+ assert!(out.contains("x_go()") && out.contains("y_go()"), "{out}");
5782 }
5883
5984 #[test]
@@ -34,26 +34,51 @@ fn run(dir: &Path, args: &[&str]) -> (bool, String, String) {
34 #[test]34 #[test]
35 fn files_are_flattened_into_one_module() {35 fn files_are_flattened_into_one_module() {
36 let d = work("multifile");36 let d = work("multifile");
37+ let root = d.join("root.rs");
37 let a = d.join("a.rs");38 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();39 fs::write(&a, "pub fn double(x: i32) -> i32 { x * 2 }\n").unwrap();
40 fs::write(40 fs::write(
41- &b,41+ &root,
42- "fn main() { println!(\"{}\", double(21)); }\n",42+ "mod a;\nuse crate::a::double;\nfn main() { println!(\"{}\", double(21)); }\n",
43 )43 )
44 .unwrap();44 .unwrap();
45 45
46- // `double` is defined in a.rs and called from b.rs: it resolves only46+ // The first input is the crate root; `a.rs` becomes module `a`, and its
47- // because both were passed in.47+ // items are emitted with that prefix so two modules may define the same
48- let (ok, out, err) = run(&d, &[a.to_str().unwrap(), b.to_str().unwrap()]);48+ // name.
49+ let (ok, out, err) = run(&d, &[root.to_str().unwrap(), a.to_str().unwrap()]);
49 assert!(ok, "multi-file transpile failed: {err}");50 assert!(ok, "multi-file transpile failed: {err}");
50- assert!(out.contains("proc double"), "missing `double`:\n{out}");51+ assert!(out.contains("proc a_double"), "missing `a_double`:\n{out}");
51 assert!(out.contains("proc main"), "missing `main`:\n{out}");52 assert!(out.contains("proc main"), "missing `main`:\n{out}");
53+ assert!(out.contains("a_double(21"), "call not qualified:\n{out}");
52 54
53- // b.rs alone must fail rather than emit a call to something undefined.55+ // The root alone must fail rather than emit a call to something undefined.
54- let (ok, _, err) = run(&d, &[b.to_str().unwrap()]);56+ let (ok, _, err) = run(&d, &[root.to_str().unwrap()]);
55- assert!(!ok, "b.rs alone should not transpile");57+ assert!(!ok, "the root alone should not transpile");
56- assert!(err.contains("unknown function `double`"), "unexpected: {err}");58+ assert!(err.contains("mod a;"), "unexpected: {err}");
59+}
60+
61+#[test]
62+fn two_modules_may_define_the_same_name() {
63+ let d = work("modcollide");
64+ let root = d.join("root.rs");
65+ let x = d.join("x.rs");
66+ let y = d.join("y.rs");
67+ fs::write(&x, "pub fn go() -> i32 { 1 }\n").unwrap();
68+ fs::write(&y, "pub fn go() -> i32 { 2 }\n").unwrap();
69+ fs::write(
70+ &root,
71+ "mod x;\nmod y;\nfn main() { println!(\"{} {}\", x::go(), y::go()); }\n",
72+ )
73+ .unwrap();
74+
75+ let (ok, out, err) = run(
76+ &d,
77+ &[root.to_str().unwrap(), x.to_str().unwrap(), y.to_str().unwrap()],
78+ );
79+ assert!(ok, "{err}");
80+ assert!(out.contains("proc x_go") && out.contains("proc y_go"), "{out}");
81+ assert!(out.contains("x_go()") && out.contains("y_go()"), "{out}");
57 }82 }
58 83
59 #[test]84 #[test]