v2026.5.1 – :script Tier 2 Lands
v2026.5.1 – :script Tier 2 Lands
Section titled “v2026.5.1 – :script Tier 2 Lands”Release date: 2026-05-01
Profiles affected: :script (Tier 2 functionally complete), :core (escalate sugar applies)
Status: Standard release (6-month support)
The April release (v2026.4.8) introduced the :script profile as a syntactic surface. This release lands Tier 2: the desugar pipeline that makes a .jans file mechanically equivalent to a hand-written :core program. Top-level statements lift, ten stdlib modules auto-import, the escalate sugar drops do/end for single-expression bodies, and janus validate --promotable proves the Script Law on demand.
What’s new at the surface
Section titled “What’s new at the surface”A .jans file from this release forward looks the way you’d hope:
let name = "World"println("Hello, {name}!")$ janus run hello.jansHello, World!No func main. No use std.io. The compiler does that work for you, additively, before sema runs.
You can prove the Script Law mechanically:
$ janus validate --promotable hello.jansvalidate --promotable: hello.jans is promotable to :core$ echo $?0If the file fails the check, you get a pointed diagnostic and exit 1.
The four landings
Section titled “The four landings”1. Top-level desugar (SPEC-045 §3.1)
Section titled “1. Top-level desugar (SPEC-045 §3.1)”Pass 1 of the :script desugar walks the parsed AST, partitions top-level decls (functions, structs, traits, etc.) from top-level statements, and lifts the statements into a synthesized func main. The synthesized function is tagged SugarKind.script_main internally so diagnostics name it accurately and downstream consumers can distinguish user-written from synthesized.
Replace-in-place strategy: the source_file root keeps its original index in the AST; only its child_lo/child_hi are updated to point at a new edge range that includes the synthesized main. Anyone who scans unit.nodes by kind discovers everything correctly.
2. Ten fixed auto-imports (SPEC-045 §3.4)
Section titled “2. Ten fixed auto-imports (SPEC-045 §3.4)”Pass 1.5 prepends the canonical SPEC-045 §3.4 import set to every .jans source file:
std.io std.fs std.processstd.text.rx std.text.peg std.text.stream std.text.searchstd.collections std.fmt std.envThe list is fixed in the spec. It cannot be extended per-project, per-user, or per-file. Future additions require a SPEC amendment. If you import one of these by hand the compiler emits E3113_AUTO_IMPORT_SHADOWED as a warning so promotion to :core produces no surprises – resolve by removing the explicit use or by aliasing it (use std.io as stdio).
3. Single-expression escalate (SPEC-044 §3.2.1)
Section titled “3. Single-expression escalate (SPEC-044 §3.2.1)”When an escalate body is exactly one expression, do/end is now optional:
// Block form (multi-statement bodies, unchanged)let r = escalate :compute do let scratch = allocate_buffer(1024) matmul(a, b, scratch)end
// Single-expression sugarlet r = escalate :compute matmul(a, b)Both forms produce identical AST shapes (the sugar wraps the expression in expr_stmt to match the block form). The disambiguation is mechanical because do is reserved and cannot head an expression. grep -nE 'escalate :[a-z]+' over your source still finds every escalation site in either form. The Anti-Gadget Law (SPEC-044 §9.5) holds.
This sugar is universal to SPEC-044 – any profile that can host escalate can use it. It’s especially load-bearing for :script because the One Escalate Law (SPEC-045 §0) judges the profile on how light a single capability bridge feels on the page.
Bug fix shipped alongside: the parser’s
convertTokenTypewas mapping the.escalate_keyword token to the AST node kind.escalate_stmt(which happens to also exist in the TokenKind enum). As a result everyparser.match(.escalate_)check returned false andparseEscalateBlockwas dead code – the escalate keyword has never actually been parsed by the compiler. Caught while implementing §3.2.1; both forms now work for the first time.
4. janus validate --promotable (SPEC-045 §7.6 / §7.7)
Section titled “4. janus validate --promotable (SPEC-045 §7.6 / §7.7)”The Script Law’s falsifiability check shipped as a CLI command:
janus validate --promotable file.jansRuns parse → top-level desugar → auto-import injection → sema, stops there, reports the verdict. Faster than a full compile (no QTJIR, no LLVM, no linking). If the file passes, promotion to :core is guaranteed to round-trip without loss.
Use it in CI before shipping any .jans that you intend to promote later.
Diagnostics: 8 new E31xx codes
Section titled “Diagnostics: 8 new E31xx codes”SPEC-045 §10 has been reconciled with the implementation. Six new error codes plus two new warnings, all in numeric order:
| Code | Severity | When it fires |
|---|---|---|
E3100_EMPTY_SCRIPT | error | A .jans file is empty (no decls, no statements). |
E3102_USER_MAIN_WITH_TOP_LEVEL_STMTS | error | The file declares its own func main AND has top-level statements. Pick one. |
E3104_RETURN_AT_MODULE_SCOPE | error | A return appeared at module scope. (Currently dormant – the parser does not yet produce top-level return_stmt.) |
E3106_RESERVED_SCRIPT_MAIN_ATTRIBUTE | error | User code applied #[script_main]. The attribute is reserved for synthesized functions. (Defensive – cannot fire today.) |
E3108_AUTO_IMPORT_RESOLUTION_FAILED | error | A SPEC-045 §3.4 auto-import (e.g. std.text.rx) could not be resolved. Replaces the generic E4100 for script_auto_import-tagged use_jan nodes. |
E3112_SERVICE_CONSTRUCT_AT_SCRIPT_TOP_LEVEL | error | A :service-only construct (spawn, await, select, nursery, receive) appears at :script top level. Promote to :service, or wrap the single call in escalate :service .... Detected during pass 2; the walker stops at closure boundaries. |
E3113_AUTO_IMPORT_SHADOWED | warning | A user use declaration shadows an auto-import without explicit as alias. Compile succeeds. |
E3114_SYNTHESIZED_MAIN_ENDS_IN_PANIC | warning | The synthesized main’s last statement is a literal panic(...). Fires only on a literal panic callee; divergent while true loops are correctly suppressed. The warning exists because a script that ends in a panic almost always means the author meant to return a status code. |
Migration: none
Section titled “Migration: none”.jan files are unaffected. .jans files that compiled before still compile. The new shadowing warning (E3113) only fires on duplicate user use declarations against the auto-import list – fix it once, ship.
What’s NOT in this release (honest about the runway)
Section titled “What’s NOT in this release (honest about the runway)”- Comment preservation through
janus desugar– the lowered AST has no comment nodes; the printer cannot emit what is not there. Scheduled with the Phase 1 sugar sprint backlog. - Some auto-import targets (
std.text.{rx,stream,search}) resolve only as the underlying modules ship. The compiler’sauto_import_known_brokenfilter preventsE3108from firing on the modules whose lowering defects are tracked in SI-1/SI-2/SI-3. They auto-emit when those issues close, with no spec or doc change required. - A REPL for
:script(Tier 4 ambition).janus run+ shebang scripts cover the Tier 1 / 3 dev loops. A live REPL is a separate sprint.
Updated 2026-05-03. The original publication of this post (2026-05-01) said pass-2 implicit-
tryinjection andjanus desugartext output were not yet shipped, and thatE3112/E3114were reserved namespace slots awaiting pass 2. All four items landed onunstablelater the same day. The diagnostics table and this section are amended to reflect ship-state. The:scriptTier 2 deep dive carries the canonical reference; if it disagrees with this release post, the deep dive wins.
Read more
Section titled “Read more”- Tutorial:
:scriptTier 2 deep dive – how the desugar actually works, what to do when a diagnostic fires, when to promote. - Overview:
:scriptprofile overview – the convenience features, the$-family, the AOT cache. - Specs: SPEC-045
:scriptprofile, SPEC-044 §3.2.1 single-expression escalate.