Emit Typed Jerboa source-span comments in Rust output
ober
0d267a6126e1604164ae81816126578c6f64592d
--- a/docs/jerboa-to-rust.md +++ b/docs/jerboa-to-rust.md @@ -79,26 +79,47 @@ Landed: wrong-type handles, expose explicit handle drops, and use a Chez guardian reaper as a cleanup safety net. -Still open: +Done in subsequent phases: -- (Done) Typed core IR adoption. The IR record types live in - `(jerboa typed core)`, the checker elaborates def bodies into IR via +- Typed core IR adoption. The IR record types live in `(jerboa typed core)`, + the checker elaborates def bodies into IR via `check-and-elaborate-typed-module`, and the Rust emitter (`typed-module->rust-string`, `typed-modules->rust-crate-string`, and their - library-form siblings) now consumes the IR through `*rust-ir-env*`. Future - rust.ss work can prune the surface fall-back paths once the wrapper - generator follows. -- Import resolution between typed modules. -- Structured Rust-to-Scheme error returns instead of conservative panic - defaults. -- Direct Scheme conversions for Option/Result instead of opaque handles. -- Resource `#:close` lowering and Rust `Drop` integration. -- Borrow-aware lowering. Current generated Rust clones liberally. -- Generated Rust source comments tied to typed source spans. Checker - diagnostics now carry expression-level `(path,line,column)` triples through - `typed-check-error-source`; the Rust emitter does not yet thread that into - generated `// source: ...` comments. -- Direct `rustc`/static integration polish and eventual LLVM parity tests. + library-form siblings) consumes the IR through `*rust-ir-env*`. +- Import resolution between typed modules. The parser accepts + `(import (module name) ...)` and the checker walks topologically ordered + module lists; `typed-modules->rust-crate-string` / + `typed-library-forms->rust-crate-string` emit a single multi-module crate. +- Structured Rust-to-Scheme error returns. Every safe export wraps its body in + `catch_unwind`; panic payloads route through `jt_capture_panic` into a + thread-local that wrappers drain via `%typed-rust-check-panic!`, surfacing + Rust panics as Scheme errors whose irritants carry the panic message. +- Direct Scheme conversions for Option and Result instead of opaque handles. + `(Option <Scalar>)`, `(Option String)`, `(Option Bytes)`, and `(Result T E)` + across every scalar/buffer combination now cross the boundary as tagged + Scheme values (`#f` / `(cons 'some V)` / `(cons 'ok V)` / `(cons 'err E)`). +- Resource `#:close` lowering and Rust `Drop` integration. Resource + declarations lower to opaque `pub struct Name { pub _opaque: () }` placeholders + and `(resource Name #:close fn)` additionally emits an `impl Drop` that + delegates to the close hook. +- Borrow-aware lowering for explicit `(Borrow T)` / `(MutBorrow T)` parameters. + Borrow params lower to `&T` / `&mut T` and call sites emit `&(expr)` / + `&mut (expr)` instead of `.clone()`. Owned values are still cloned at + ordinary `T` parameter positions (conservative). +- Generated Rust source comments tied to typed source spans. Records, variants, + resources, and defs emit `// source: path:line:column` comments above each + declaration when the parsed datum carries a source-location. + +Still open: + +- Per-module file layout (`src/modules/<module>.rs`). The current emitter + inlines all modules into a single `lib.rs`. +- Direct `rustc` / static binary integration polish and eventual LLVM parity + tests. Today the build goes through `cargo build` only. +- Expression-level source comments inside function bodies (only the + declaration headers carry source spans today). +- Cross-module `use` declarations and per-module Rust files for richer + workspaces. ## Output Layout @@ -517,15 +538,22 @@ unsafe { ## Source Locations -Generated Rust should preserve source locations in comments: +Generated Rust preserves source locations in comments above each declaration +when the typed parser was given an annotated datum (typically from +`jerboa-read-file`): ```rust // source: src/jerboa-emacs/typed/rope.tss:42:3 pub fn rope_insert(...) -> Rope { ... } ``` -When Rust compilation fails, the typed compiler should try to map errors back -to Typed Jerboa source. In the MVP, readable generated Rust is enough. +Current landing: records, variants, resources, and defs each emit a +`// source: path:line:column` comment above their generated declaration. The +checker already threads spans through `typed-check-error-source`, so future +work can wire expression-level spans into intra-body comments as well as +richer source maps. When Rust compilation fails, a developer can map the +reported file to the typed source through these comments. Mapping cargo +errors directly back to typed source line and column remains future work. ## Build Integration --- a/lib/jerboa/typed/rust.ss +++ b/lib/jerboa/typed/rust.ss @@ -18,6 +18,8 @@ (import (chezscheme) ; jerboa-security: suppress direct-chezscheme-import-user-code -- trusted typed compiler Rust emitter (only (jerboa core) def) + (only (jerboa reader) source-location? source-location-path + source-location-line source-location-column) (jerboa typed parser) (jerboa typed checker) (jerboa typed core)) @@ -69,6 +71,21 @@ (display text port) (newline port)) + (def (source-location->rust-comment source) + (and (source-location? source) + (string-append + "// source: " + (or (source-location-path source) "<unknown>") + ":" + (number->string (source-location-line source)) + ":" + (number->string (source-location-column source))))) + + (def (write-source-comment port level source) + (let ([text (source-location->rust-comment source)]) + (when text + (write-line port level text)))) + (def rust-keywords '("as" "break" "const" "continue" "crate" "else" "enum" "extern" "false" "fn" "for" "if" "impl" "in" "let" "loop" "match" "mod" @@ -313,6 +330,7 @@ (thunk))) (def (emit-record record port) + (write-source-comment port 0 (typed-record-source record)) (write-line port 0 "#[derive(Clone, Debug, PartialEq)]") (write-line port 0 (string-append "pub struct " (rust-symbol-name (typed-record-name record)) " {")) @@ -354,6 +372,7 @@ (write-line port 1 "},")]))) (def (emit-variant variant port) + (write-source-comment port 0 (typed-variant-source variant)) (write-line port 0 "#[derive(Clone, Debug, PartialEq)]") (write-line port 0 (string-append "pub enum " (rust-symbol-name (typed-variant-name variant)) " {")) @@ -1557,6 +1576,7 @@ (cond [ir-entry (emit-expression (cdr ir-entry))] [else (emit-begin (strip-source-annotations (typed-def-body def)))])]) + (write-source-comment port 0 (typed-def-source def)) (write-line port 0 (string-append "pub fn " @@ -1573,6 +1593,7 @@ (def (emit-resource resource port) (let ([name (rust-symbol-name (typed-resource-name resource))] [close (typed-resource-close resource)]) + (write-source-comment port 0 (typed-resource-source resource)) (write-line port 0 "#[derive(Debug)]") (write-line port 0 (string-append "pub struct " name " {")) (write-line port 1 "pub _opaque: (),") --- a/tests/test-typed-rust.ss +++ b/tests/test-typed-rust.ss @@ -2,6 +2,7 @@ ;;; Tests for (jerboa typed rust) (import (chezscheme) + (jerboa reader) (jerboa typed rust)) (define pass 0) @@ -551,6 +552,29 @@ (substring? import-crate-rust "(p).y")) #t) +(define annotated-form + (car (jerboa-read-file "tests/fixtures/typed/rust-basic.ss"))) + +(define annotated-rust + (typed-library-form->rust-string annotated-form)) + +(test "rust emits source-span comments above defs" + (and (substring? annotated-rust "// source: tests/fixtures/typed/rust-basic.ss:") + (substring? annotated-rust ":15:") + (substring? annotated-rust "pub fn zero() -> u64")) + #t) + +(test "rust emits source-span comments above records and variants" + (and (substring? annotated-rust "// source: tests/fixtures/typed/rust-basic.ss:8:") + (substring? annotated-rust "pub struct Box {") + (substring? annotated-rust "// source: tests/fixtures/typed/rust-basic.ss:11:") + (substring? annotated-rust "pub enum Token {")) + #t) + +(test "rust omits source-span comment when datum has no source" + (not (substring? calc-rust "// source:")) + #t) + (printf "~%Typed Rust emitter: ~a passed, ~a failed~%" pass fail) (when (> fail 0) (exit 1))