Skip to main content

bynk_emit/
lib.rs

1//! Bynk's TypeScript emission, plus the per-unit build sequencing that drives
2//! it — the layer above `bynk-project` (discovery, the dependency graph) and
3//! `bynk-check` (all semantic checking, R3.5).
4//!
5//! `project` owns `compile_project`/`run_checks`: the two-pass sequence over
6//! a project's units — discover and parse (`bynk-project`), then resolve,
7//! type-check (`bynk-check`) and emit each unit with full visibility of what
8//! it `uses`/`consumes`. `emitter` lowers a checked program to TypeScript.
9//! Input: a project tree (or an in-memory overlay). Output: TypeScript files
10//! plus diagnostics — this crate originates none of its own (P5.5,
11//! `design/tracks/semantics-in-the-checker.md` §3.5, R10.1).
12//!
13//! Extracted from `bynkc` as slice 4 of the crate-decomposition track over
14//! `bynk-syntax` + `bynk-check`. Behaviour is unchanged; `bynkc` depends on this
15//! crate and re-exports its modules so its public API (`compile_project`,
16//! `ProjectOutput`, …) and the binary are untouched.
17
18pub mod emitter;
19pub mod project;
20
21/// P6.1 (design/tracks/the-ir.md §6, #1141): the IR's core node types
22/// (`ir`) and the `CheckedProgram → Ir` lowering skeleton (`ir::lower`).
23/// `pub(crate)`, not `pub` — no consumer outside this crate yet, and no
24/// consumer inside it either: this module is additive scaffolding with no
25/// call site anywhere in the existing emission path (`emitter`/`project`)
26/// until a later slice (P6.2 onward) wires one in. `#[allow(dead_code)]`
27/// (Decision D) — its own `#[cfg(test)]` module is this slice's only
28/// caller (Decision E), and a plain, non-test library build compiles
29/// without it; remove this `allow` the moment a real caller lands.
30#[allow(dead_code)]
31pub(crate) mod ir;
32
33#[cfg(test)]
34pub(crate) mod testkit;
35
36use bynk_check::{checker, resolver};
37use bynk_syntax::{CompileError, lexer, parser};
38
39/// A single-file compile that also returns the non-failing warnings produced on
40/// success — what a CLI prints (v0.89, ADR 0117). [`compile`] is the
41/// warning-discarding convenience over this.
42///
43/// Lives in `bynk-emit` (slice 7 precedent, alongside [`NODE_MAJOR_FLOOR`]) so
44/// both `bynkc` and the `bynk` driver can compile a self-contained single-file
45/// commons in-process without depending on each other; `bynkc` re-exports it so
46/// `bynkc::compile_with_warnings` and `bynkc::Compiled` are unchanged.
47pub struct Compiled {
48    pub ts: String,
49    pub warnings: Vec<CompileError>,
50}
51
52/// Compile a single Bynk source string to a TypeScript string.
53///
54/// Parses the input as a self-contained, single-file commons with no `uses`
55/// against other commons. Use [`project::compile_project`] for multi-file
56/// projects or for any source that declares `uses`. `filename` is used only for
57/// diagnostic rendering.
58pub fn compile(source: &str, filename: &str) -> Result<String, Vec<CompileError>> {
59    compile_with_warnings(source, filename).map(|c| c.ts)
60}
61
62/// The warning-preserving single-file compile behind [`compile`]. See [`Compiled`].
63pub fn compile_with_warnings(source: &str, _filename: &str) -> Result<Compiled, Vec<CompileError>> {
64    let tokens = lexer::tokenize(source).map_err(|e| vec![e])?;
65    // ADR 0117: parse-time warnings (orphan doc blocks) ride alongside the
66    // AST — they surface with the build's warnings instead of failing it.
67    let (commons, mut warnings) = parser::parse_with_warnings(&tokens, source)?;
68    // v0.20a: function types are confined to non-boundary positions — the same
69    // rule the project path applies.
70    let mut boundary_errors = Vec::new();
71    let boundary_types = bynk_check::project_model::collect_type_decls(commons.items.iter());
72    bynk_check::project_model::check_function_type_boundary_items(
73        &commons.items,
74        &boundary_types,
75        &mut boundary_errors,
76    );
77    if !boundary_errors.is_empty() {
78        return Err(boundary_errors);
79    }
80    let resolved = resolver::resolve(commons)?;
81    let typed = checker::check(resolved)?;
82    warnings.extend(typed.warnings.clone());
83    // T3.7 (R3.10): `check` already gated on error-severity diagnostics, so
84    // `typed.warnings` — the only diagnostics left riding along with it — can
85    // never contain one; `certify` re-asserts that structurally rather than
86    // trusting the caller not to skip it.
87    let program = checker::certify(typed, warnings.clone()).unwrap_or_else(|_| {
88        panic!("bynk internal error: check() already gated on error-severity diagnostics")
89    });
90    Ok(Compiled {
91        ts: emitter::emit(&program),
92        warnings,
93    })
94}
95
96/// Minimum supported Node.js **major** version for the `node` platform binding
97/// and for running Bynk's emitted TypeScript.
98///
99/// Single source of truth for the Node floor: the emitted code targets it, the
100/// `bynk` driver's `doctor` command compares a detected `node` against it, and
101/// `bynkc`'s CLI re-exports it rather than restating the number. Lives in
102/// `bynk-emit` (which emits the TS that runs on Node) so both binaries share one
103/// definition (slice 7; was a `bynkc` const before the driver dropped that dep).
104pub const NODE_MAJOR_FLOOR: u32 = 18;
105
106// `write_output`/`write_compiled_file` moved to `bynk-driver` (#1047, R2.3/
107// T0.7 residue): every caller was already at driver level, so this crate
108// never needed direct filesystem access for it — the pure move closes it
109// out of this crate's `fs_below_driver` count. See `bynk-driver::output`.