Skip to content

Jaclang Release Notes#

This document provides a summary of new features, improvements, and bug fixes in each version of Jaclang. For details on changes that might require updates to your existing code, please refer to the Breaking Changes page.

jaclang 0.16.7 (Latest Release)#

Breaking Changes#

  • Lambda parameters now require a known type (#6804): A lambda whose parameter type cannot be inferred from context, such as a bare f = lambda x: -x;, is now a type error (E1119) instead of silently typing the parameter as unknown. Annotate the parameter (lambda x: int : x + 1; the return type is still inferred) or use the lambda where its type is fixed, e.g. as a typed callback (sorted(xs, key=lambda x: ...)) or bound to a Callable-typed target.

jaclang 0.16.6#

New Features#

  • Optimistic concurrency for concurrent check-then-create (#6266): A walker or function endpoint that does "look it up, create it if missing" against the same node no longer yields duplicate children under concurrency. Each node carries a version; an edge-list write to a node the request read is applied with a compare-and-swap, and the request boundary aborts and replays the loser so find-or-create converges on the winner. A blind append (no preceding read) still merges lock-free. The read is recorded independently of how the traversal resolves -- the topology-index/backend fast path and edge-reference reads ([edge -->]) take the dependency just as the linear edge-walk does -- and the read snapshot advances after each commit, so a request that commits more than once never conflicts with itself. A new on_commit(...) ambient builtin defers external side effects to run exactly once after a successful commit (and is discarded on abort/replay). The policy is configurable in jac.toml under [serve]: on_conflict (retry/fail), conflict_max_attempts, and conflict_backoff_ms. The losing unit of work is atomic: SqliteMemory.apply() rolls the whole transaction back on a conflict, so a lost race leaves the store unchanged with no orphaned child, and diagnostics accrued by an aborted attempt are cleared so a converged replay's 200 response carries no stale permission warnings. jac db fsck now also sweeps half-linked edges (cited by only one of their two endpoints) and the nodes they strand, reclaiming any residual orphan. See Persistence -> Concurrent writes.
  • New client project kind: Added a client project kind for browser-only apps with no backend. jac run serves it as a static page (no API server), and jac build/jac start (via jac-client) produce a portable, self-contained dist.

Bug Fixes#

  • Fix: glob declared in an impl annex now resolves on the native (.na.jac) backend (#6754): A global variable declared in an impl-annex file (*.na.impl.jac), rather than the head .na.jac file, is now registered in the native module's globals and resolved by references in annexed ability bodies, matching the @sv and ecmascript backends. Previously such a reference failed to lower with error[E5090]: Native pathway does not yet support expression 'Name' (or 'BinaryExpr'), an opaque diagnostic that blamed the reference expression rather than the unresolved global, and produced a silently-degraded build when the global only fed a loop bound. The root cause was annex weaving being re-derived per backend at codegen time: @sv/ecmascript wove the annex bodies in (_merge_module_bodies), while native's statement scan (iter_context_stmts) walked only the head module's body. Annex handling is now centralized in the front end: a single ModuleCodegenPass.iter_woven_segments is the one source of truth for which statements an annexed module contributes (all three backends derive their woven view from it), a native (.na.jac) head's impl/test annexes are coerced to NATIVE code-context once attached -- keyed off the head, so both the canonical bare foo.impl.jac and the variant-specific foo.na.impl.jac forms are covered -- and type inference runs over annex modules so native codegen finds the types it needs (covering both the same-module and the imported-module cases).
  • Fix: skip in an impl-annex walker event ability no longer breaks native jac test (#6794): A walker event ability that uses skip and is split decl/impl -- the can flood with Area entry; declaration in the head .na.jac and its body in an impl Walker.flood with Area entry {...} annex -- compiled and ran correctly on its own, but a native test (or any module) importing it failed with RuntimeError: native engine unavailable for every test. The native capability authority accepts skip inside a walker with X entry event ability (where it lowers as a no-value return) and rejects it in a value-returning function; it found the enclosing ability by walking up to an Ability node. When the body lives in an annex the skip is enclosed by an ImplDef (carrying the EventSignature in its spec), not an inline Ability, so once iter_context_stmts began scanning annex bodies (#6754) the authority reached the annex skip, saw no enclosing Ability, and falsely flagged it as unsupported. That dropped the imported module's LLVM IR (E5090), so linking the importer failed (E5024) and no JIT engine was produced. The capability check now recognizes the decl/impl-split form: a skip whose nearest enclosing ImplDef has an EventSignature is accepted exactly like an inline event-ability skip, while a skip in a value-returning function (inline or annex) is still rejected.
  • Fix: sv import of a node/obj type no longer crashes the client bundle (#6795): A .cl.jac file that sv imports a function together with the node/obj type it returns used to emit two declarations with the same name in the compiled JS, an async function <Type>() RPC stub and the wire class <Type>, which crashed the bundler with The symbol "<Type>" has already been declared even though jac check passed. The ecmascript codegen (EsastGenPass._generate_sv_import_stubs) was generating an HTTP RPC stub for every imported item regardless of kind; it now skips any item whose resolved symbol is a type (node/obj/edge/enum) or a walker, since a node/obj/edge already lowers to a wire class, an enum to a frozen object, and a walker is reached through __jacSpawn. Only def:pub abilities get an RPC stub. This mirrors the server-side (sv -> sv) Python-path fix from #6710.

jaclang 0.16.5#

New Features#

  • Feature: jac eject compiles a project into a runnable FastAPI + JavaScript app: jac eject now writes a project-internal ejected/ folder split into a FastAPI backend/ (each .sv.jac/.jac walker compiled to Python and exposed as POST /walker/<Name>, functions as POST /function/<name>) and a frontend/ (the .cl.jac UI compiled to JavaScript with a Vite setup). The backend runs the walkers on the installed jaclang runtime and uses jaclang-native auth: /user/register + /user/login via UserManager, with :pub walkers open and the rest token-gated (each user gets their own persistent root graph). One uvicorn main:app serves the API and the built frontend. Re-running regenerates the folder without --force (it carries a .jac-ejected marker), and the output directory is overridable via [eject].output in jac.toml.
  • jac install now uses uv as its pip backend when available: Dependency installs run through uv pip for significantly faster resolution and downloads. A new --no-uv flag on jac install (or JAC_NO_UV=1 env var for all install paths) opts back to plain pip.
  • jac install <pkg> installs packages into the activated environment (#6703): jac install now accepts package arguments (e.g. jac install numpy pandas), routing them directly to pip against the currently activated Python environment - no jac.toml modification. Existing no-arg behavior (jac install from a project directory) is unchanged.
  • Feature: User-defined iterators in native code (#6403): Any obj that implements __iter__/__next__ can now be iterated in the native (na {} → LLVM) backend. It works in for loops, via first-class iter()/next(), through the map() adapter, and across function boundaries as Iterator[T], with StopIteration ending iteration as in Python.

Bug Fixes#

  • Fix: Native and clib type annotations now reject unresolved type names instead of silently defaulting to i64 (issue #6676): C-library import declarations and native-context annotations (.na.jac / na {}) whose parameter, return, or field type names the compiler cannot resolve are now a hard E2018 error at jac check (not just a W2001 warning) and are reported honestly at nacompile via E2018/E5090, instead of being silently widened to a signed i64 at the FFI boundary with a wrong ABI and no diagnostic. A clib declaration with no return arrow (def f(x: T);) now correctly lowers its return to void (matching an explicit -> None) instead of silently declaring the void C function as returning a signed i64.
  • Fix: Unresolved object-field type names are now named in the diagnostic, not rendered as <Unknown>: an object field whose annotation names an unresolved type (a typedef'd C handle, an unmodeled width like uint32_t) reaches native codegen through a non-expression node, so the E2018 used to read the useless Undefined name '<Unknown>' -- str() of the checker's UnknownType -- losing the name the user wrote. The label now recovers it from the field's type tag, so the error points at the actual type name. (The front-end E2018 covers clib/native signature positions but not object field declarations, so the codegen pass is currently the sole catcher for bad field types.)
  • Fix: Clear diagnostic for a re-wrapped expression in a JSX slot: Re-wrapping a single expression in extra {...} inside a JSX slot body ({"text"}, {n + 1}, {<li/>}) used to fall through to a misleading E0002 Missing ';' that pointed at the following line, while the control-flow form ({if ...}) got a clear error at the {. The redundant expression wrap is now reported as a dedicated error (E2028) pointing at the { itself: the expression sibling of the control-flow E2023, co-located in the parser so the whole redundant-slot-wrap rule lives in one place. The parser recovers by dropping the redundant braces (exactly as the control-flow form does), so the rest of the file still parses without an error cascade. Genuine collection literals ({1, 2}, {a: 1}) carry a ,/: and are left untouched (#6733).
  • Fix: Bare expression statements in JSX slots no longer silently render nothing: Inside a JSX {...} statement slot, a bare expression statement (a string, f-string, identifier, or call) was evaluated and discarded instead of being added to the slot's children, so conditional content written that way compiled but rendered nothing. Every value-producing expression statement directly inside a slot body is now accumulated as a child, consistent with the {expr} slot and bare JSX elements. The misleading W0060 "docstring" warning that previously fired on such strings is suppressed inside slot bodies (it still fires for genuine misplaced docstrings elsewhere).
  • Fix: by postinit fields no longer show a false warning: A field declared with has x: T by postinit was reporting a spurious W1051 ("Expression type could not be resolved") warning even though the code was correct. The warning is no longer raised.
  • Fix: jac run/jac start on a .sv.jac or .cl.jac entry: Running or serving a server (.sv.jac) or client (.cl.jac) file directly by path failed with Cannot find module main.sv / No module named 'main.sv', because proc_file stripped only the trailing .jac from the compound codespace suffix and left main.sv. It now strips the full suffix for all three compound codespaces (.na.jac/.sv.jac/.cl.jac) so the entry resolves to its bare module stem.
  • Fix: Native bytes concatenation and the bytes()/bytearray() constructors: bytes + bytes no longer crashes native code generation, and bytes([...])/bytearray([...]) now return a length-aware value that preserves embedded NUL bytes, matching the interpreter.
  • Fix: complete the native bytes length-aware migration (issue #6749): the native (.na.jac to LLVM) backend made bytes a distinct length-aware struct in #6727/#6745, but seven operations still assumed the old NUL-terminated i8* representation and either crashed, failed to compile, or returned silently wrong results. They are now all length-aware on the struct, matching the sv (CPython) backend including embedded NUL bytes: repeat (b * n), membership (x in b, via memmem so it sees past NULs), iteration (for x in b and iter(b), yielding int byte values), truthiness (if b and bool(b) are emptiness, not a null-pointer test, so empty bytes is correctly falsy), str(b) (the exact CPython b'...' repr with \t/\n/\r/\\/\xNN escapes and CPython quote selection), bytes dict keys ({b: v} and d[b] compare by value/length instead of pointer identity, so distinct literals and keys differing only by an embedded NUL or length resolve correctly), and ordering (</>/<=/>=, lexicographic over unsigned bytes with a length tiebreak).
  • Fix: null-safe bytes comparison against None on the native backend (issue #6751): comparing a bytes | None value to bytes when the value is None no longer crashes or silently drops the result on the native (.na.jac to LLVM) backend. The length-aware bytes comparison helpers dereferenced both operands unconditionally, so a None (a null bytes pointer) caused a segfault for a branchy union and silent empty output for a constant None (the null load was undefined behavior the optimizer deleted). ==/!= now follow CPython identity semantics (equal only when both operands are None, so None == b"x" is False and None == None is True) and only read the bytes contents when both operands are non-null, while ordering (</>/<=/>=) against None raises TypeError to match CPython instead of dereferencing null.
  • Type Checker: Infer unannotated lambda parameter types from the expected callable: A lambda passed to a generic builtin like sorted(rows, key=lambda d: d["n"]) no longer reports a false error[E1054] ("No matching overload"). The lambda parameter's type is now solved from the call context (the same way def-typed callbacks already worked), so the body type-checks, hover shows the inferred type, and the result type is correct. Works for sorted, max, min, filter, zip, and enumerate.

Documentation#

  • Docs: Client skill-guide fixes: Corrected contradictions and non-compiling examples in the client (cl) skill guides that led AI code generation to emit broken Jac (file-based-vs-manual routing rule, subscript hook/server-result access, and verified sorting + number-formatting recipes).

jaclang 0.16.4#

New Features#

  • Feature: Kind-aware jac create: jac create --kind <kind> scaffolds a project for a specific project kind, stamping [project] kind into jac.toml and laying the entry-point in the right codespace so the new project's bare jac run dispatches deterministically (execute / serve / build). The eight core kinds (cli, native-app, native-binary, shared-library, api-service, microservices, pypi-package, npm-package) ship with jaclang; plugin kinds fail fast with an install hint. --kind and --use are mutually exclusive, and jac create --list_jacpacks now lists the available kinds and named variants. The register_project_template hook is now aggregating (was first-result), so every installed plugin contributes its kind templates.
  • Feature: isinstance on an any value in native code: The native (na {} → LLVM) backend now lowers isinstance(x, T) for int, bool, float, str, list, dict, set, and tuples of those (isinstance(x, (int, str))). It works whether x is an any/JacVal (runtime tag check) or a concrete value (folds to a constant), reusing the existing JacVal box scheme. bool is now boxed under its own tag so it stays distinguishable from int, matching CPython's bool ⊂ int (isinstance(True, bool) and isinstance(True, int) are both true, isinstance(5, bool) is false). Assigning an explicit any into a concrete-typed slot (z: int = a) is also allowed in native context now, unboxing at the coercion seam to stay congruent with the sv pathway. This unblocks generic-over-any Mechanism-B stdlib code (json, collections, …) in the native standard-library roadmap (#6404).
  • Feature: Crash-atomic graph writes: Graph mutations now flush as a single unit of work in referential-integrity order, so a crash mid-request can no longer leave dangling references, and a walker that fails no longer persists partial mutations.
  • jac browse actionability, console access, WebGL, and a real viewport: closes the four load-bearing gaps from head-to-head QA vs agent-browser (jaseci-labs/jaseci#6597). Offscreen interactions no longer silently no-op while reporting success - every click/type/fill target (CSS or @ref) is scrolled into view and must be visible, position-stable, inside the viewport, and the top element at its action point per elementFromPoint, else the command hard-errors with the reason. A new console [--clear] action reads buffered console/log/exception output by draining Chrome's per-context replay on attach (no resident listener). Headless launch now enables software WebGL via SwiftShader (canvas/wasm content renders) and defaults to an exact 1280x720 viewport (--viewport WxH to change), calibrated against window-chrome height loss. Also adds wait <ms|selector> and scroll <dir|top|bottom|selector> [px] actions, and Chrome discovery on macOS. (jaseci-labs/jaseci#6635)
  • Native OSP: graph navigation, cross-module archetypes, and edge disconnect (issue #6642): jac nacompile now lowers the remaining Object-Spatial constructs a real multi-module graph app needs. Edge-reference trailers lower as general expressions in every form (typed and untyped, [edge ...] edge objects, trailing node-type filters like [--> [?:Area]], and any origin including a plain def parameter), not only as a bare visit target. del e on an edge now disconnects it from the in-memory adjacency instead of silently doing nothing. And spawn / connect / edge-ref on archetypes imported from another native module now work across the boundary: imported OSP archetypes materialize into the consumer with deterministic sorted type tags, so a library walker (compiled with the library's tags) and the consumer's graph wiring agree at runtime without recompiling library code.
  • Native OSP: general cross-module type tags and bare-edge connects (issue #6642): native Object-Spatial type tags are now globally-stable content hashes instead of per-module positions, so spawning, connecting, and edge-referencing archetypes imported from another native module works in every configuration, including a consumer that defines its own OSP archetypes or imports OSP from more than one library (previously these shifted the shared tags and were rejected). And a bare-edge-type connect, a +>: Edge :+> b without the (), now records the edge with its type instead of silently dropping it, matching the Edge() form and the server/client pathways.
  • Feature: Read-path dangling-reference healing, jac db fsck, and a get_persistent_memory hook (#6619): Graph traversal now resolve-or-skips a dangling reference instead of raising "is not a valid reference" on every read shape (the #6587 incident): a genuinely-missing referent is filed into the quarantine store under a new DANGLING_REF reason code and its stale citation pruned, staged as a normal edge-list-delta intent so even a read-only request self-heals on commit; a recoverable (schema-drift / class-missing) quarantine is left intact for jac db recover. A new jac db fsck action scans for dangling references and orphan edges/nodes (read-only by default) and, with jac db fsck repair, prunes danglers and collects orphans in one transaction. A new get_persistent_memory plugin hookspec lets a DB-backend plugin supply its own PersistentMemory (consulted by TieredMemory before falling back to SqliteMemory) instead of cloning the whole execution-context joint.
  • Typed edge endpoints (issue #6657): an edge may now declare the source and target node types it connects, after a : and using the traversal arrow -- edge Follow: Profile --> Profile {}. A neighbour traversal then infers the declared node type instead of any: [x ->:Follow:->] is list[Profile] (target) and [x <-:Follow:<-] is list[Profile] (source), with no [?:Type] filter -- so a field read off the result resolves statically and reads correctly on the native backend. The declaration is opt-in (an untyped edge Link {} keeps any -> any connectivity and list[any] results), is a bound (Profile or a subtype), and is inherited by subtype edges (edge TimedFollow(Follow) {}) unless re-declared; the () after the name stays reserved for edge inheritance. The clause is edge-only -- placing it on a node/walker/obj is a new E2027.
  • Feature: Project-root-absolute imports and scoped jac test discovery: No-dot imports (e.g. import from engine.math.vec3 { Vec3 }) now resolve against the project root (the nearest jac.toml directory) from any depth, so a test in a tests/ subdirectory uses the same import path it would at the repo root. No-argument jac test honors [test] directory in jac.toml, scoping collection to that directory so application modules whose top-level with entry runs on import are not executed during test discovery. Native and interpreted resolution now break same-name collisions identically, preferring the importing file's own directory.
  • Feature: Kind-aware jac run: In a project, a bare jac run (no file) resolves the project kind from [project] kind in jac.toml (or infers it from the entry-point's codespace) and does the natural action for that kind - execute (cli/native-app), serve (api-service/fullstack/...), or build (native-binary/shared-library/pypi/npm packages). jac run --show prints the resolved plan and equivalent primitive command without running it. Explicit jac run <file> is unchanged.
  • Feature: first-class iter()/next() and iter() over range/dict/set in native code: The native (na {} / .na.jac to LLVM) backend now supports first-class iterator values. it = iter(x); next(it) and for x in it over a stored iterator work, with next() raising StopIteration at exhaustion. The native type checker infers iter(x) -> Iterator[T] and next(it) -> T by resolving the element type through the same __iter__/__next__ machinery the for-loop uses, with Iterator[T]/Iterable[T] stubs added to the native ambient surface. iter() now also works over range (both directions), dict (yielding keys), and set, in addition to the existing list/str/map/filter/enumerate/zip forms. Plain container for-loops keep their inlined fast paths, so there is no hot-path change. This is phase 2 and M1 of the native iterator protocol (#6403).
  • Feature: GDB-debuggable native binaries (jac nacompile -g): Passing -g/--debug to jac nacompile emits DWARF line tables and DISubprograms and has the pure-Python ELF linker relocate the .debug_* sections and write a .symtab plus a full section header table, so gdb/lldb can resolve source-level breakpoints and backtraces. -g implies --scrub so a cached non-debug build isn't reused; non-debug builds remain byte-identical to before.
  • Feature: length-aware bytes, the struct module, and binary file reads in native code (issue #6404): The native (na {} / .na.jac to LLVM) backend gains the Python-congruent byte-handling surface a binary parser needs, so the same import struct; struct.unpack(...) and open(path, "rb").read() source compiles and runs on both the sv (CPython) and na (LLVM) backends. bytes is now a distinct length-aware type (a pointer to { i64 len, [N x i8] data }) instead of an alias for the NUL-terminated str, so embedded NUL bytes survive and len() is exact: byte-string literals, indexing (returning an int, with negative and bounds-checked indices), slicing (returning bytes), ==/!=, str.encode(), bytes.decode(), int.from_bytes() and int.to_bytes() all key off the explicit length (the old from_bytes used strlen and truncated at the first NUL). The struct module lowers a compile-time format literal to byte-exact, host-independent reinterpretation of ints and floats with little/big-endian, repeat counts, and pad bytes: unpack returns a tuple, pack returns bytes, and calcsize returns the byte width. open(path, "rb") returns a BinaryFile whose read()/readline() yield length-aware bytes and whose write() takes bytes (mirroring CPython's BufferedReader vs TextIOWrapper split), modeled with an open overload in the native ambient stubs so the static type and the emitted struct always agree; text-mode open() is unchanged.

Bug Fixes#

  • Fix: Catch bare re-declarations in .impl.jac files: Added a new E2026 compiler diagnostic so bare def foo() { ... } definitions in .impl.jac files are flagged instead of silently failing to link to their declaration stubs. Use an impl block to provide the implementation body. The diagnostic is emitted once per offending definition even when cross-variant connect_impls would otherwise report it multiple times.
  • Fix: Direct calls to JSX-returning functions now normalize to component props: Functions that return <JsxElement> may lower to a const { a, b } = props destructuring pattern. Direct calls like Greeting("World"), keyword calls like Greeting(name="World"), and positional spread calls like Greeting(*vals) now compile to the same single-props ABI that JSX runtime calls use, instead of passing the raw positional shape through.
  • Fix: console cyan/dim Span roles render unstyled under jac-super: Facade and Rich renderer now use semantic roles highlight and muted instead of raw color names, so styled chrome matches across ANSI and Rich backends.
  • Fix: jac2py crash on f-strings with single-quoted literals around interpolation (#6564): PyastGenPass.exit_multi_string no longer wraps an already-generated JoinedStr inside a second JoinedStr when the parser represents an f-string as MultiString([FString]). Mixed implicit concatenation ("pre" f"{x}" "post", f"a" f"b") now flattens to a spec-compliant JoinedStr whose values are only Constant(str) and FormattedValue, so ast.unparse() in jac2py succeeds.
  • Native OSP stopgap gate made sound (issue #6636): visit over a list target (a walker-field list or an inline list literal) now enqueues the list's elements instead of aborting at runtime after a clean compile; skip now lowers to a bare return (it was silently dropped, turning the documented if here in self.seen { skip; } visit-once guard into dead code); edge connects lower in any module that uses OSP constructs rather than only modules that happen to define a walker; and spawn/connect/edge-ref on OSP archetypes imported from another native module fail with an E5090 whose help text names the defining module and the cross-module materialization gap (#6146) instead of a generic unsupported-op.
  • Native diagnostics: jac check / jac nacompile parity (issue #6636 follow-ups): jac nacompile now renders diagnostics with their help: text (via Alert.pretty_print, like jac check/jac run) instead of dropping it, so native E5090 guidance (the cross-module-OSP hint and the unsupported-stdlib-import hint) finally reaches the user; and skip in a value-returning native function (an object-spatial walker statement that native lowers only inside a walker with event ability) is now caught at jac check time via the shared native_capability_violations authority, instead of passing check and failing only at jac nacompile. As a side effect jac check now also previews the other structural native gaps (non-allowlisted imports, structural match patterns).
  • Fix: spurious W1051 on named entry-block labels (#6644): A named entry block such as with entry:__main__ or with entry:foo no longer emits a bogus W1051 ("Expression type could not be resolved") on the entry-point label. The label is a ModuleCode.name Name node with no symbol, not a variable reference; the type checker's exit_name handler now skips it alongside the other label-like Name nodes (member-access targets, keyword-argument keys, type-annotation tags, JSX names) instead of type-evaluating it to Unknown. This warning previously fired on the idiomatic with entry:__main__ guard of nearly every runnable Jac program.
  • Native OSP: reading a field off an edge-ref / node-ref result (issue #6646): a graph navigation like [edge here ->:Portal:->] or [here --> [?:Area]] returns its elements as opaque handles, so reading a field off one (ports[0].cx, for e in [...] { e.cx }, a [?:Area]-filtered nodes[0].name) used to drop to E5090 or crash the compiler with 'IntType' object has no attribute 'is_opaque'. Native now recovers the archetype struct pointer from the handle, so edge fields and filtered node fields read correctly; an unfiltered node-ref element (typed any) stays a clean E5090 instead of an uncaught traceback.
  • Native OSP: actionable error for field access on an untyped neighbour traversal (issue #6646): reading a field off an unfiltered node-ref element ([here ->:Portal:->][0].tag) cannot resolve a layout on native, because an edge may connect any node types so the neighbour's element type is any. That access still E5090s, but the diagnostic now explains the cause and points to the only sound fix today, a [?:Type] node filter (e.g. [... -->[?:NodeType]]), instead of a bare "does not yet support attribute access". The longer-term language fix, typed edge endpoints, is tracked in issue #6657.
  • Native OSP: cross-module node field layout is now sound (issue #6659): a node/walker/edge archetype imported from another native module is now laid out identically to its defining module, fixing three cross-module corruption bugs. (1) A consumer that only constructs and reads an imported node (no spawn/connect/visit, so it does not "use OSP") reserved no OSP header, so a.field read the vtable slot and returned garbage; imported archetypes now always reserve the header to match the defining module, while loading the kernel stays gated on real OSP usage. (2) Writing an inherited field of an imported subtype (p.yaw = ..., p.pitch += ...) raised E5090 because the consumer's layout registry never saw the imported base classes; the importing module now copies each imported module's archetype hierarchy and MRO, so inherited fields resolve for both reads and writes (imported ancestors are also laid out before their subtypes). (3) A cross-module Area | None return, narrowed with is not None and then read, passed to an Area-typed parameter, or used as a spawn target, returned garbage or crashed at runtime; with the layout sound it now agrees with the @sv pathway.
  • JS codegen: except Exception catches native JS errors (issue #6656): On the ECMAScript target, try { ... } except Exception { ... } lowered to a typed __jac_e instanceof _jac.exc.Exception guard. Native JS throws (a SyntaxError from JSON.parse, an interop TypeError, host/library Errors, thrown non-Error values) are not part of the _jac.exc class hierarchy, so they fell through to the re-throw else, silently breaking the common best-effort try { … } except Exception { } resilience pattern and diverging from both Python and the pre-0.16 codegen. The generated runtime now exposes a _jac.exc.matches(e, name) predicate (derived from JAC_TYPE_REGISTRY) encoding Python catch semantics: BaseException (and bare except) catches everything; Exception catches every Jac Exception plus any non-Jac throw; a narrower builtin stays a hierarchy-aware instanceof so native errors are not swallowed by e.g. except ValueError; and a user-defined exception archetype emits __jac_e instanceof X instead of the old if (true) catch-all that caught every exception.
  • Fix: JIR-reloaded FuncCall keeps callee_decl / call_kind (issue #6656): JirReader._read_node rebuilds every AST node with cls.__new__, which skips __init__/postinit, then re-seeds each node type's postinit-only fields; FuncCall had no such branch, so its callee_decl and call_kind vanished on a JIR-cache roundtrip (absent, not None). The native and ECMAScript (client/JS) backends read .callee_decl off the call node to lower kwargs into ordered positional args and have no re-resolution fallback, so on a warm build they crashed with 'FuncCall' object has no attribute 'callee_decl'. The reader now seeds both postinit defaults for FuncCall, so a JIR-reloaded call carries a clean "unresolved" declaration (exactly what a builtin/unresolved target gets) instead of a missing attribute, and warm builds no longer crash.
  • Native: is not None flow narrowing now applies on the imported-module path (issue #6667): a T | None guarded by if x is not None { ... x.field ... } (or the is not ternary) narrowed to T when the code was the entry module passed to jac nacompile, but stayed T | None (E1099 / E1115 / E1053) when the same code lived in an imported module -- blocking any multi-module native app that returns optionals from library code and consumes them elsewhere. Flow narrowing inside the type checker walks the control-flow graph (bb_in/bb_out), which a full compile builds via CFGBuildPass; an imported dependency was compiled symtab-only (CFG skipped) yet native still ran inference on it (the cross-module linker lowers a provider's annotations through Expr.type), so the narrowing silently no-op'd. The CFG is now built before inference whenever it was not already built (gated on symtab_ir_only, so the entry path is untouched), making imported-module narrowing identical to the entry path.
  • Native OSP: kernel links when an imported module does the spawn/visit (issue #6669): a layered native binary whose Object-Spatial spawn/visit lives in an imported module rather than the thin entry now links the OSP kernel and runs, instead of building cleanly but failing at load with undefined symbol: __jac_glob_init_osp_kernel.
  • Native: jac check now surfaces untyped-traversal and multi-hop edge-ref errors that previously only failed at jac nacompile (issues #6646 / #6636): a field read off an untyped (any) graph-traversal handle (e.g. nbrs[0].field where nbrs = [here -->]) and a multi-hop / value-compare-filter edge reference ([here -->-->], [?:Area(x > 1)]) were rejected with E5090 only by the native backend, which jac check never runs (it compiles with codegen off). Both checks now live in the central native_capability_violations authority -- detected structurally from the checker type / AST shape -- so they report identically under jac check and jac nacompile. The now-redundant codegen guards (plus a redundant MatchOr alternative check) are removed.
  • Fix: Native OSP graph survives a cross-module spawn (#6678): when the graph was built in one module (e.g. a build_arena(world) helper) and a walker was spawned in another, the spawning module's first spawn wiped the graph -- [world -->] collapsed from its real count to 0 after a single spawn (headless), or aborted rendering a freed graph (windowed). The OSP kernel's adjacency globals (_e_src / _walk_stack) are link-merged singletons, but __osp_setup and its __osp_inited guard are per-module (internal), so each module re-ran the shared kernel glob-init (__jac_glob_init_osp_kernel) the first time it spawned/connected, re-initializing _e_src = []. The kernel glob-init now runs exactly once program-wide behind a shared __osp_kernel_inited flag, while per-module descriptor setup still runs once per module.
  • Native: Object-Spatial nodes built in a non-OSP module now carry their dispatch tag (#6683): a native program that constructs a node/edge/walker in a module that does no spawn/visit/connect of its own -- e.g. building the world graph in the entry module while the walker that spawns over it lives in an imported module -- no longer aborts mid-walk (Aborted (core dumped)) when the imported code spawns on that instance.
  • Native: jac run loads C shared libraries declared in imported modules (#6687): a native program whose FFI lives behind a logical ($ORIGIN-relative) C-library import in an imported module no longer segfaults under jac run (in-process JIT) -- the library is now loaded so its symbols resolve, matching the jac nacompile standalone binary that already ran fine.
  • Fix: native for (k, v) in d.items() over-released the dict's key/val: The items loop bound the borrowed key/val handles (returned by __dict_get_key/__dict_get_val) into pointer locals that scope-exit cleanup releases, but never retained them, so a dict's last key+val were over-released and their heap backing was freed under the still-live dict. In a long-running native OSP loop that read a node's str field each spawn this surfaced as a use-after-free, eventually handing a dangling char* into a C-FFI call. The loop now retains each borrow and releases the prior occupant per iteration, matching the existing list/dict discipline (issue #6688).
  • Fix: native os.getenv(key, default) segfaulted on an unset variable: The native lowering ignored the default argument and returned a NULL pointer when the variable was unset, so any downstream string use of the result (len, +, !=) ran strlen on NULL and crashed at startup. os.getenv now substitutes the provided default (matching Python: the default is returned when the variable is unset, None when no default is given). This was the real cause of the issue #6688 crash (os.getenv("QP_SMOKE", "") != "" crashing ./main instantly when QP_SMOKE was unset), which had been misattributed to a per-frame node-field refcount over-release.
  • Fix: native iterator over a stored iterator double-released its reference (use-after-free): In native (na {} / .na.jac to LLVM) code, consuming a stored iterator value (for x in iter(it), it2 = iter(it), or an adapter such as for x in map(f, it)) returned the same boxed JacIter the variable already owned without taking a new reference, while the consumer (the loop, the new binding, or the adapter's owned upstream) released it as if it owned a +1. The single reference was therefore released twice: once by the consumer and once when the source variable went out of scope. iter() of an already-iterator now retains the reference so it genuinely carries the +1 its consumer releases, matching the ownership contract for every other iter() result. Also, iter()/next() are now correctly rejected by the native type checker when called with a keyword argument (e.g. next(it=x)), matching CPython where both are positional-only, instead of type-checking a form the backend could not lower (#6403).
  • Fix: Typed archetype returns rehydrate across sv import boundaries: A server-to-server sv import whose declared return type is an archetype no longer hands the value back as a raw _jac_type dict (which made attribute access raise 'dict' object has no attribute ...). Boundary-type collection previously accepted the consumer's own sv import binding (a non-type node) and short-circuited the provider AST-walk fallback when the provider was not in the program hub at codegen time, as happens during per-service compilation under jac start. It now only accepts a symbol that resolves to an Archetype/Enum, so the return is wrapped in <RetType>._from_wire(...) and the imported type lowers to a proper boundary stub class.
  • Fix: Native OSP archetypes wired only from an impl annex now compile: A native module that imports a node/edge/walker and connects or spawns it only from a companion .impl.jac file no longer fails to compile (E5090 ... not materialized) or crash at runtime; impl-annex usage is now recognized the same as inline usage.

Refactors#

  • Analysis centralization (train 9 - plan complete): the One Owner Per Analysis relocation (jaseci-labs/jaseci#6542) closes out. Codegen-time expression-type reads have one owner (type_utils.expr_primitive_name: stamp when present, lazy authority query otherwise - the ES backend contains zero type-evaluator references); the explicit-native rejection table (NATIVE_REJECT_NODES) rejects yield/edge-disconnect/inline-Python pre-codegen, fixing a silent drop of ::py:: blocks in native binaries; spawn-walker detection consumes the checker-stamped symbol; and the backend-purity test becomes the standing end-state contract - migration debt zero, with all remaining analysis-API matches sanctioned by exact count and per-entry rationale. (jaseci-labs/jaseci#6542)
  • Refactor: get_all_sub_nodes drops brute_force (#6670): The _sub_node_tab descendant-by-type index is now invalidated on every kid mutation (set_kids, add_kids_left, add_kids_right, insert_kids_at_pos, plus the direct .kid writers in esast_gen and jac_auto_lint), so it stays authoritative. UniPass.get_all_sub_nodes reads the table unconditionally: the brute_force parameter, its ValueError fallback, and the recursive kid-walk are removed along with the flag at every call site. This also closes a latent staleness bug where a stale but non-empty cache silently returned wrong results instead of falling back to the walk.
  • Refactor: Eliminate cross-skill duplications and introduce jac-shadcn-blocks skill: Introducejac-shadcn-blocks-* skill into a detailing design system constants and composition patterns.
  • Refactor: Centralize OSP compile-time analysis into one source (#6725): All object-spatial (OSP) compile-time facts -- archetype kinds, globally-stable type tags, the is-a closure, dispatch slots, and construct usage -- are now derived once in a single osp_model.jac module (OspAnalysis.compute -> OspModel) and stored on CodeGenTarget.osp_model, replacing the duplicated and diverged derivation logic that the native (LLVM) and ECMAScript backends each carried. Both backends now read the same model, the shared stable_osp_tag definition eliminates the prior hash-vs-positional tag drift between them, and tags are masked to 53 bits so they stay exactly representable in the ECMAScript backend's JS Number (keeping the compile-time collision check sound for both backends).

Documentation#

  • Skill guides hardened against a real production project: corrections measured against the flagship showcase app - endpoint registration via client sv import stubs (no entry-module import needed), WritePerm vs ConnectPerm grant levels for field-mutation interactions, jobj() cross-user lookup, plain-name C-FFI $ORIGIN resolution - plus a new jac-sv-streaming guide (SSE/Generator endpoints), the client .impl.jac handler-annex pattern, the real wasm import surface (host-side allocator, WebAssembly.Module.imports() introspection), and a server-contract drift-detection workflow. The jac guide listing is now grouped by domain, and ten diagnostic codes (E1050, E2025, E1102, W0060, W3037, ...) now print -> run 'jac guide <name>' hints.

jaclang 0.16.3#

New Features#

  • Declarative schema repair for persisted archetypes (__jac_schema__): Nodes and edges can now declare how their stored shape evolved by defining a static def __jac_schema__() -> None; whose body calls the new ambient builders: schema_was("old.mod.Name") for class renames, schema_alias("name", stored="username") for field renames, schema_drop("legacy") for removed fields, and schema_upgrade(fn, when=pred) for arbitrary transforms. Rules are validated against the live fields at registration and applied automatically when old documents load: renamed fields are repaired in place, removed/unknown field values are preserved in a __jac_attic__ sub-document instead of silently dropped, aliased fields dual-write their old stored name so old app versions keep reading during rolling deploys, and fields added after a document was written are backfilled from their defaults (fixing a latent AttributeError on default_factory fields). The JAC_SCHEMA_REPAIR environment variable gates the engine (repair/detect/off), and jac db schema rules lists the registered rules.
  • test blocks run natively in the na codespace: a test in a .na.jac module or inline na {} block compiles to native code, executes via the JIT under jac test, and reports through the standard test pipeline with source-located assertion failures. (jaseci-labs/jaseci#6599)

Bug Fixes#

  • Fix: Jac or/and with getattr/dict.get defaults no longer emit invalid JS: getattr(obj, key, default) and dict.get(key, default) lower to nullish coalescing (??). When that result is combined with Jac or or and (lowered to ||/&&), the unparenthesized ?? subexpression produced a JavaScript SyntaxError. ES codegen now parenthesizes the ?? default so ||/&& chains compile and run correctly.
  • Fix: Port collisions no longer break the dev server: Vite starts after the API server resolves its port, so the proxy always targets the real API even when ports collide. The startup banner shows the actually-bound ports.
  • Perf: fixed a regression that made compiling and starting plain Jac programs roughly 2-3x slower and use about twice the memory. Type checking now runs only when it is actually needed (native and client builds, or jac check), so a normal jac run no longer pays for it. Restores first-install, startup, and memory back to previous levels. (jaseci-labs/jaseci#6621)

Refactors#

  • Analysis centralization (train 6): the JIR-carries-semantics question is measured and decided (semantic annotations stay recompute-on-load; warm cache hits already skip analysis entirely, with numbers recorded in the architecture spec); foreign calls now classify centrally as CallKind.CLIB - the seam the Phase 8 marshalling plan builds on. (jaseci-labs/jaseci#6542)
  • Analysis centralization (train 7): the native foreign (clib) boundary now has a single owner. A central foreign declaration model (compiler/targets/foreign.jac) owns the declared struct vocabulary and C data layouts; foreign call marshalling plans (struct classification, coerce expansion, sret promotion) are decided by classify_foreign_fn in the pure target ABI library, with the backend reduced to LLVM materialization. The backend's field_type_node annotation-AST cache is deleted and the purity ratchet tightens. Also pins the type registry's bootstrap compile order via a module-level checker import, keeping first-use compilation memory flat. (jaseci-labs/jaseci#6542)
  • Analysis centralization (train 8): the foreign declaration model now owns declared clib function signatures (collect_foreign_fns; the backend's _clib_type_name annotation walker is deleted); declaratively-unsupported native constructs (disqualifier nodes, structural match patterns, non-allowlisted Python imports) are rejected by a single pre-codegen capability sweep instead of scattered mid-emission checks; and the ES backend's primitive dispatch reads checker-stamped Expr.type instead of re-running inference at codegen, with two typed-tree stamp gaps (resolved bare names, CfgExpr narrowing wrappers) closed at the source - which also drops cold-bootstrap compile cost (~10% wall, ~0.6GB peak). (jaseci-labs/jaseci#6542)

Documentation#

  • Generated AGENTS.md covers the full dev loop: projects created with jac create now point coding agents at jac start for running fullstack apps and jac browse for headless-browser QA, alongside jac check and jac run.
  • Agent skill guides overhauled (22 → 35): 17 verified accuracy fixes across the existing jac guide set (typed-edge syntax, generic-entry semantics, native stdlib/subset claims, impl-file layouts, endpoint auth/isolation semantics, Ref[T] refs, and more), plus 13 new guides - testing, debugging, jac.toml config, a project-kinds router, microservices, deployment, client JS interop, desktop, mobile, C-ABI shared libraries, wasm, Python interop, and concurrency. Every ``jac example in every guide now passesjac check, and related doc/CLI corrections (including the brokenjac create` hint flags) ride along.

jaclang 0.16.2#

New Features#

  • jac install -e now accepts multiple packages: Pass multiple editable paths in one command - space-separated (-e ./a ./b) or with repeated flags (-e ./a -e ./b) or mixed. Dependencies across all packages are resolved in a single pip call so cross-package conflicts are caught upfront. (jaseci-labs/jaseci#6414)
  • Feature: System-browser SSO login for the desktop shell: The client runtime gains jacSsoLogin() (plus jacSetToken()). Inside the Jac-native desktop shell it hands login to the user's real system browser via the loopback OAuth broker (RFC 8252) and reads the token back, instead of forcing a fresh login inside the embedded webview; on the web it redirects to the server's /sso/<platform>/<operation> endpoint as before. (jaseci-labs/jaseci#6485)
  • Feature: native Object-Spatial graph traversal: The native (LLVM) backend now lowers visit, disengage, and report, plus edge connect (++>, +>: Edge :+>) and edge references ([-->], [->: Edge :->]), to the portable osp_kernel. Edges are stored in a native in-memory adjacency (osp_graph.jac) compiled into the kernel module, so a walker can traverse a graph natively (typed refs filter by edge type). OSP instances live in a program-lifetime graph arena so the walk's borrowed location handles are not refcount-freed mid-traversal. report collects onto a built-in walker reports list that the kernel's on_complete delivers at the spawn boundary, so w.reports reads back. All six cross-backend Object-Spatial equivalence fixtures (visit order, exit ordering, disengage, report, typed edges, subtype dispatch) now run on the native backend. (jaseci-labs/jaseci#6541)
  • root.shared addresses the public graph: Any walker can read and extend the deployment's shared root - the graph unauthenticated requests run on - directly via root.shared, with commons-by-default access (readable and attachable by every user) in place of allroots() scans and app-managed arming grants. (jaseci-labs/jaseci#6554)
  • Zig shooter twin gains the self-screenshot protocol: The raylib shooter example's Zig baseline honors the same dormant .screenshot capture file as the Jac build, so tooling can screenshot both engines' real runs. (jaseci-labs/jaseci#6573)
  • Native: Callable = 0 (NULL fn-ptr) defaults in clib struct fields: In native (.na.jac) modules an integer may now initialize a Callable[[...], ret] slot, typically the literal 0 for a C NULL function pointer in clib vtable structs (CEF/libuv callback slots). Regular Jac modules keep rejecting the assignment (E1001).
  • Native: C→Jac callback trampolines for clib vtable structs: A Jac def stored into a Callable field of a clib struct now goes through a cached C-ABI trampoline ({fn}.__clibcb.{n}) carrying the slot's exact C signature, so C libraries (CEF, libuv) can invoke Jac callbacks with correct argument/return marshalling, including integer sign extension.
  • Native: flat C-owned allocation for clib vtable structs: Instantiating a clib struct with function-pointer slots now produces a zero-initialized calloc block with the C memory layout (no RC header, destructor, or retain/release traffic), so C runtimes that validate struct->size at offset 0 and hold the vtable beyond the creating scope (e.g. CEF) see exactly the struct they expect.
  • Dev-server build health via GET /__build_status: The hot reloader now exposes its build status (ok / compiling / error / unavailable, with the failing file and message on error) so external tooling can tell a clean build from a broken one.

Bug Fixes#

  • Fix: Scheduler uses UTC consistently: The runtime Scheduler now uses datetime.now(UTC) for all scheduling comparisons and treats naive date=... strings on @schedule as UTC, so static scheduled tasks fire at the same wall-clock moment regardless of the container/process local timezone.
  • Fix: Type narrowing now works inside list comprehensions in ternary expressions: When you write [x for x in items.data] if items else [], the type checker now correctly understands that items cannot be None inside the comprehension. Previously it raised a false error on the attribute access even though the condition already proved it was safe.
  • Fix: x = None; if cond { x = real_value; } no longer raises a false E1002: the idiomatic None-then-real pattern was dropping its new type post-branch because the CFG narrowing walker rejected set[str] as not assignable to NoneType. It now applies the existing evolving-locals rule, matching Pyright.
  • Fix: bare jac run reports a missing filename instead of crashing: Running jac run with no file argument passed None into the handler and raised a TypeError. CLI positionals without defaults are now treated as required, so users get a clear argparse error (the following arguments are required: filename).
  • Fix: byte-string literals (b"...") are typed as bytes, not str: Assigning a b"..." literal to a bytes variable or adding it to other bytes no longer raises false type errors.
  • Fix: jac2js crash on isinstance with builtin types (#6458): isinstance(x, str) (and any other builtin-type check, e.g. the shadcn sidebar component) no longer aborts JavaScript codegen with a bare IndexError: list index out of range. The ES pass now lowers builtin-type names (str, int, list, ...) used as values, and isinstance/issubclass route through a runtime helper that implements correct Python semantics for builtin types, tuples of types, and user archetypes (including bool as a subclass of int). Any remaining builtin-call codegen failure now surfaces a located E5016 diagnostic instead of crashing the build. As a consequence, a builtin name used as a dict key (e.g. {type: "email"}) now correctly lowers to that key instead of the accidental key fallback it produced before; JacForm's field renderer is updated to read config.type accordingly so field_config input types (e.g. type: "email") are honored and native HTML validation works again.
  • Fix: dev server banner now links to the frontend, not the API: In HMR dev mode the headline Local: URL pointed at the API port instead of the Vite dev server, so the link opened the wrong address. It now points at the frontend you actually open in the browser.
  • Fix: DOM member access off document.documentElement/body/head in client code: Reaching members like .classList off document's root elements in .cl.jac code now type-checks instead of failing with E1030: Type "object" has no attribute "classList". (jaseci-labs/jaseci#6489)
  • Fix: Slicing a list-typed object field no longer crashes in the native backend: In the native (LLVM) backend, slicing a list-typed object field with a range slice (such as b.items[:pos] + b.items[pos:]) aborted at runtime with a spurious out-of-bounds error when the index was conditionally computed. The slice was mistakenly lowered as a plain index. Field slices now behave identically to plain-list locals.
  • Fix: Native field access on union/tuple-trigger values: On the native (LLVM) backend, accessing a field on a union-typed value (x: A | B; x.val), and in particular here.val inside an OSP tuple trigger like with (Sub, Other) entry, silently emitted no code, leaving the function body empty and producing no diagnostic. The implicit here/visitor for a tuple trigger now type-checks as the union of its member types (instead of a tuple), and native codegen lowers the access through a representative member layout, raising a new E5091 diagnostic when members disagree on a shared field's offset.
  • Fix: a cl→sv endpoint module can also be a server→server consumer: A server .jac reached by the browser via a client's sv import that also did its own relative sv import from .other_service silently lost all of its own endpoints (walkers/functions vanished from the API with no error). The relative provider name was recorded with its leading dots (.other_service) but looked up bare (other_service), so no RPC stub was generated and the import fell through to a plain Python import that crashed the module at load time. Relative and absolute sv import now resolve identically, and a provider module that fails to import is logged instead of silently dropped. (jaseci-labs/jaseci#6507)
  • Fix: Native backend no longer drops statements silently: A class of native (LLVM) codegen gaps used to compile clean and then misbehave at runtime, because a statement whose sub-expression could not be lowered (an unresolved attribute/index access, an untracked container, an unsupported for iterable such as a set) was dropped with no diagnostic. These cases now raise E5090 at compile time, covering return values, assert/if/while/for conditions, assignment right-hand sides, subscript and field stores, and for-loop targets/iterables, so the failure is reported at its source instead of producing an empty body or a default return.
  • Fix: Concrete scalars stored into any tuple slots in a bare list are now boxed in the native backend: In the native (LLVM) backend, putting a tuple built from a concrete int/float into a bare list whose elements are any (such as stack.append((7, 0)) after seeding the list with an any-typed value) stored the scalar without boxing it, so reading it back through the any seam returned 0/garbage. The scalar is now boxed to match the list's element layout, matching the Python backend.
  • Fix: Imported public walkers and functions are now reliably reachable without auth: Serving an app that imports a :pub walker or function from another module could intermittently reject anonymous requests with Unauthorized. The endpoint's access level is now resolved deterministically regardless of import order, so public imported endpoints stay public.
  • Fix: server-to-server sv import calls no longer report a spurious missing-await error: Binding the result of a function imported from another server via sv import to its declared return type was type-checked as if it returned an un-awaited coroutine. Server-to-server calls resolve synchronously, so they now type-check directly to the provider's return type, while client-to-server sv import calls still correctly require await.
  • Fix: Jac client components can now forward refs: A client JSX component that declares a trailing ref: Ref parameter now lowers to forwardRef(function C(props, ref) {...}) instead of a plain React function component, so it can be used as a ref target. Previously there was no way to author a ref-forwardable component (the trailing param was wrongly destructured out of props), which silently broke every radix asChild composition over a Jac component (DropdownMenu / Tooltip / Popover / HoverCard / Dialog triggers never positioned). Components that declare no ref param compile exactly as before.
  • Fix: typed connect operators format compactly: jac format now renders typed connect operators such as +>:Edge:+> and +>:Edge:weight=5:+> without spaces around the inner colons, matching the existing traversal (->:Edge:->) and disconnect (del ->:Edge:->) forms.
  • Fix: false type error on classes shared through a circular import: When two modules imported from each other and one of them was the file you ran jac check on, a class shared across the cycle could be reported as not matching itself (e.g. Cannot return tuple[Tok, Tok], expected tuple[Tok, Tok]). Such code now type-checks correctly.
  • Native for-loop dispatch hardening: the unified for-loop skeleton's element fetch now dispatches explicitly per iteration kind and raises an E9002 internal-contract error on an unknown kind, instead of silently falling through to key-fetch semantics. (jaseci-labs/jaseci#6542)
  • Fix: Python classes with both __init__ and init no longer rejected as duplicate methods: Importing a Python class that defines __init__ alongside a plain method named init (such as Pillow's PIL.ImageFile.PyCodec) raised error[E0076]: Duplicate method 'init' in class body, so any program importing Pillow failed to compile. The duplicate-method check now distinguishes the constructor from a regular init method the same way Python code generation does, while still flagging genuine duplicates.

Refactors#

  • Analysis centralization (One Owner Per Analysis, first phases): type inference now runs on every codegen-bearing compile so Expr.type is a pipeline invariant; new semantic facts live on the unitree (Symbol.storage, Archetype.arch_kind, Ability.event_triggers, LambdaExpr.captures, FuncCall.call_kind) and the ECMAScript/native backends consume them instead of re-deriving types, scopes, captures, and call classifications locally. A missing semantic annotation at codegen is now an E9002 internal error instead of a silent i64 fallback. (jaseci-labs/jaseci#6542)
  • cl markers are never needed in .cl.jac files: The .cl.jac suffix is the client marker - bare top-level globs and imports type-check and compile as client code, and reactive useState lowering applies only to component (def) state, never to plain object fields. (jaseci-labs/jaseci#6557)
  • Analysis centralization (Phase 4, call resolution): the type checker now stamps the resolved callee declaration on every call node (FuncCall.callee_decl); the native backend reads resolved signatures (parameters, default-argument expressions) off the call site and records interop-marshalling return facts at declaration time, deleting its name-keyed AST-signature cache (method_ast_sigs). Default-argument resolution now respects user shadowing the way the checker does. (jaseci-labs/jaseci#6542)
  • Analysis centralization (Phase 7, ownership - first slice): the reference-counting ownership invariant for expression results now lives in one place (compiler/ownership.jac: OWNED / BORROWED / STATIC / UNKNOWN with the documented rules); the native backend applies it at a single seam in expression dispatch, deleting the 13 scattered per-site owned-marking decisions and the _return_tuple_ctx emission flag (return-position tuple packing is now read off the tree). (jaseci-labs/jaseci#6542)
  • Native backend dedup (analysis centralization train): every native for form (range, list, dict keys, dict items, string) now lowers through one indexed-loop skeleton with a single home for the loop-variable RC discipline and loop-exit releases, replacing five duplicated emission paths; the duplicated non-primary-ancestor method walk in archetype registration is unified into one declare/emit-mode helper. (jaseci-labs/jaseci#6542)
  • Analysis centralization (train 4): one capability/portability authority (capability_check_pass.jac with declarative tables; UniTreeEnrichPass and PortabilityCheckPass deleted); declarations lower checker-stamped types (annotation-walk allowlist at zero for expr/globals); a pure target-ABI library (compiler/targets/abi.jac) with direct unit tests owns by-value C struct classification; user-defined map/filter/range no longer get misrouted into builtin fast paths. (jaseci-labs/jaseci#6542)
  • Analysis centralization (train 5): loop-exit release lists become a central ownership fact (ownership.loop_body_locals), deleting the emission-order snapshot diffing at every loop site; ES call resolution drops its scope-lookup and evaluator fallbacks (first ES purity-allowlist ratchet); native import legality is classified by the capability authority; the One Owner Per Analysis contract and authority map are promoted into the compiler architecture spec. (jaseci-labs/jaseci#6542)

jaclang 0.16.1#

New Features#

  • Feature: test blocks run in the client (cl) codespace: A test in a .cl.jac file / cl {} block now actually runs - it compiles to JS, executes under bun, and reports through the same jac test/pytest summary as server tests (codespace inferred from the file/block, no new keyword). Full-stack cl tests boot the backend they sv import on an ephemeral port (falling back to a sibling server.jac/main.jac, or JAC_CL_TEST_SERVER) so client logic is exercised over the real cl->sv fetch/auth bridge. See examples/littleX/tests/test_fullstack.cl.jac: two users sign up, post, follow, and feed propagation is verified end-to-end.
  • jac bundle now produces both a wheel and an sdist: The bundle command generates a PEP 625-compliant .tar.gz source distribution alongside the .whl file in a single run, using a fixed mtime for reproducible archives. Both artifact sizes are reported and written to the output directory together.
  • Native C-FFI: standalone binaries find sibling shared libraries from any directory: A jac nacompile executable now resolves its imported shared libraries relative to the executable's own location instead of the launch directory. The ELF linker emits DT_RUNPATH=$ORIGIN (and the Mach-O linker loads local libraries via @loader_path), and a relative import with a directory component (e.g. import from "./libfoo.so") records just the soname so the runpath applies. Absolute system paths and bare sonames are kept as-is. This means a library shipped next to the binary is found no matter where the program is run from, and a single import from "libfoo.so" resolves across Linux and macOS when the right binary is staged beside the executable. (jaseci-labs/jaseci#6353 gaps #4/#5)
  • Native C-FFI: Python-style extensionless imports with per-platform library resolution: Native imports can now name a shared library by a dotted, extensionless logical name instead of a literal path with a baked-in extension. import from raylib { ... } resolves to libraylib.so / libraylib.dylib / raylib.dll from the target triple, so a single unchanged .na.jac targets Linux, macOS, and Windows. A leading . (import from .raylib) is source/binary-relative and a dotted path (import from libs.raylib) maps to a sub-directory, both pinned to the loader origin ($ORIGIN on ELF, @loader_path on Mach-O) and composing with the runpath from the prior release. The explicit string-path form is unchanged for pinned or versioned sonames. (jaseci-labs/jaseci#6353 gap #5)
  • Native iterator protocol: lazy map/filter/enumerate/zip in for-loops: Native (na) for-loops can now iterate map, filter, enumerate, and zip, and the adapters compose lazily (for example map(f, filter(p, items))) without building intermediate lists. (jaseci-labs/jaseci#6403)
  • Feature: Python-congruent native standard library (v1): The native (na {} -> LLVM) backend now supports import math, time, sys, os/os.path, and random with the same import X; X.func(...) surface as the Python pathway. math lowers to libm (ULP-congruent); time uses POSIX clocks; sys adds maxsize/byteorder/platform; os/os.path cover common filesystem calls; and random ships a CPython-faithful MT19937 so random.seed(n) reproduces the same sequence on both pathways. Unsupported members are rejected at compile time rather than silently producing a wrong binary.
  • jac nacompile --target wasm32 and full-stack na+cl wasm apps: The native backend can now target wasm32-unknown-unknown and link the relocatable object into an instantiable module with a pure-Jac wasm linker -- no wasm-ld or emscripten -- where undefined externs (e.g. import from raylib { ... }) become the module's wasm imports. jac start and jac build compile an na {} codespace to /static/main.wasm as part of the client build, so an na game/library and the cl page that drives it ship from a single module.
  • API server serves a conventionally-named client page at /: When [serve] base_route_app is unset, the server serves a conventionally-named client page (app, index, main, home, or root) at the root path; otherwise the root path stays the JSON API index.
  • Perf: Cache get_type_hints via typecache module for fast deserialization: get_type_hints was called per-anchor during deserialization, accounting for ~77% of deserialization cost. Field-type resolution is now cached per-class in a new typecache module, yielding a 122x speedup on that path and reducing total deserialization time from ~452ms to ~107ms for 999 anchors.
  • jac nacompile --target windows and a pure-Jac PE/COFF linker: The native backend can now produce a runnable Windows .exe, closing the last gap in the cross-platform native story (Linux/ELF and macOS/Mach-O already shipped). A new PeLinker (jaclang/compiler/passes/native/pe_linker.jac) parses the relocatable COFF object llvmlite emits for a *-pc-windows-msvc triple and writes a PE32+ image entirely with Python's struct, with no link.exe/lld-link/external toolchain, matching the ELF/Mach-O/wasm linkers. It lays out .text/.rdata/.data/.idata at a fixed ImageBase with base relocations stripped (no ASLR/.reloc), builds the import directory + IAT with per-function import thunks (the PE analogue of the ELF PLT) so DLL imports resolve at load, applies the COFF relocation kinds the backend emits (ADDR64/ADDR32/ADDR32NB/REL32[_1..5]), resolves weak externals (the __rc_* runtime globals) to their local definitions, and registers .pdata as the exception directory. Undefined externals bind to msvcrt.dll by default (with malloc_usable_size aliased to _msize and snprintf to _snprintf for glibc-isms), while a clib import that names a real .dll is honored and grouped into its own import descriptor; a Linux .so/macOS .dylib clib path falls back to msvcrt. The PE entry stub recovers argc/argv via msvcrt __getmainargs and defines the MSVC _fltused marker locally. jac nacompile hello.na.jac --target windows (or any JAC_NATIVE_TARGET=x86_64-pc-windows-*) emits hello.exe. (jaseci-labs/jaseci#6439)
  • Feature: Native walker spawn node through the portable OSP kernel: The native (LLVM) backend now lowers Object-Spatial spawn into the shared osp_kernel, dispatching by the location's runtime type tag instead of an exact-type stopgap. This fixes the cases the stopgap got silently wrong (computed locations like w spawn items[0], tuple triggers like with (A, B) entry, subtype dispatch such as with Base entry on a Sub, exit abilities, and spawns nested inside an ability) and enforces native equivalence with the server via require=["na"] on the entry-spawn equivalence fixtures.
  • jac nacompile --shared builds C-ABI shared libraries (.so/.dylib/.dll): The native backend can now package a .na.jac module as a shared library that any C/C++/Python/Rust host can dlopen/link, the inverse of import from "lib.so" { ... }. The export surface is whatever you mark :pub (functions and globals; re-exported transitively across imported native modules), so it stays curated rather than dumping every symbol. Jac objects/strings cross the boundary as opaque void* handles, and the library exports jac_retain/jac_release so a host can manage their reference-counted lifetime; module globals initialize automatically on load (ELF DT_INIT_ARRAY, Mach-O __mod_init_func, PE DllMain). The same toolchain-free pipeline emits a position-independent ELF ET_DYN (with a real .dynsym/.hash and section headers, so gcc -l, readelf and nm -D all work), a Mach-O MH_DYLIB (export trie, LC_ID_DYLIB, ad-hoc code signing on arm64), or a PE IMAGE_FILE_DLL (export directory, .reloc, DllMain) -- jac nacompile mathlib.na.jac --shared [--target macos|windows].

Bug Fixes#

  • Fix: ANSI console honors style=: emit_raw applies semantic roles and legacy token styles on the default backend; CLI call sites migrated off inline Rich markup to console.print(..., style=) and helpers.
  • Fix: CFG dot output incorrectly shows full ternary return statement in function-entry block: When a function body contained return x if c else y, the jac tool ir cfg. output placed the entire return statement text inside the function-entry block alongside the function signature. The entry block now shows only the function signature, and each branch of the ternary is correctly labeled as return <value> in its own block.
  • Fix: no spurious CFG edge for a comprehension inside a ternary: jac tool ir cfg no longer draws an invalid edge out of a function whose return is a ternary with a list comprehension in its true branch (e.g. return [x for x in c.items.data] if c.items else [];). The control flow graph now renders correctly for these functions.
  • Fix: keyword parameter name no longer cascades into spurious argument-count errors: Using a Jac keyword as a parameter name (e.g. def f(by: float)) reported the correct E0013 keyword error but then dropped that parameter and every parameter after it, truncating the signature so every call site cascaded into spurious E1051 "Too many positional arguments". The parser now keeps the offending parameter for error recovery, so only the real E0013 (with its backtick-escape hint) is reported, and the escaped form (def f(`by: float)) still compiles. Applies to both function and lambda parameters.
  • Fix: root parameter name no longer breaks bytecode precompile: Three jac0core modules used the reserved keyword root as a parameter name, which raised an Empty py_ast on ParamVar internal compiler error and left those modules out of the shipped precompiled bytecode (degrading cold start). The bindings were renamed to root_node.
  • Fix: parameterless functions with no return type are no longer dropped by the native backend: def foo { ... } (no parameter list and no -> return type) is semantically def foo() -> None, but the RD parser collapsed its empty signature to None, leaving the ability with no FuncSignature node. jac nacompile then silently elided such functions and every call to them (e.g. a def end_frame { EndDrawing(); } wrapper compiled to nothing). The parser now always keeps the (empty) signature, so an absent return type consistently means a None return across type checking, Python codegen, and the native backend; the no-parens form still round-trips to source unchanged.
  • Fix: includes topology changes in the hash computation: Previously the graph topology changes are removed from hash generation. Therefore, the topology changes are not persisted properly in MongoDB. Now we include the topology in the hash computation.
  • Fix: avoid stack overflow: Replace Python recursion with an explicit stack in _visit_recursive (sync) and _visit_recursive_async (async) in osp_kernel.jac / osp_kernel_sv.jac.
  • Fix: reassigning an unannotated literal-typed local no longer errors: An unannotated local's declared type is now widened past its first binding's literal (s = "" freezes s as str, not Literal[""]), so s = "other" is accepted instead of reporting Cannot assign Literal["other"] to Literal[""]. A genuine cross-type reassignment (e.g. str to int) is still rejected, and its message reports the base type (str), not a Literal[...].
  • Fix: native Mach-O clib symbols resolve to the right dylib: The pure-Python Mach-O linker hardcoded the libSystem ordinal for every undefined symbol, so functions imported from another shared library (e.g. import from raylib) bound against libSystem and crashed at launch with dyld: Symbol not found. The native backend now threads a per-symbol library map from the clib imports through to the linker, which emits the correct two-level-namespace dylib ordinal for each symbol.
  • Fix: clearer native and lowercase-literal diagnostics: Lowercase true/false/null/none now get a "did you mean True/False/None?" hint on the W2001 warning instead of only a confusing downstream Cannot return <Unknown> error. jac nacompile now rejects constructs outside the native subset loudly with a clear E5090 -- Python module imports (import math;), builtins that cannot be lowered for the given argument type (e.g. sum over a range), and calls to undefined functions -- instead of silently emitting a binary that prints empty/garbage. The native import allowlist (NATIVE_SUPPORTED_STDLIB) is the extension point for the Python-congruent native stdlib roadmap.
  • Fix: native dict[*, any] literal use-after-free: Boxing a value into a JacVal stored the inner pointer without retaining it, while the dict literal still released owned temporaries, so two or more owned string values in one literal (e.g. {"a": x + y, "b": z + w}) could be freed early and read back as garbage. The boxed path now retains the inner pointer to match the typed-set contract.
  • Fix: native open() raises FileNotFoundError: Opening a missing path now raises FileNotFoundError (catchable as OSError/Exception) instead of returning None, matching CPython.
  • Fix: jac format preserves significant JSX whitespace around inline elements: When a JSX element mixes text with inline child elements (e.g. score <b>0</b> fps), the formatter used to break every child onto its own line and drop the same-line spaces, silently changing the rendered output to score0fps. The mixed-children layout now follows Prettier's model: a significant space at a text/element boundary survives a line break as a {" "} whitespace marker (and a literal space when flat), short space-separated JSX stays on one line, and newline-separated children keep their existing layout. Re-formatting is idempotent (an emitted {" "} is recognized as whitespace on the way back in), and element-only children are left unchanged.
  • Fix: native list/any codegen gaps: In the native (LLVM) backend, list.pop(index) now honors the index (including negative) instead of always popping the last element; storing tuples in a list and popping/unpacking them round-trips correctly; a non-empty list literal with a boxed any element no longer collides with i64 lists (it gets its own jacval helper); and unpacking pop() of a bare list type-checks the same in native codespace as on the server (gradual any is treated as unpackable).
  • Fix: native container truthiness and silent acceleration failures: In the native (LLVM) backend, while <list> / if <container> now tests whether the list/dict/set is empty (its length field) instead of testing the always-non-null pointer. Previously such loops never terminated and ran pop() past the end into out-of-bounds memory (the source of the tag_of segfault during OSP kernel bring-up). Native acceleration failures for .na.jac modules, previously debug-logged and invisible, are now surfaced to stderr under JAC_NATIVE_DEBUG=1.
  • Fix: native backend decodes \x/\u/\U string escapes: The native (na) backend previously passed numeric and codepoint escape sequences (\xNN, \uXXXX, \U00XXXXXX, \N{...}, octal) through string literals literally, decoding only simple escapes like \n and \t. This broke the na ⊆ sv guarantee, since the same source produced different string bytes depending on the pathway. The native string-literal and f-string codegen now decode through the shared String.lit_value (the same ast.literal_eval-based decoder the sv pathway uses), so every escape form resolves identically across pathways by construction.
  • Fix: native gradual any values round-trip faithfully: In the native (LLVM) backend, scalar and pointer values now box into, and unbox out of, the any (JacVal) representation through a single coercion seam, so passing a literal to an any parameter, reading a value out of a dict[str, any] local, and storing a tuple with an any element in a list then unpacking it all preserve the value instead of failing to compile or coming back as 0. Tuples containing an any element are now sized by LLVM's exact ABI layout, fixing the heap-overflow that corrupted them (the source of the tag_of segfault during OSP kernel bring-up).
  • Fix: edits to core compiler passes no longer silently ignored by stale caches: The bytecode caches are consolidated onto one content-addressed identity (a SEC_MODKEY section embedded in every JIR) validated by a single check shared by the module cache and the shipped _precompiled/ bundle. Editing a .jac, .impl.jac, variant, or style file now reliably invalidates both, fixing the case where a precompiled bundle validated only the head source hash (ignoring impl hashes) and then laundered stale bytecode into the module cache with a fresh mtime. The precompiled bundle no longer needs a manifest.json, the bootstrap cache stores plain marshalled code objects, and two debug escape hatches were added: JAC_REBUILD=1 (force recompile) and JAC_NO_PRECOMPILE=1 (skip the bundle). The JIR format version bump transparently regenerates existing caches.
  • Fix: Native backend leaked owned temporaries: the LLVM native code-gen never released the temporary it consumed at several reference-counting seams (for/comprehension iterables that are call results, in/not in operands, set.add elements, and call/method arguments), so move-generation-heavy native programs grew memory without bound. Each seam now releases owned temporaries, and the debug-only malloc_usable_size call was moved behind the runtime trace flag so it is no longer invoked on every free.
  • Fix: varargs/kwargs params dropped in client codegen: *args / **kwargs parameters now lower to a JS rest parameter (...args) in the ECMAScript target instead of being omitted from the signature, which previously left the function body referencing an undefined name. (jaseci-labs/jaseci#6466)
  • Fix: jac2js interprets string escape sequences: The ECMAScript (cl) target now decodes string-literal escapes (\n, \t, \xNN, quotes, backslashes, and implicit concatenation) via the shared lit_value decoder, matching the Python (sv) target instead of emitting the backslash verbatim (#6476).
  • Fix: overloaded functions imported through re-exports resolve correctly: Calling an @overload-ed function imported via a re-export chain (e.g. SQLAlchemy's create_engine) no longer reports a spurious missing-parameter error for calls that match a later overload.

Refactors#

  • Refactor: unified client build pipeline moved into core: The bun toolchain, Vite bundler, client config loader, jac→js bridge, and build orchestration now live under jaclang.runtimelib.client (one runtime, one bundler) instead of being duplicated inside the jac-client plugin. Downstream consumers import the bundler, JacClientConfig, and get_bun from jaclang.runtimelib.client rather than the old jac_client.plugin.src.* paths. (jaseci-labs/jaseci#6390)
  • Refactor: version-guarded symbols resolve through the type checker: Symbols declared under sys.version_info / TYPE_CHECKING guards (including version-specific typeshed overloads like zip.__new__) now resolve to the branch live for the running Python version.

Documentation#

  • Docs: sonner in the jac-shadcn component guide: The bundled component-selection guide now lists the sonner toast component. (jaseci-labs/jaseci#6467)

jaclang 0.16.0#

New Features#

  • Feature: Full Object-Spatial traversal runs in client (cl) code: cl now compiles and runs complete Object-Spatial programs in-process through the portable kernel, not just entry-only spawn: visit, edge connect (++>, +>:E:+>) and edge references ([-->], [->:E:->]), disengage, report, exit abilities, and subtype / tuple-trigger dispatch. A local-archetype walker runs locally; a server (sv-imported) walker spawned at root keeps the __jacSpawn RPC path unchanged. The emitted module carries a small in-memory graph runtime (nodes hold their incident edges) plus per-archetype dispatch descriptors, type tags, a subtype is_a table, and a client NoopHost.
  • Native C-FFI: System V AMD64 ABI lowering for by-value structs: Foreign (import from "lib") functions that take or return structs by value now follow the platform System V AMD64 calling convention instead of being handed to LLVM as raw aggregates. Structs are classified per the eightbyte algorithm (INTEGER to iN, SSE to float/double/<2 x float>, aggregates over 16 bytes via byval/sret), with nested structs flattened to their C layout. This fixes float-field math structs like Vector3/Camera3D (previously dropped fields or returned garbage) and removes a compile crash when a scalar integer return shared an LLVM type with a small struct's coerced type. x86_64 only for now; other targets are unaffected.
  • Native C-FFI: AArch64 AAPCS ABI lowering for by-value structs: Foreign (import from "lib") functions that pass or return structs by value now follow the AArch64 AAPCS calling convention on arm64 targets, alongside the System V support added for x86_64. Homogeneous float aggregates go in SIMD registers ([N x float] / [N x double]), other aggregates up to 16 bytes use general registers (i64 / [2 x i64]), and larger aggregates are passed indirectly and returned via sret, matching what clang/gcc emit. Also adds a JAC_NATIVE_TARGET environment override so jac nacompile can cross-compile for another target triple, and fixes the ELF linker emitting an empty interpreter path when cross-compiling.

Bug Fixes#

  • jac check --disable-error-code now suppresses diagnostics during compilation without mutating project config, and it consistently resolves direct codes, lint aliases, comma-separated values, and inline ignore tokens.
  • jac nacompile now produces working standalone aarch64 binaries instead of ones that crash at startup.

jaclang 0.15.6#

Breaking Changes#

  • Breaking: check removed in favor of assert: Test assertions are written with assert on every backend; a failing test assertion now raises a plain AssertionError.

New Features#

  • Feature: jac check --disable-error-code: Suppress selected diagnostic codes for a single check run (e.g. jac check app.jac --disable-error-code E1030 W2001). Tokens accept codes or lint kebab aliases (same as [check].suppress in jac.toml), including comma-separated values; config changes are restored after the run. Named after mypy’s flag (issue #6108 originally suggested --ignore).
  • Feature: Object-Spatial programs compile and run natively: The native (LLVM) backend now compiles and executes Object-Spatial constructs (walkers, nodes, and spawn) by routing the OSP runtime through a portable kernel and an OspHost seam (report sink and spawn commit boundary), with ability triggers emitted by the compiler instead of resolved via runtime inspect.
  • Feature: Client-called endpoints serve without an explicit entry import: A server endpoint reached only through a client module's sv import is now registered at serve time directly from the compile-time cross-boundary table, so the entry module no longer needs to re-import it. Both endpoint shapes are covered - def functions (/function/<name>) and walker archetypes (/walker/<name>) - and each keeps its own access level (a priv/prot callee stays auth-gated, secure by default). As a result an entry like littleX's main.jac can drop its to sv: endpoint import entirely. The compiler no longer force-type-checks .cl.jac modules to do this (boundary analysis reads the AST and symbol table, not resolved types), and the boundary-completion walk skips the built-in @jac/* framework runtime so it no longer leaks spurious type errors into client apps.
  • jac ai --ui call-detail view for the token-usage stream: Clicking a token-usage row opens a modal that reconstructs that model call - its full input conversation, the diff vs the prior call (the prompt-cached prefix is collapsed to a one-line note so the call-by-call context growth is obvious), and the output the call produced. Prev/Next step through the calls of the same phase.
  • jac ai --ui call-detail modal now shows the tool schemas: A call's tokens_in includes the tool JSON schemas (the API tools parameter), which are not messages, so the modal could not previously account for them. A collapsible "tools available" section now lists each tool's schema with a token estimate, so the count reconciles as messages plus tool schemas.
  • hasattr(x, "name") now narrows union types: Inside the true branch of if hasattr(x, "foo"), x is narrowed to the union members that declare foo; the else / not hasattr branch keeps members without it. Inherited attributes count, classes with __getattr__ are never filtered out, and a dynamic (non-literal) attribute name leaves the union as-is without erroring.
  • Feature: jac ai read-only fast path: A typed-bool classifier runs after the Plan phase to decide whether carrying out the request needs any file created or edited. Pure questions and read-only inspections now finish straight from Plan, skipping Build (and its wrote-nothing nudge loop) and QA, while anything that touches code still routes to Build, which stays the default so a misjudgement never silently skips real work.
  • Type variables are now inferred through function-typed arguments, so a generic like apply(f: Callable[[], T]) -> T resolves T from the function you pass in. This also lets @contextmanager / @asynccontextmanager methods be recognized as context managers, removing the false "cannot be used in 'with' statement" errors on with blocks that use them.
  • Feature: Module-level types are shared across codespaces: A module-level obj/node/edge/walker declaration is now shared across every codespace (sv/cl/na) - each backend's codegen materializes the shared types its own code references, so a program is declared once instead of redeclared per codespace. A declaration inside an na {}/cl {}/sv {} block still overrides the shared type for that target.
  • Bare-serve auto-caches reader endpoints: The dependency-free bare-serve client runtime now caches reader walker and function calls, dedups concurrent identical requests, and invalidates on writes and auth changes, matching jac-client.
  • Feature: JavaScript globals type-check in client code: Standard ECMAScript and browser globals such as Date, Math, JSON, and console now resolve in .cl.jac client code, and new(cls, ...) keeps the constructed type instead of collapsing to a bare object.
  • jac ai --ui activity column and per-call token counts: The activity feed now lives in its own resizable column to the right of the phase graph, so the workspace reads as three side-by-side columns (conversation, graph, activity) with the activity feed mirroring the conversation across the graph. The token-usage call-detail modal also gains a per-box ~N tok estimate on every message and tool-schema block, and its note now distinguishes calls that actually send tool schemas (Plan/Build/QA) from tool-less routing and classifier calls.
  • jac ai --ui phase-context inspector, graph pan/zoom, and agent runtime hardening: The phase inspector now shows the selected phase's captured context (system prompt, the kickoff directive, and the tool turns it accumulated) via a new agent_phase_context endpoint, and the phase graph is pannable (drag the background) and zoomable (scroll wheel). The agent runtime is also hardened: code intelligence clears its dirty flag only after a successful refresh, the --ui mutating endpoints are guarded against running-turn races (with a real bus lock serializing turn start), the read-only classifier falls back to its Build-biased default on error while terminal runs always reclaim the dev server and browser, file I/O is pinned to utf-8, and a leaked log file handle in serve() and the UI launcher is closed.
  • Feature: Short JSX return statements stay on one line: The formatter previously forced every return <Element/>; onto a second indented line. It now keeps the element on the return line when the whole statement fits the print width (e.g. return <p>Hello</p>; or return <Tag prop={x}/>;), and only breaks it onto its own indented line when the statement overflows the print width or the element has multi-line children.
  • Feature: Object-Spatial walkers run in-process in client (cl) code: A walker whose archetype and abilities are compiled into a client unit now runs locally through the portable Object-Spatial kernel when spawned at a local node (w spawn Node(...)), instead of issuing a __jacSpawn server RPC. The compiler emits the kernel, per-archetype dispatch descriptors, type tags, and a client NoopHost; abilities lower to kernel dispatch slots. Server (sv-imported) walkers spawned at root keep the RPC path unchanged. Entry-only spawn for now; visit/disengage/report and edge traversal follow.
  • Feature: TypedDict support in the type checker: Indexing a TypedDict with a literal key (doc['name']) now resolves to that field's declared type, and get/pop/setdefault return the precise per-key type. Subscripting an unknown key reports an error. Overload resolution also now discriminates on literal argument values, so Literal-keyed overloads pick the matching signature.
  • Feature: Literal[...] type annotations: Literal["a"], Literal[1, 2], and Literal[True] now resolve to literal types in variable, parameter, and return annotations. Assigning or passing a value whose literal differs from the annotation is reported, and overloads keyed on distinct Literal values select the matching signature.
  • Feature: Literal[None] and negative-int literal annotations: Literal[None] resolves to NoneType and negative integer literals such as Literal[-1, -2] resolve correctly, where both previously widened to an unresolved type.
  • Feature: npm package publishing via jac bundle --target npm: Client-side Jac libraries can now be published to npmjs.org, mirroring the wheel/PyPI flow. jac bundle --target npm reads jac.toml, compiles each client module to ESM JavaScript, generates package.json, and emits .d.ts TypeScript declarations (plus inline JSDoc), producing a reproducible .tgz that any JS/TS project can npm install. Modules that cross a server (sv) boundary are rejected; JSX/reactive modules get an @jaseci/runtime dependency wired in (and react/react-dom as peer dependencies when imported). --target npm-runtime packages the Jac client runtime as @jaseci/runtime, and --target all builds both the wheel and the npm tarball. Upload stays out of scope (npm publish), as wheels leave it to twine.

Bug Fixes#

  • jac bundle --precompile now fails fast on incomplete precompilation: Missing python3.X executables now emit a warning, finding zero interpreters is a hard error, and any per-version failure aborts the bundle - previously all three cases silently succeeded, shipping wheels with incomplete _precompiled/ content.
  • jac ai --ui no longer duplicates the conversation in every phase prompt: run_phase is a byLLM by function, which serializes its parameters into the prompt as text inputs. It took the phase's tools and conversation as parameters AND passed them via the by clause, so the whole conversation was sent twice (once as real messages, once as a Python repr) and the tool list leaked as object reprs. Tools and the conversation are now read from the agent runtime in the by clause, leaving only the directive as a prompt input, which cuts the first phase call from roughly 14.5k to 5.5k tokens (and more on later calls).
  • Fix: client-to-server endpoints registered without a re-import in main.jac: a sv import inside a .cl.jac module now records its client-to-server binding at compile time, so the def:pub / walker:pub endpoint registers correctly without a runtime walk or re-importing it in main.jac (previously returned a misleading 405).
  • Generic free functions now narrow to the argument's actual type: calling identity(d) where d is a subclass returns the subclass, so accessing subclass-only members no longer raises a spurious "cannot access attribute" against the bound.
  • Fix: formatter spacing for enum base types: the Jac formatter no longer inserts a stray space before the typed-base colon (enum X: int) or inside base-class parens (enum X(A, B)). DocIRGenPass.exit_enum now spaces these forms like archetype base classes instead of routing them through the generic kid branch, which had appended a trailing space after every kid and produced enum X : int / enum X ( A, B ).
  • Fix: try/awaiting/except JSX under bare-serve: Bare-serve now provides the JacAwaiting and client error-boundary wrappers the compiler emits for these views, so a bare-serve app using them no longer fails with an unresolved @jac/runtime import.
  • Loop bodies now correctly loop back in the control-flow graph: the end of a for/while body now returns to the loop header instead of falling through to the code after the loop, fixing spurious type-narrowing and unreachable-code results inside loops.
  • isinstance now narrows values typed Any: a guard like if isinstance(x, Foo) on an Any-typed value (e.g. an item from a bare list) now narrows x to Foo, so accessing the type's members no longer reports a spurious error.
  • Fix: def:pub endpoints of a jaclang-bundled app no longer return 401: A fullstack app bundled under the jaclang package root (e.g. jac ai --ui) is routed by the compiler into a separate internal program, so its modules never land in Jac.program.mod.hub. The server's access-level introspection looked the main module up only in that one hub, missed it, and left every def:pub endpoint auth-required. Module resolution for path-keyed structural lookups (the auth/manifest introspection and the web target) now goes through a single program-spanning accessor, JacProgram.find_module, that resolves a path across every program without recompiling. The client bundler keeps its own single-hub lookup on purpose: it produces output JS and must compile each module in its own program context.
  • Fix: generator-returning def:pub endpoints stream as Server-Sent Events instead of crashing: A def:pub function whose result is a generator (e.g. -> Iterator[dict], the shape jac ai --ui's agent_stream uses) is a stream, not a single value. The base server JSON-encoded it and raised "Object of type generator is not JSON serializable". It now detects a generator result and emits each yielded item as a canonical data: {json}\n\n SSE frame terminated by event: end, matching the framing jac-scale's gateway emits, so one client consumer works against either server.
  • Fix: sv import-bound endpoints of a jaclang-bundled app register instead of returning 405: A fullstack app bundled under the jaclang package root (e.g. jac ai --ui) is routed by the compiler into a separate internal program, so the cross-boundary bindings the static compiler records land in that hub, not in Jac.program.mod.hub. The serve runtime's cross-boundary endpoint collector and its endpoint-effects aggregation scanned only Jac.program.mod.hub, so every client-called server endpoint went unregistered and /function/* calls returned 405 with an empty UI. Both now union over JacProgram._search_hubs() -- the same program-spanning span find_module uses -- and the collector no longer early-returns when this program's own hub is the empty dict a bundled app leaves it. Completes the bundled-app resolution started in #6273 for the endpoint-registration path.
  • Server no longer prints a traceback when a client disconnects mid-response: A client that reloads, navigates away, or closes the tab drops the socket while the request handler is still reading or writing. The base handler only caught TimeoutError, so the resulting BrokenPipeError/ConnectionResetError/ConnectionAbortedError escaped into socketserver and printed an alarming traceback for a routine disconnect. JacRequestHandler now overrides handle_one_request to absorb those errors and close the connection quietly.
  • Spread arguments no longer trigger a false duplicate-argument error: mixing a */** spread with an explicit keyword for the same parameter (e.g. greet(**defaults, name="x") or f(*args, c=3)) no longer reports a spurious "Parameter 'X' already matched"; the spread still satisfies the required-parameter check.
  • with cm() as x now types x as the value the context manager yields (its __enter__ result) instead of the context manager itself, and async with is checked against the async __aenter__/__aexit__ protocol. Using the bound variable inside the block no longer raises spurious type errors.
  • The type checker now evaluates if TYPE_CHECKING: blocks as taken (and if not TYPE_CHECKING: as skipped), so types and aliases that libraries declare only for type checkers resolve correctly. Imports guarded by if TYPE_CHECKING: are now usable in annotations instead of resolving to Unknown.
  • Fix: Browser engine docstring typo: Corrected the subcommand name in the browser_engine module docstring.
  • Fix: Container and string method calls on untyped values now work in the JavaScript backend: Methods like .get(), .keys(), .append(), or .strip() on a value whose static type can't be proven now resolve to the correct operation at runtime instead of leaking the Python method name into the generated JavaScript.
  • A served :pub walker or function is no longer intermittently rejected with Unauthorized. Its public access level is now always recorded at compile time, so the server consistently treats it as auth-free.
  • Fix: CSS reset conflict with Tailwind v4 in JacCoder-generated projects: Removed the "resets" wording from jac-fullstack-patterns and added an explicit no-reset rule (with a plain-CSS-only exception) to both skill files.
  • Fix: cl/ES codegen honors Python negative-indexing and collection truthiness: The ECMAScript backend now lowers lst[-1] to count from the end (via .at() for known sequences or _jac.poly.getitem for unknown types) instead of emitting lst[(-1)] (which is undefined in JS), and boolean contexts (if/while/not/and/or/ternary) on a collection now test Python emptiness rather than JS truthiness (empty list/dict/set is falsy). Negative-index list assignment is routed through _jac.poly.setitem.
  • Fix: and/or-in-condition truthiness in cl/ES codegen: A condition that is itself an and/or expression (which lowers to _jac.poly.and_/or_ and returns an operand) is now wrapped in Python-truthiness coercion, so if x and y / while x and y treat an empty-collection result as falsy instead of relying on JS truthiness (where []/{} are truthy). Follow-up to #6320.
  • Fewer false type-check errors: the type checker no longer flags valid code that uses with blocks or that unpacks values like byte strings and text into separate variables.

Refactors#

  • Refactor: Centralize call-kind classification: Whether a call is a builtin, a class instantiation, a method, or a free function was decided independently in each backend by matching name strings, which meant a user symbol shadowing a builtin or class name (for example a local, or def len(...)) was silently ignored. Both the native and ecmascript backends now share a single resolved-symbol-based classifier, so user definitions correctly shadow same-named builtins.
  • Refactor: Centralize primitive-type classification: The resolver that maps an expression's type to a primitive name (str/int/list/...) was embedded in the ECMAScript codegen backend, and the native backend hardcoded each primitive's method-name set in inline tuples that duplicated the emitter contract. The resolver now lives in the shared type system (type_utils.primitive_type_name_of) and native primitive method dispatch derives each type's methods from its emitter, so both are defined in one place.
  • Interop: unified cross-boundary analysis into one IR-resident table: InteropAnalysisPass and WalkerEffectsPass are merged into a single front-end BoundaryAnalysisPass that runs once per module (server, client, and native) and produces one interop table (bindings, boundary types, endpoint effects, and per-symbol auth access). Codegen and the serve runtime now read that table instead of independently re-deriving the same facts; the runtime access AST-walk and EsastGen's standalone sv import resolver are removed.
  • Refactor: Fold literals via AST .lit_value instead of re-parsing: Compile-time literal value extraction was reimplemented by parsing raw token text in the native const/enum codegen and in the type-system enum helper, duplicating the AST nodes' own .lit_value getters. They now reuse .lit_value, which also fixes a compiler-wide bug where hex/oct/bin integer-enum values (e.g. RED = 0xFF) raised an error during type checking because they were parsed as base-10.
  • Refactor: Single @jac/runtime surface contract: The client runtime surface shared by bare-serve and jac-client is now declared in one place, so the compiler's import injection and the cross-runtime alignment check derive from a single source instead of separately maintained copies.
  • Refactor: Native-speed archetype field writes: Removed the per-write __setattr__ dirty-field tracking on archetypes, which was redundant with the value-hash diff the memory backends already use to detect changes, restoring C-level attribute stores across the runtime.
  • Refactor: Sv now uses the portable OSP kernel for sync traversal: JacWalker.spawn_call / JacWalker.visit / JacWalker.disengage / log_report now lower to osp_spawn / osp_visit / osp_disengage / osp_report (kernel) via make_sv_runtime, and the sync legacy walker engine (_execute_entries, _execute_exits, _visit_node_recursive) is deleted. The kernel was extended to sv parity (DFS recursion, post-order exits, queue-FIFO entries across branches, WalkScope.path, osp_visit insert_loc, opt-in visit-once via scope.ignores, osp_spawn takes starts: list for edge spawn). The async walker engine still uses the legacy CallState/OspHost path; the kernel migration for async is future work.

Documentation#

  • Docs: jac-shadcn component guide rewritten for the jac-super flow: The jac-shadcn-components guide now teaches quoted, hyphen-preserving import paths, the jac add/jac remove/jac retheme workflow, corrected component names (e.g. InputOTP), and accurate style/theme/font/radius options.

jaclang 0.15.5#

New Features#

  • CLI: jac bundle --precompile: jac bundle now accepts a --precompile (-p) flag that automatically discovers every python3.X on PATH, compiles .jac.jir bytecode in an isolated venv per version, and packages the results into the wheel, replacing the manual precompile CI step.
  • jac ai --ui streams every token live: The web UI renders the agent's output token-by-token as it is generated, including the tokens behind tool calls and the QA phase's next-step routing decision, alongside a stop control and a per-turn stats summary.
  • jac ai --ui adds resizable panes, a live ledger, and a phase inspector: The agent web UI now has drag-to-resize panels, a running ledger of changes and notes, and a per-phase inspector that shows each phase's prompt and activity, with its phase graph traced from the actual workflow.
  • jac ai --ui settings panel for model and credentials: A topbar gear opens a settings dialog to pick the model (with quick-pick presets) and supply an API key, base URL, temperature, and context window, applied live in the running session. The panel auto-opens when the active model needs an API key the session does not have, detected up front and reactively on an authentication failure.
  • jac ai --ui shows per-call token usage and collapsible tool outputs: The web UI replaces the raw token firehose with a live per-call token-usage stream (tokens in/out, cache, latency, and phase), and renders each tool's output as a collapsible, scrollable dropdown.

Bug Fixes#

  • Fix: Docstrings before declarations inside function bodies now work correctly: Writing a docstring before a class or function declaration inside a function body used to produce a W0060 warning and the docstring was ignored. It now attaches to the declaration the same way it does at module level.
  • Fix: Clear error when glob is used inside a function: Using the glob keyword inside a function body now emits E0063: 'glob' is only valid at module level instead of the misleading E0002: Missing ';' error. Previously, jac run would also silently execute wrong code in this case.
  • Fix: jac start --dev watchdog install on cross-version venvs: When the Jac CLI ran on a different Python minor version than the project's .jac/venv, _ensure_watchdog_installed would pip-install watchdog successfully but fail to import it, because get_venv_site_packages resolved the path using the host interpreter's version. The venv path is now resolved from pyvenv.cfg (with a lib/python* discovery fallback), the venv is added to sys.path after install, and the import is re-verified before HMR is reported as ready.
  • Fix: jac create without a name initializes the current directory: Running jac create with no project name previously scaffolded a new jactastic/ (or jactastic1/, jactastic2/, ...) subdirectory. It now matches cargo init / uv init behavior: it initializes the project in the current working directory and derives the project name from that directory. Passing a name (jac create myapp) still creates a subdirectory as before.
  • Fix: tuple unpacking in for loops now works in client code: A for (i, item) in enumerate(items) loop (and any tuple target, including nested for ((a, b), c) and starred for (a, *rest)) compiled without complaint but left the unpacked variables undefined at runtime in the browser, so lists rendered empty. The generated JavaScript now destructures the loop variable correctly.
  • Fix: skip in a client function body now lowers to an early return: skip is a Return-family keyword, but the client/ECMAScript codegen lowered it to JS continue; outside a JSX slot. That is illegal outside a loop and crashed the client build at runtime with no jac check error. It now emits a bare return;, matching the Python target and the documented semantics; the JSX-slot fragment-return guard is unchanged.
  • Fix: calling an async function (or sv import RPC stub) without await is now a type error: An async def call evaluates to a coroutine, not its return value, but the type checker modeled neither the coroutine wrapping nor await unwrapping, so tasks: list[Task] = get_tasks() (an un-awaited sv import call, which lowers to an async RPC stub on the client) type-checked clean and crashed at runtime with tasks is not iterable. Calls to async functions now resolve to Coroutine[Any, Any, T], await unwraps them back to T, and assigning or returning an un-awaited coroutine raises E1042 with a "did you forget await?" hint.
  • Fix: glob outside module scope is now rejected before execution: glob used inside a function, a with entry block, or a test block emits E0063 and now blocks code generation, so jac run exits without executing the invalid module instead of running it. glob remains valid at module level and inside cl/sv/na codespace blocks.

Refactors#

  • Refactor: Centralize the builtin exception hierarchy: The builtin exception hierarchy (the parent relationships behind except ArithmeticError catching ZeroDivisionError, etc.) was duplicated in three places: the native backend, the ecmascript backend, and the generated JS runtime. It now lives in a single source of truth in JacTypeRegistry, and the native matching, ecmascript matching, and the JS runtime exception classes are all derived from it.

Documentation#

  • Docs: jac-cl-components skill update: Adds a pitfall on inline lambda vs anonymous def in JSX handlers.
  • Docs: sv import await rules added to skill guides: sv import stubs are async functions -- items = fetch() assigns a Promise, not the data. Updated jac-fullstack-patterns, jac-cl-components, and jac-core-cheatsheet with the await rule and the two valid async contexts: async can with entry (fetch on mount) and async def handler -> None (handlers that call sv import).

jaclang 0.15.4#

Breaking Changes#

  • defview declarator removed: replace defview Name { ... } with def:pub Name() -> JsxElement { return <>{...}</>; } and use the unified JSX statement-slot form for the body.
  • obj Foo; obj Foo { ... } is now a hard error (E0077): Jac's separation model is decl/impl, not decl/decl -- module-wide symbol resolution means forward declarations are unnecessary. The pattern used to be silently accepted but produced an empty class scope that cascaded into spurious E1030 "no attribute" errors. To fix, either inline the body into a single obj/node/edge/walker/enum block, or split into a body-less declaration plus an impl Foo { ... } block (the legitimate decl/impl pattern stays valid since impl is an ImplDef, not an Archetype).
  • Breaking: remove no-op Jac.setup() and no-token request-context APIs: JacBasics.setup (empty), JacRuntime.set_request_context, and JacRuntime.clear_request_context are gone; use push_request_context / reset_request_context with the returned token for any code that scoped a context to a server request.
  • Bare return; inside a JSX slot body is now rejected (E2020): the legacy slot early-exit form is replaced by skip;. E2020's scope broadened from "in template loop" to "anywhere in a slot body" with a message pointing at skip;. E2019 (value-form return expr;) and E2021 (break; in slot loop) messages updated to also mention skip;. skip; is no longer rejected inside slot for-loops -- inside a loop it now exits the whole slot (abandoning remaining iterations and later JSX); use continue; to skip a single iteration.

New Features#

  • Feature: S3-Compatible Storage Interface: Extended the base Storage interface with an abstract get_url() method to support public and pre-signed URL generation for all storage backends.
  • Feature: LocalStorage URI Support: Updated LocalStorage to support the new get_url() interface, returning file:// URIs for local assets.
  • Publish: PyPI classifiers support in jac bundle: The [project] section in jac.toml now accepts a classifiers list. Declared classifiers are emitted as Classifier: headers in the wheel's METADATA file, enabling proper PyPI discoverability, license badge display, and search filtering.
  • JSX statement slots: {...} in JSX child position now accepts either a single expression or a statement-form body whose control flow (if/for/while/match/switch/with/try) yields JSX children directly, with return; as an early-exit guard.
  • unsafe_html ambient builtin: unsafe_html(x) marks a string for raw-HTML rendering, lowering to dangerouslySetInnerHTML on jac-client and innerHTML on bare-serve.
  • E20xx/W2019 apply to JSX slot bodies: the slot-body rule diagnostics (value-returning return, return/break/skip inside template loops, keyless JSX in a while loop) now target the unified slot scope.
  • awaiting clause on try: A new clause -- try { ... } awaiting { ... } -- names the JSX (or value) to use during the dispatched-but-not-joined window of any async work inside the try body. On the cl target the view-lowering pass wraps the slot in <JacAwaiting fallback={...}>{...}</JacAwaiting> from @jac/runtime (a React.Suspense shim); on sv and na the awaiting body is dropped with a new W2020 warning until the streaming-SSR and native-thread lowerings land. finally alongside awaiting is rejected with E2022.
  • QueryPlan IR + capability-driven planner: jaclang.runtimelib.query_plan defines a typed QueryPlan/HopSpec IR with needs()/is_trivial(); jaclang.runtimelib.planner picks a strategy (pushdown → mixed → ordered → floor) against any backend that declares capabilities(). Set JAC_QUERY_TRACE=1 for per-call strategy logging.
  • Memory.capabilities() + Memory.execute_plan(plan) protocol: backends opt into native query pushdown by declaring capabilities (type_pushdown, field_pushdown, id_in, slice) and implementing execute_plan. Default impls return empty so every existing backend keeps working unchanged; TieredMemory delegates to L3 and promotes hits into L1/L2.
  • Memory.query_by_type(name, field_filter=None) convenience: ergonomic wrapper over execute_plan available on every backend; returns empty for backends without native pushdown so callers see a uniform API.
  • {name} JSX attribute shorthand: <Box {title} {count} {onClick}/> is now sugar for <Box title={title} count={count} onClick={onClick}/>. The parser recognises { followed by a single bare identifier and a closing } as a shorthand; the resulting JsxNormalAttribute carries an is_shorthand flag so the formatter round-trips the source verbatim. Explicit dict-spread ({**expr}) is unaffected.
  • E2024: has-field declarations inside a JSX {...} slot body are now rejected. A slot body is a statement template that re-runs on every render; declaring reactive state there would compile to a conditional useState and violate React's rules of hooks. Declare has-fields at the enclosing component scope (the def -> JsxElement body) instead.
  • W2021: for loops in JSX slots now warn when their body emits JSX without a key= attribute, matching the existing W2019 for while. Closes the long-standing recursion gap in ViewLowerPass._check_body (issue #6082 item 2). Annotate one child of each iteration with key= to recover stable identity across re-renders.
  • skip; for JSX slot early-exit: skip; inside a {...} JSX slot body is now the canonical slot early-exit -- the view-lower pass rewrites it to the same return <>{acc}</>; fragment-return shape that the legacy bare return; used to lower to. Reads correctly ("skip the rest of this slot's accumulator") instead of misleadingly suggesting a function-exit.
  • E2023 for redundant {...} slot wrapping inside a slot body: writing {if cond { <X/> }} inside an already-slot-mode body (the common copy-paste mistake when moving conditional render from JSX-child position into a slot body) now produces a single targeted diagnostic with the fix written out, instead of a cascade of E0001/E0002 parse errors. The parser consumes the redundant braces and parses the inner statement so the rest of the file still parses cleanly; the formatter refuses to rewrite source while error_count > 0, so the file on disk is untouched.
  • Feature: jac code structural code-intelligence command: A new jac code command group answers structural questions a text search cannot -- where a symbol is defined, everywhere it is used, its type, the project map, and which walkers traverse a node type -- emitting machine-readable JSON by default (or --text) so terminal-driven agents can query the compiler the way they reach for grep.
  • Native enums honor their declared type: Native compilation now treats an enum member as its enum type rather than a bare integer, and lowers .value and .name on direct integer-enum members.
  • Ref[T] has-fields lower to useRef (#6082 item 8): In client context (.cl.jac / cl {}), a has-field of type Ref[T] constructed with = Ref() lowers to React's useRef(null), and = Ref(initial) lowers to useRef(initial), mirroring how has count: int = 0; lowers to useState(0). useRef is auto-imported from react, the field is non-reactive (no setter, no useState), and the JSX ref={...} attribute wires the handle to the DOM node. A new ambient Ref[T] type gives .current a real type (T | None), closing the long-standing .current type-stub gap. Client compilation now always runs the type checker (inference-only) so the lowering is driven by the field's resolved type.
  • E2025: a Ref[T]-typed has-field declared without a Ref(...) construction is rejected. A bare has r: Ref[T]; has no ref object to hold, so .current would never be defined; write = Ref() (DOM ref) or = Ref(initial) (value ref) instead, like every other has-field carries a value.
  • Feature: .style.css annex auto-scoping: A .style.css file sharing a base name with a .cl.jac module now declares scoped CSS classes. The compiler hashes each declared selector with a per-module digest, rewrites the CSS rule selectors and matching JSX className/class literals to the hashed form, leaves :global(...) selectors verbatim, and emits the rewritten stylesheet as Module.gen.css plus a side-effect import "./<base>.css";.
  • Feature: W1037 now flags typing.Any annotations: The explicit-Any warning (W1037), previously limited to the Jac any keyword, now also fires on typing.Any used in annotations: on parameters, return types, fields, assignments, and nested forms like list[Any] or Any | None. typing.Any is matched by resolved type identity against the prefetched Any class, so aliases such as from typing import Any as A are caught while a user-defined class named Any is not. This closes the silent loophole where importing Any bypassed the warning that the any keyword triggers.
  • except arms on a slot try/awaiting: On the cl target, a JSX slot try { ... } awaiting { ... } except { ... } now lowers to a <JacClientErrorBoundary fallback={...}> (the error fallback) wrapping the existing <JacAwaiting fallback={...}>{...}</JacAwaiting> (the loading fallback). Both components are auto-imported from @jac/runtime, where JacClientErrorBoundary re-exports react-error-boundary's ErrorBoundary -- the same boundary jac-client installs at the app root. Previously ViewLowerPass returned early once it built the <JacAwaiting> wrapper, so the except arms were parsed and then silently discarded; the authored error UI never reached the runtime (issue #6082 item 3). The except bodies are concatenated in source order into the boundary's fallback; per-type dispatch and the optional except ... as <name> binding are not modeled, matching the app-level boundary's behavior.
  • Feature: Native File has a central type model: Added na_builtins.pyi, a native-scoped ambient stub modeling the runtime File type (and the native open() -> File). The TypeEvaluator loads it like the JSX dom_types.pyi stub but, instead of merging it into the global builtins, resolves its names only inside .na.jac modules via a native-context-gated lookup -- so File/open never leak into regular .jac code. File-typed annotations now carry a real ClassType("File") (previously UnknownType), and NaIRGenPass._lower_class_type lowers File to its runtime struct on demand. This removes the last name-based fallback in _resolve_jac_type for native code (the residual drops to zero hits across the native suite), unblocking the #6119 capstone. Part of #6114 / #6049 section B.
  • Feature: jac ai reports generation throughput: The per-turn stat line now shows generation speed in tokens per second (tok/s), computed from the turn's completion tokens over its wall-clock duration, alongside the existing token totals.
  • Feature: Walrus operator in native compilation: The := walrus operator now lowers in the native (LLVM) backend, binding a name inside an expression (such as an if/while condition) and evaluating to the assigned value.
  • Feature: Pipe-forward operator in native compilation: The |> pipe-forward operator now lowers in the native (LLVM) backend, desugaring lhs |> f to f(lhs) (with a tuple left-hand side spreading into positional arguments).
  • Feature: C-style iter-for loop in native compilation: The for <init> to <cond> by <step> { ... } loop now lowers in the native (LLVM) backend, with break and continue honoring the step (continue still advances the counter).
  • Feature: Nested function definitions in native compilation: A def nested inside another function now lowers in the native (LLVM) backend (including self-recursion and module-global access). Closure capture of enclosing-function locals is not yet supported and is rejected at compile time.
  • Feature: Match statements in native compilation: match now lowers in the native (LLVM) backend for value, singleton, wildcard, capture (as), and OR patterns, plus case guards. Structural patterns (sequence/mapping/class) are not yet supported and are rejected at compile time.
  • Feature: async/await in native compilation (synchronous model): An async def compiles like a normal function and await e now lowers to the value of e in the native (LLVM) backend. Native has no event loop, so awaits resolve immediately; this is correct for sequential async code and does not provide concurrency.
  • Feature: flow/wait in native compilation (synchronous model): flow e and wait h now lower in the native (LLVM) backend, running sequentially: flow e evaluates e eagerly and wait h reads the result. Results match a concurrent run for independent tasks; there is no actual parallelism.
  • jac browse headless browser command: Drive a headless Chrome/Chromium over the Chrome DevTools Protocol to navigate, interact with elements, snapshot the accessibility tree, and capture screenshots.
  • Feature: property-to-native auto-lint (W3043): The auto-linter now rewrites Python-idiomatic @property / @x.setter / @x.deleter declarations into native has x: T { getter; setter(value: T); deleter; } accessor blocks, coordinating the matching impl Cls.x bodies into impl Cls.x.getter/.setter/.deleter. The bootstrap transpiler (jac0.py) also learned native property syntax, so the accessor form now works throughout jac0core as well. jaclang's getters/setters have been migrated to the native form codebase-wide.
  • jac ai drives a built-in browser: The coding agent can open a headless browser, read pages, and exercise user flows through built-in browse_* tools, with no external browser CLI to install.
  • jac ai runs in Plan, Build, and QA phases: The coding agent now works through distinct planning, building, and QA stages, each with its own focused tools, and shows generation speed (tokens/sec) for each step.
  • Feature: Cross-run structural index cache: CodeIntelligence now persists a flat, parse-only structural index (archetypes, fields, signatures, and walker entry points) to a SEC_SYMINDEX JIR section, so jac code map/walkers serve the project map on a cold start without re-parsing. The cache is keyed by source content hash, invalidating per-module on edit.
  • Feature: Lazy code-intelligence load: CodeIntelligence(lazy=True) builds only the cheap structural index up front and defers the full type-checked pipeline until the first symbol or diagnostic query that needs it. The jac ai agent uses this so project_map and walker queries return immediately, paying semantic-analysis cost only on demand.
  • jac ai --ui web mode with live phase-graph visualizer: The coding agent can now run in a web UI that streams the agent's Plan/Build/QA progress in real time, with a live phase graph, activity feed, conversation view, and stats bar.

Bug Fixes#

  • Fix: with root entry in walker abilities now raises an error: Writing root (lowercase) instead of Root (uppercase) as a walker ability trigger used to compile without any complaint but the ability body would never run at runtime. The type checker now emits E1116 pointing directly at the keyword, covering bare root, union form Root | root, and tuple form (Root, root). At runtime, using an archetype instance as a trigger annotation now raises a clear TypeError with a hint pointing to the correct class.
  • Fix: clear error message when jac.toml is malformed: A TOML syntax error in jac.toml previously surfaced as multiple misleading "Install the missing package" plugin-load warnings. read_toml_with_encoding now wraps TOMLDecodeError as a typed MalformedJacTomlError carrying the file path, so every caller can produce an actionable error. import jaclang and get_disabled_plugins log a single stderr warning and continue (notebooks, scripts, and jac format keep working). The jac CLI's _load_project_config catches the typed error and exits with a red banner pointing at the file plus common-cause hints.
  • Fix: SqliteMemory.recover_all now processes nodes before edges, & warns when a re-link target is missing: Quarantine recovery previously iterated in undefined DB order -- if an EdgeAnchor was restored before its connected NodeAnchor, the re-link step silently no-oped and left data.edges empty even though both rows were nominally recovered. The batch is now sorted so every NodeAnchor is written back first. An explicit logger.warning is also emitted when the target node is absent from live anchors, giving operators a clear signal when recovery is partial.
  • Fix: jac check on .impl.jac files backed by .na.jac declarations no longer floods false E1032 errors: When a method implementation file (.impl.jac) has its class declaration in a .na.jac file, jac check failed to find the declaration, so self resolved as Unknown and every self.x access raised a spurious E1032: Type is Unknown.
  • Fix: T | None narrowing now propagates into JSX statement slot bodies: CFGBuildPass gained an exit_jsx_slot handler that sequentially links the slot body's CodeBlockStmts and wires the first body statement's bb_in to the slot's enclosing CodeBlockStmt. A name reference inside {for ... { ... }} / {if ... { ... }} now sees narrowing established by an outer if x is None { return; } guard, eliminating false-positive E1099 ("Cannot access attribute") inside slots.
  • Fix: registry-driven numeric kind for fixed-width primitives: JacTypeRegistry now records each primitive's NumericKind (IntKind, FloatKind, NotNumeric) and TypeEvaluator._assign_class consults it. Fixed-width primitives (i8..u64, f32, f64) are width refinements of int / float, so x: f64 = 3.14, i32 + i32, f32 -> f64, and i32 -> f64 all typecheck (the existing int -> float allowance generalises to any IntKind -> FloatKind); f64 -> i32 and other FloatKind -> IntKind moves stay banned. jac_builtins.pyi is now flagged as a builtin stub so its fixed-width classes satisfy is_builtin(). Drops native-fixture type errors 171 -> 48 across the 80 fixtures.
  • Fix: topology-index queries now see cross-root edges: Edges crossing root boundaries are mirrored into the target's per-root index so reverse queries find them, and plan_query bails out of multi-hop chains the moment a hop lands on a foreign-owned node (the natural edge walk takes over). Per-root index queries no longer return empty / undercounted sets for forward chains that cross into another user's subgraph.
  • Fix: type-checker import resolver prefers local files over typeshed: resolve_module (in modresolver.jac) used to check vendor/typeshed/stdlib/ before the importer's base_dir, so a local ast.na.jac next to the file doing import from ast { ... } was silently shadowed by stdlib/ast.pyi. The type checker then saw the wrong symbols (or none), producing <Unknown> cascades that masked real types. Now the resolver checks _candidate_from(base_dir, ...) first -- mirrors NaIRGenPass's struct-registration fix and unblocks fix_shadow_entry.na.jac for type-check. Closes the remaining shadow gap in #6058.
  • Fix: in-process clients no longer share app identity: ExecutionContext now owns its own base_path_dir and full_target_path, so two JacTestClient instances (or an embedded REPL / MCP host) in the same process can't poison each other's L3 SQLite file or surface stray anchors through allroots().
  • Fix: super.init(...) into a parent with an auto-generated constructor now type-checks cleanly: calls no longer raise a spurious unresolved-type warning.
  • Fix: Union / Optional with string type names no longer produces false type errors: String names inside Union[(...)], Union['T'], and Optional['T'] were treated as plain text (Literal["T"]) instead of the actual types they refer to, so valid return values and assignments were incorrectly rejected. The type checker now resolves these strings to in-scope types when possible; unresolvable names still degrade to Literal[...] as before.
  • Semantics change: Code that intentionally used Union[('Foo',)] to mean Literal["Foo"] (non-standard) will now resolve Foo as a type when it exists in scope. Use Literal['Foo'] for literal string types.
  • Fix: reassigning a module global from an .impl.jac body keeps its declared type: global x; x = ... in an implementation file no longer shadows the global with an untyped local, so int arithmetic on it type-checks correctly.
  • Fix: hyphenated bool CLI Args leaked a phantom X-dashed: True namespace key (#6115): --X and the auto --no-X companion now share the underscored dest, and HookContext.get_arg falls back to the underscored form. get_arg("dry-run") and get_arg("dry_run") both resolve cleanly.
  • Sibling JSX slots no longer crash on render: Two {...} statement slots in the same element's children now each lower to an accumulator IIFE that declares its own let binding. Previously every author-written slot reused the name __jac_view_kids, so the second sibling IIFE emitted a bare reassignment to a name scoped to the first (already-returned) IIFE, throwing ReferenceError: __jac_view_kids is not defined under ES-module strict mode -- any cl-target component with 2+ sibling slots crashed as soon as it rendered. ViewLowerPass now suffixes each slot's accumulator (__jac_view_kids, __jac_view_kids_1, ...) off the existing nested-slot counter.
  • Fix: Native lexer parity on non-ASCII source: The lexer now uses byte offsets and rune-aware scanning (the Rust/Go/Zig convention), so the native and Python lexers produce identical token values, positions, and diagnostics. Previously token positions drifted after any non-ASCII character, illegal non-ASCII characters were over-counted (one error per UTF-8 byte), and embedded NUL truncated the source. Also implements native bytes.decode(), which had returned an empty string. parse() now always routes through the native-capable lexer surface, so parses transparently use the native-compiled lexer wherever it has been built (set JAC_NATIVE_LEXER=0 to force the Python lexer).
  • Fix: multiple impl-separated property accessors in one class: impl Cls.prop.getter (and .setter/.deleter) now resolve per-property instead of colliding on a single class-level accessor symbol. Previously a class with more than one impl-separated native property silently left all but one getter unimplemented (empty pass body), since every getter registered under the same bare name in the class scope.
  • Fix: assigning a plain field no longer fails with a false "read-only property" error after a cached recompile: When an object had both a plain has field and a native getter/setter property, reopening or re-checking the file would falsely reject assignments to the plain field with E1005: Cannot assign to read-only property. A fresh compile was fine; only the cached run errored. The cache now correctly distinguishes a plain field from a property, so assignments work consistently whether or not the cache is warm.
  • Fix: jac format corrupts a decorated async archetype: the formatter moved async ahead of the decorator (async @decorator walker ...), producing code that no longer parsed while still exiting successfully. async now stays before the archetype keyword, so formatting is a no-op.
  • Fix: a null field no longer quarantines your entire node graph: A single stored null on a node field used to cascade into thousands of quarantined nodes and edges. The runtime now recovers the field to its declared default and loads the node normally; only nodes with no recoverable default are quarantined, and only that one node.
  • Fix: jac format merges an impl ability name into its with event clause: formatting a separated implementation such as impl Greet.start with Root entry { ... } dropped the space between the target name and the event keyword, emitting impl Greet.startwith Root entry, which no longer parsed while the formatter still exited successfully. The event signature now keeps its leading space, so the implementation stays valid and formatting is a no-op.
  • Formatter spacing for set literals and assign comprehensions: jac format now puts a space after commas in set literals and assign comprehensions, and wraps long set literals one element per line, matching how lists, tuples, and dicts are formatted.

Refactors#

  • Refactor: NaIRGenPass consumes the central type checker for type lowering: NA's parallel AST-walking type resolver (_resolve_jac_type) is replaced by a TypeBase -> ir.Type lowerer (_lower_type) that consumes Expr.type produced by TypeEvaluator. JacCompiler.compile() now auto-runs the type-check schedule for any .na.jac module before codegen (gated by a re-entrancy guard, with inference-only diagnostic suppression so native keeps its own type discipline). TypeCheckPass.exit_sub_tag persists .type on every annotation tag so codegen backends can lower it directly. _resolve_jac_type shrinks 364 -> 195 lines; _llvm_to_spec, _resolve_callable_type, and _resolve_func_ptr_type are removed. Native test suite passes 342/342; legacy AST-fallback usage across the 80 native fixtures drops from 2025 to 60. Follow-ups tracked in #6049.
  • topo_utils is now strictly index maintenance: plan_query, plan_query_ordered, batch_load_nodes, batch_load_nodes_by_ids, _extract_type_name_from_filter, and _filter_has_predicates are removed (~297 lines). Filter introspection moves into the planner. topo_utils writes the topology index on graph mutations; the planner reads it for queries. refs() in jac0core now drives the whole pushdown ladder through a single planner.execute_path call.
  • Refactor: Native builds are type-checked: Implicit native (.na.jac) compilation keeps its type diagnostics and a type error fails the build, holding native code to the same type discipline as the rest of Jac.
  • Refactor: Imported native modules load type-checked instead of raw-parsed: When NaIRGenPass._register_imported_struct_types hits a .na.jac import that is not already in the module hub, it now pulls it in through JacProgram.compile (which runs the implicit native type-check and hub-caches the result) rather than a raw parse. The raw-parse fallback produced a second AST that never ran TypeCheckPass, leaving .type = None on its annotations and silently routing imported f64/str/enum types to name-based resolution; the pipeline load makes them lower through the unified _lower_type path. prog._compile_options is saved/restored across the nested compile so subsequent native codegen keeps its skip_native_engine/aot_mode. Closes the last divergent un-type-checked path; the common hub-resident path was already covered by #6096 / #6050. Part of #6114 / #6049 section B.
  • Refactor: Native global-init type inference consumes Expr.type: NaIRGenPass._infer_global_init_type now lowers the initializer's central .type (populated by TypeEvaluator) through _lower_type before falling back to initializer-shape inference, so un-annotated native globals resolve through the same unified seam as annotated ones instead of an ad-hoc AST walk. The name-based fallback in _resolve_jac_type is retained as a documented safety net until imported native modules are type-checked (#6117) and native File is modeled centrally (#6118); collapsing it to the unified path is the tracked follow-up. Part of #6114 / #6049 section B.
  • Refactor: Native type lowering is fully central -- name-based fallback removed: With imported native modules type-checked (#6117) and native File modeled centrally (#6118), every .na.jac annotation now resolves through the unified _lower_type path. NaIRGenPass._resolve_jac_type is collapsed to the unified path plus an i64 default; its name-based fallback (the struct_types / type_map lookups) is removed after instrumentation showed it reached zero times across the native suite. The unreachable enum_types branch in _lower_class_type is also dropped (every enum name is registered in type_map too, which is checked first -- the same proof as #6120). No behavior change. Part of #6114 / #6049 section B.
  • Refactor: JSX {...} slots lower at cl codegen, surface AST stays faithful: The ViewLowerPass that desugared each JsxSlot body in place into the __jac_view_kids accumulator IIFE before analysis is removed. Its two jobs are split: the slot body-rule diagnostics (E2019-E2024, W2019-W2021, including the awaiting E2022/W2020) move to ASTValidationPass as a non-mutating walk over the author's body, and the accumulator IIFE plus the try/awaiting/except -> <JacAwaiting>/<JacClientErrorBoundary> wrappers are built as direct ECMAScript AST in EsastGenPass (the cl target only). Because the surface tree is no longer mutated, the JsxSlot.view_raw_src verbatim-source snapshot is deleted and the formatter/unparser round-trip slots from real tokens, so comments and formatting inside a slot body work the same as anywhere else. Each slot's IIFE still declares its own let accumulator with a unique name (preserving the #6128 sibling-slot fix). No behavior change to emitted output.
  • Refactor: Unify client endpoint-effect classification into one pass: The client SWR auto-cache's reader/writer {is_writer, touches} tagging was produced by two divergent classifiers (a lazy spawn-site analysis in EsastGenPass._analyze_body_effects, and a weaker runtime duplicate in ModuleIntrospector._analyze_endpoint_effects that always emitted touches: ["*"]), which could disagree and never classified imported-module walkers precisely. Both are replaced by a single authority, WalkerEffectsPass, that runs after symbol resolution and before EsastGenPass, classifying every walker archetype and public server-context def at its declaration via resolved symbols (no syntactic fallback). EsastGenPass now seeds its manifest from the precomputed effects, and the server's _load_manifest aggregates endpoint_effects across all compiled modules so imported-module endpoints are covered precisely. Removes roughly 340 lines of duplicated logic; full compiler and runtimelib suites pass (2878 passed, 57 skipped).
  • Refactor: Console facade/renderer split with a structural trust boundary: JacConsole becomes a capability/routing facade that owns terminal capability detection (use_color/use_emoji) and stream routing (_resolve_stream), composes its 15-method surface once, and delegates drawing to a swappable ConsoleRenderer (base renders ANSI). Caller data now flows as Span{text, style|None}/Content values that every renderer must emit verbatim, so markup/ANSI in paths, URLs, diagnostics, and source snippets is never interpreted (the #5995 injection class). Also fixes print(..., file=...) routing to arbitrary streams. Plugins customize output by overriding _make_renderer rather than re-implementing the surface.

Documentation#

  • Skill: jac-sv-multi-user: New jac guide skill for cross-user data sharing (ambient grant/revoke/allroots, per-user roles, access-level table); jac-sv-auth back-links to it.
  • Docs: jac-cl-components skill: Clarified JSX comment syntax - {#* ... *#} is only valid inside JSX element children; using it outside JSX or using JS-style {/* ... */} is a parse error (E0001).

jaclang 0.15.3#

Breaking Changes#

  • Breaking: defview is now a reserved keyword: defview introduces the new view declarator. The single-token name view is not reserved -- only defview is -- so existing variables, parameters, fields, and walker names called view keep working unchanged.

New Features#

  • Views: defview declarator: New defview declarator, sugar for def:pub Name(...) -> JsxElement with a statement-based template body, statement-position control flow (if/for/while/match/switch/with/try), and a bare return; guard. The parameter list is optional, so a view with no props can be written defview Name { ... }.
  • Views: dynamic JSX tags: <@expr /> resolves its element tag from an expression (an identifier, a dotted access, or a brace-wrapped <@{expr}>) to a host-tag string, a view reference, or a str | type value.
  • CLI: jac guide reference guides: The curated Jac reference guides now ship inside the compiler. jac guide lists every guide, jac guide <topic> prints one, --search filters by keyword, --json emits machine-readable output, and --export <dir> writes the guides as a Claude Code skills directory.
  • CLI: Diagnostics link to guides: jac check diagnostics now point at the relevant guide when a code maps to one (e.g. a type error prints run 'jac guide jac-types' for guidance).
  • CLI: jac create seeds AGENTS.md: New projects are scaffolded with an AGENTS.md that points AI coding agents at jac guide.
  • Feature: expr as Type cast operator: Added a first-class, type-erased cast. value as Type evaluates to the operand at runtime and takes the named type statically (the same semantics as typing.cast, with no import). It gives an explicit escape hatch for re-typing an Any source, such as result.reports[0] as list[TweetView], without tripping the strict-gradual E1001. with/except keep their own as alias; parenthesize a cast used in those positions.
  • Feature: Type-check JSX props-bag components: A component declared with a single props parameter (the props-bag pattern that codegen already supports) is no longer rejected by the JSX prop checker. An untyped props: any bag accepts all JSX attributes; a typed props bag validates each attribute against the members of the props object's type. New warning W1052 is emitted once at the definition of a component using an untyped props: any bag, noting it opts out of all prop name and type validation and pointing at typed alternatives.
  • jac ai interactive coding agent: A new jac ai command launches an interactive Jac coding agent in the terminal, working with local or cloud byLLM models.
  • Feature: Freeze inferred local variable types at first binding: An unannotated local variable's type is now inferred and frozen at its first binding, enforced as if it had an explicit annotation; reassigning an incompatible type is now a type error (E1001). The None-sentinel pattern and the discard name _ remain exempt.
  • Type Checker: Warn on explicit any annotations: Explicitly typing a parameter, return type, field, or assignment any now emits a W1037 warning.
  • Feat: cl { } / sv { } / na { } braced blocks no longer deprecated: Module-level codespace braced blocks no longer emit the W0064 deprecation warning; the diagnostic code is removed. Braced blocks and to cl: / to sv: / to na: section headers are now both fully supported ways to mix codespaces in a file -- braced blocks are recommended because the braces bracket exactly the tagged region, while section headers are the flatter alternative for a file that is mostly one codespace.

Bug Fixes#

  • Fix: by llm() in async abilities now emits await acall_llm(...) instead of a blocking call_llm(...): The compiler and runtime hook interface are extended so async walkers get a non-blocking LLM call path. Sync walkers are unaffected.
  • Fix: Client in operator on dynamic values: in / not in on a value of unknown static type now produces Python-equivalent membership in transpiled client code instead of emitting a JavaScript in that could crash or return wrong results.
  • Fix: jac guide reference-guide inaccuracies: Corrected jac check-verified errors in the bundled guides -- a jac-sv-persistence example that failed type-check (E1002), a jac-types Any example that contradicted its own pitfall, and stale claims in jac-core-cheatsheet, jac-walker-patterns, jac-node-edge-patterns, and jac-scaffold. Guides now omit -> None on void functions (W3037). A new test_guide.jac test extracts every ```jac example and runs it through the compiler, so guide examples can no longer drift from the language.
  • Fix: Walker effect classification follows helper calls: The reader/writer classifier used for client-side cache invalidation now recurses into def/ability helpers, so a walker whose graph mutation lives in a helper is correctly classified as a writer instead of a reader (which previously left the client cache stale until a full page reload).
  • Fix: cascade-quarantine dangling edges on schema drift: When a NodeAnchor's archetype becomes unresolvable (e.g. a node type is removed between deploys), SqliteMemory now also quarantines every connected EdgeAnchor and strips those IDs from the source node's data.edges, preventing permanently corrupt traversal state. Recovery (recover-all) re-links edges back to their source node, fully restoring graph connectivity.
  • Fix: jac.toml writes no longer corrupt or misroute: Saving jac.toml (via jac add/remove/update, jac config set/unset, or jac plugins) is now handled by a single canonical TOML serializer, so authors/maintainers inline tables round-trip correctly, deeply nested tables like [dependencies.npm.dev] are preserved, and no stray empty [plugins] table is emitted. Mutating commands now warn before writing to a parent project's jac.toml when the current directory has none of its own.
  • Fix: remaining jac guide reference-guide inaccuracies: Follow-up QA pass correcting seven claims verified wrong against the compiler -- jac-cl-auth (jacSignup returns an always-truthy dict, so if not signup_result never catches failure), jac-sv-endpoints (a plain def is a registered endpoint, not an internal helper), jac-cl-styling (cn() is variadic), jac-npm-packages (jac-shadcn ships only clsx/tailwind-merge/tw-animate-css), jac-types (lowercase any is the gradual type, not capitalized Any), jac-impl-files (abs is unenforced -- a missing impl silently returns None), and jac-by-llm (glob llm is an optional override). All guide examples still pass test_guide.jac.
  • Fix: localStorage/sessionStorage now type-check in frontend Jac: The type checker had no declaration for the browser Web Storage globals, so they resolved to Unknown and every .getItem/.setItem/.removeItem access raised a spurious E1032. Added a Storage ambient class plus localStorage/sessionStorage globals to dom_types.pyi; a bogus attribute now raises the precise E1030 instead.
  • Fix: Native compilation now works on macOS: The native LLVM backend emitted calls to malloc_usable_size, a glibc symbol absent from macOS libSystem, so every native binary crashed at load with Symbol not found: _malloc_usable_size. The GC-debug emitters now select malloc_size on Darwin and malloc_usable_size elsewhere.
  • Fix: Packaged Jac wheels import without an __init__.py: A pip installed Jac package now resolves its .jac submodules automatically, so jac bundle projects need no hand-written __init__.py bootstrap.
  • Fix: Native function-scope global rebinding now matches Python/sv semantics: A bare function-scope assignment to a name that is also a module glob (e.g. counter = 5) rebound the module global in the native (.na.jac) pathway, while the sv pathway treats it as a new function-local per Python scoping rules, so identical source produced different behavior across pathways. Native now binds a function-local in that case; rebinding a module global requires an explicit global x;, matching CPython and the sv pathway. Native code that relied on the implicit rebind must now add global x;.
  • Fix: version-guarded stdlib symbols now type-check: Any stdlib symbol defined inside a top-level if sys.version_info >= (...) block in its .pyi stub (over 120 stdlib modules, including datetime.UTC, abc, hashlib, weakref, locale, …) was invisible to the type checker, so using it produced spurious W1051: Expression type could not be resolved warnings and E1053 parameter-type errors. These symbols now resolve to their declared types regardless of where they appear in the user code (value, annotation, function argument).
  • Fix: client runtime Any annotations now resolve as the Jac top-type: client_runtime.cl.jac and its impl used Any in 88 annotation sites but never imported it, so the checker treated every occurrence as an undefined name and cascaded W2001/W1051/E1032 across the file. Renaming to the canonical lowercase any builtin clears 107 W2001 warnings, 359 W1051 warnings, and 56 errors with no runtime change.
  • Fix: client runtime dict/list annotations now declare element types: 38 bare dict/list parameters and returns in client_runtime.cl.jac and its impl now spell out their element types, using dict[str, any] / list[any] for genuinely heterogeneous JSX prop bags, RPC payloads, and hook return tuples; dict[str, str] for CSS style maps; and list[str] where the site iterates strings. Clears all 38 W1036 warnings in the file with no runtime change.
  • Fix: any keyword now resolves to typing.Any at runtime: PyastGenPass.exit_builtin_type emitted literal any in Python, which evaluated to builtins.any (the iterable predicate) when tools like Pydantic BaseModel or FastAPI called get_type_hints() on an annotation. Disambiguation is now by AST position: a BuiltinType("any") that is the immediate target of a FuncCall (e.g. any(iter)) still emits builtins.any; everywhere else it emits Name("Any") with an auto-import from typing. With the runtime semantics fixed, 369 .jac files in the tree are normalised to the canonical lowercase any in type-annotation positions; identifier uses (enum members, .Any attribute access) and runtime-value uses (cast(Any, ...), else Any, isinstance(_, Any)) are preserved.

Documentation#

  • CLI: jac-packaging reference guide: A new jac guide entry covers packaging a Jac project as a wheel and publishing it to PyPI: jac.toml metadata, the package-directory layout, the __init__.py bootstrap that makes .jac modules importable after install, console-script entry points, jac bundle, and twine.

jaclang 0.15.2#

Bug Fixes#

  • Native: Bug Fixes: Fixed multiple bugs in the jac-native AOT/JIT compiler, including symbol-collision detection at link time (E5026) with promotion of import compile/link failures from warnings to errors (E5024/E5025), and type-safe handling of typed variable re-declarations (x: T = ...) where the new type differs from the previous alloca's type.
  • Native: Incremental Compilation: jac nacompile now caches per-module LLVM IR under .jac_ir/ keyed by source SHA-256 hash and skips recompilation of unchanged modules. A new --scrub flag wipes the cache for a clean rebuild, and jac run --no-cache now also clears .jac_ir/ directories under the cwd.
  • Fix: Cross-user root discovery: allroots() now returns every root across all memory tiers instead of only the requesting user's own root.

jaclang 0.15.1#

New Features#

  • Feature: Native property syntax with getter/setter/deleter accessor blocks: Adds first-class property declarations on has bindings: has x: T { getter; setter; deleter; }. Properties are pure computed accessors -- backing storage is always declared explicitly via a separate has _x: T = value; field and referenced as self._x from accessor bodies. Supports dotted-impl bodies (impl Cls.x.getter, etc.), read-only enforcement when setter is omitted (E1005), and write-type checking against the property's declared type. Combining an initializer with an accessor block emits E0080; an empty accessor block emits E0081.

jaclang 0.15.0#

Breaking Changes#

  • Breaking: Strict Any semantics in Jac modules: In .jac files, Any can no longer be silently assigned to a declared non-Any, non-object destination. The rule applies at every "destination has a declared type" site (annotated assignment, has-var initializer, function argument, return, yield, edge-connection assign) and recurses into containers (list[any] -> list[T] errors via element-wise recursion). Inferred destinations and explicit any annotations remain permissive -- they are the user's escape valve. Any -> object and Any -> TypeVar stay permissive too, so print(x) and generic-bound calls keep working. Python modules (.py/.pyi) keep PEP 484 semantics. Migration: either type the source (typed walker has reports, typed Python stubs) or accept Any explicitly at the boundary (x: any = py_call();).

New Features#

  • Add: SSE-aware sv_service_call hookspec + streaming generator returns: The default sv_service_call impl now inspects the downstream response's Content-Type; when it's text/event-stream, the call returns a Python generator that yields parsed data: event dicts and terminates on the event: end framing (or raises RuntimeError on event: error). Lifecycle of the underlying httpx connection follows the generator (closing on exhaust, drop, or scope-exit). Pairs with a _finalize_call_response fix in runtimelib/server: the isgenerator check was on reports rather than result, so explicit generator returns from def:pub walkers/functions silently fell into the str() fallback and were never streamed. New runtimelib/sv_sse.jac exposes the consumer-side helpers.
  • Lint: W3042 Auto-Convert .map(lambda → JSX) to List Comprehension: jac lint --fix now rewrites verbose React-style .map() lambda patterns into Jac's native JSX comprehension syntax. items.map(lambda item: any -> any { return <li>{item}</li>; }) becomes [<li>{item}</li> for item in items]. A leading .filter() chain is also collapsed: items.filter(lambda item: any -> bool { return item.active; }).map(lambda item: any -> any { return <li>{item.name}</li>; }) becomes [<li>{item.name}</li> for item in items if item.active]. Only single-parameter lambdas are converted; two-parameter forms (e.g. lambda item: any, idx: int -> any { ... }) are left unchanged, as there is no reliable way to statically determine which parameter is the index. The rule is suppressible via # noqa: W3042 or map-lambda-to-comprehension in jac.toml.
  • Topology Index: O(1) Typed Lookups + MRO + Planner Bypass: Reimplement the topology index so type-filtered chain steps resolve in O(1) per filter via a per-source type-keyed lookup map (instead of scanning the full adjacency list), with MRO fan-out so parent-type queries ([-->[?:Base]]) return all subtype instances at zero extra cost. Bidirectional indexing (IN/OUT/ANY) shares the lookup path. The encoded blob stays the same compact size as before (only canonical edges + a small per-type MRO chain are persisted; the lookup map is rebuilt on decode). plan_query now short-circuits to a None return when every chain step has no edge-type and no node-type filter, so fully-unfiltered traversals ([node-->]) skip the index decode entirely and fall through to the natural edge walk. Single-hop typed queries see up to 51× speedup at low selectivity; 2-hop chains compose for 10×+ wins. Old version-1 blobs decode to an empty index and repopulate on the next mutation.
  • Feature: Curated typing constructs are ambient: Callable, Protocol, TypeVar, Generic, Iterable, Mapping, and similar annotation-only typing names resolve in user code without import from typing { ... }.
  • Feature: Lint redundant imports of ambient typing names: New W1103/W1104 warnings flag import from typing { ... } (and from collections.abc { ... }) of names that are now ambient -- e.g. Callable, Mapping, Protocol, TypeVar -- and recommend the lowercase any keyword in place of typing.Any. Driven by typing_ambient.pyi's __all__ so the lint stays in lockstep with the merged builtins. Aliased and * imports are skipped.
  • Feature: Type-check walker typed reports: Walker archetypes can now declare has reports: list[T] = []; to type their report channel end-to-end. New E1112 rejects non-list reports shapes and E1113 flags report X whose value type isn't assignable to the declared element type T. Untyped walkers keep the inherited list[Any] and stay permissive.
  • Feature: sv import supports walker:pub archetypes: A walker on the provider can now be imported across the sv-to-sv boundary; the compiler generates a stub class whose construction issues a POST /walker/<name> and rehydrates the response into a real walker instance with has fields and reports populated. Previously walker symbols silently fell through to a regular in-process import and call sites failed at runtime with <Walker>() got an unexpected keyword argument.
  • Feature: import type for annotation-only imports: A new opt-in import type from foo { Bar } form lowers to if TYPE_CHECKING: from foo import Bar in the generated Python, breaking circular imports between modules that reference each other only in type signatures.
  • Feat: Storage-layer pushdown for sliced edge-ref traversals: [-->][?:T][a:b] now bounds the storage-layer load to the sliced subset instead of materializing every neighbor first. The compiler fuses the trailing IndexSlice into a slc= kwarg on the emitted Jac.refs(...) call; the runtime hands the slice to a new plan_query_ordered planner that walks the topology index's insertion-ordered adj_list, applies type filters via the existing buckets, and returns the bounded ordered UUID list. The persistence backend is then asked only for those UUIDs via batch_get. Phase 1 covers single-hop chains with type-only filters; multi-hop and predicate-filtered chains fall back to the existing path with a post-load Python slice (semantics unchanged). On a 2,000-neighbor graph, [-->][?:Lead][0:50] drops from 4,400 SQLite fetches / 710 ms to 50 fetches / 14 ms (52x).
  • Feature: --extras flag for jac install: Optional dependency groups defined under [optional-dependencies] in jac.toml can now be installed directly via jac install --extras <group> (short: -x). Works for both editable (-e) and venv installs. Self-referencing group entries (e.g. "pkg[all]" = "*") are expanded transitively via BFS; third-party extras like testcontainers[mongodb,redis] pass through to pip unchanged. Resolution logic lives in the new DependencyInstaller.get_extra_package_specs() method.
  • Feat: Multi-hop slice pushdown for edge-ref traversals: Extends the single-hop slice-pushdown from #5912 to multi-hop chains. [r-->-->[?:T]][0:50] now resolves non-final hops via the set-based resolve_chain and drives the final hop through the existing resolve_chain_ordered adj_list walk, so storage is asked only for the sliced subset instead of materializing the full type-filtered neighbor set. The plugin contract is unchanged (storage still sees only batch_get); the enrichment lives entirely in the topology index layer so the optimization stays plugin-agnostic across Memory backends. Predicate filters still bail (Gap 2 of #5913 remains open). Addresses Gap 1 of #5913.
  • Feature: Portable new(...) ambient builtin: A new ambient builtin new(Cls, ...args) is now available without import in every codespace. On the server it returns Cls(*args); in cl blocks the compiler lowers the call to Reflect.construct(Cls, [args]) in the generated JavaScript. This lets the same call site work whether the surrounding code targets Python or JavaScript, and replaces the previous client-only Reflect.construct(...) idiom. Diagnostic E0012 now recommends new(target, ...args) instead of the bare Reflect.construct(...) spelling, and the AST validation pass no longer rejects new as a standalone identifier.
  • Feature: Expose pip install flags via jac install: Adds --force-reinstall, --no-cache-dir, --pre, --dry-run, --no-deps, --quiet, and --prefer-binary flags to jac install, passing them through to the underlying pip invocation. --upgrade remains hardcoded in DependencyInstaller.install_package, preserving existing upgrade behaviour across all commands. Internally, DependencyInstaller now accepts an install_flags: list[str] field for additional pip flags.

Bug Fixes#

  • Fix: CLI registry preserves extensions across higher-priority command replacement: When two plugins register the same command name and the higher-priority one wins, pre_hooks, post_hooks, and extra_args registered via extend_command on the original spec are now carried over instead of silently dropped. This unblocks cases like jac setup mobile, where jac-client's _handle_setup_target was being wiped by jac-scale's setup re-registration.
  • Fix: SqliteMemory dirty-field tracking prevents concurrent walker data loss: sync() now tracks changes at the individual field level rather than comparing whole archetypes. Only fields that actually changed since the last DB load are written back, so two walkers modifying different fields on the same node no longer clobber each other. In-place container mutations (e.g. list.append) are also detected correctly. As a side effect, read-only walkers no longer trigger spurious write-access checks or access-denied log entries during synchronization.
  • Fix: Type narrowing through subscript access: Resolved a false-positive type error where items[0].attr was not narrowed after an is not None or isinstance guard on items[0].
  • Fix: cast() with a union target type: Resolved a false-positive type error where cast((A | B), x).attr was rejected because cast returned class types instead of instances for union targets.
  • Parser: Actionable diagnostics for malformed typed connection operators: Replaces generic cascade errors with two precise codes (E0027, E0028) for typed connection operators (+>: / <+:) missing their closing token. Error recovery via synchronize() suppresses cascade noise.
  • Fix: walker has fields with mutable defaults no longer crash request validation: walkers like has results: list[T] = [] were unusable over HTTP because the dataclass _HAS_DEFAULT_FACTORY sentinel leaked into the Pydantic body schema; introspect_walker now resolves the factory and returns the actual default.
  • Fix: align bare-serve @jac/runtime with React Router v6 surface: Bare-serve's runtime declared a hand-rolled router (Route(path, component, guard), Link(props: dict), createRouter([...])) while jac-client re-exports React Router v6 (<Route element>, <Link to>, <Routes>, ...). jac check correctly rejected valid React Router JSX against bare-serve's stale stubs even though jac-client rendered the same code fine; following the stub's advice (<Route component={...}/>) compiled clean but rendered a blank page. Bare-serve now natively implements the React Router v6 surface (Router, Routes, <Route path element>, <Link to>, Navigate, Outlet, plus useNavigate/useLocation/useParams/useRouter) over the existing signal-based reactive context, with no React/Vite/Bun dependencies. The legacy createRouter/RouteConfig/function-style Route(path, component, guard) are removed; user code with router JSX is now portable between bare-serve and jac-client unchanged. jacSignup also gains a profile parameter and the sync jacSpawn wrapper is added so apps written against either runtime work in the other without per-call edits. A new tests/runtimelib/test_runtime_surface_alignment.jac parses both client_runtime.cl.jac files and asserts bidirectional alignment of the routing/hooks/walker/auth surface, so future drift fails CI.
  • Fix: sv import calls with kwargs no longer drop named arguments: calling an sv imported def:pub server function from .cl.jac client code with kwargs (e.g. await greet(name=x)) silently compiled to __jacCallFunction("greet", {}): EsastGenPass collected kwargs into props then immediately reassigned props = [] and rebuilt the RPC args object from positional arguments only, so the runtime received an empty body and double-wrapped the value, returning a 422 from the server. The call-site rewrite now resolves positional and keyword arguments through a single ordered (name, expr) pass with one place applying __to_wire() boundary wrapping, so await greet(name=x), out-of-order kwargs, mixed positional+kwarg, and **dict spread all compile to the same shape positional calls produce. New diagnostic E5080 rejects the duplicate-argument case (greet(x, name=y)) at compile time, mirroring Python's TypeError: got multiple values for argument. Upstream KWPair handling also now lowers **dict to es.SpreadElement instead of a Property keyed by the literal 'key', so the spread works for every call site, not just the server-call rewrite.
  • Fix: jac bundle fails with "No bytecode found" due to root keyword conflict: Renamed the local variable root to proj_root in builder.impl.jac, metadata.impl.jac and sources.impl.jac. root is a reserved Jac keyword (graph root node) and its use as a plain variable prevented bytecode generation, causing jac bundle to crash on import. Also corrected the set_config signature to accept JacConfig | None, matching its actual usage.
  • Fix: JavaScript keyword argument lowering: Corrected ECMAScript generation for keyword arguments, defaults, method calls, and **kwargs spreads.
  • Fix: language server refreshes cross-file diagnostics when a dependency changes: an open consumer file's diagnostics no longer go stale when an imported module is edited; JacProgram now mtime-validates its hub on every lookup and the LSP fans out re-checks to open consumers when a dependency receives a did_save/did_change.
  • Fix: language-server diagnostics no longer drop after a debounce-driven type check: in _do_type_check, the defensive cache eviction inside _fanout_dependents ran after the primary task was queued, racing with the dispatcher worker. When the worker won the race, invalidate_module wiped the IR entry the worker had just written into mod.hub, and the file's diagnostics disappeared. Eviction now runs before the task is queued so the worker's write is final.
  • Fix: type checker now narrows T | None after ==/!= against None: classify_condition only recognized is/is not None and let the equality forms fall through to the literal-eq/ne branch (which excludes Null), so if x != None { x.attr } produced spurious E1099 / E1096 / E1097 errors while if x is not None { x.attr } worked. The same Null-on-the-right CompareExpr branch now also accepts Tok.EE and Tok.NE, reusing the existing NONE_IS / NONE_IS_NOT predicates so AND/OR combinators, assert and early-return CFG paths get symmetric narrowing for free.
  • Fix: type-checker validates visit against the runtime contract: visit accepted any list/tuple/set/frozenset target unconditionally, so visit ["a", "b"] and visit [1, 2, 3] slipped through with no diagnostic, while visit some_edge and visit list_of_edges were incorrectly flagged. The validator now matches JacWalker.visit exactly: a target may be a node, edge, ObjectSpatialPath, or a collection of nodes/edges. Element types of parameterized collections and members of unions are checked recursively; Any/Unknown and unparameterized collections still pass. Diagnostic E1094 text updated accordingly.
  • Fix: type-checker validates spawn operand types: spawn previously returned right_type unconditionally with no operand checks, so p spawn 42, p spawn NotAWalker(1), and n spawn 3.14 all passed silently. The checker now mirrors JacWalker.spawn's runtime contract: exactly one side must be a walker instance (or list[Walker]); the other must be a node, edge, or collection thereof. New diagnostics E1114 (no walker on either side) and E1115 (invalid spawn target) cover the gap. is_walker_type() is added on ClassType for symmetry with is_node_type/is_edge_type. Spawn now also returns the actual walker type regardless of operand order, so (W() spawn p).reports and (p spawn W()).reports are typed alike.
  • Fix: type-checker validates ability trigger archetype kind: can ... with X entry/exit triggers had no archetype-kind check, so structurally invalid abilities like walker W { can on with int entry; } and node N { can on with Place entry; } slipped through. The validator now mirrors JacWalker._execute_entries / _execute_exits: walker abilities must trigger on a node, edge, or Root; node/edge abilities must trigger on a walker. Tuple (A, B) form is checked element-wise; A | B union members must all be valid. Untyped with entry / with exit (fires on every step) and Any/Unknown remain valid. New diagnostics E1116 (invalid walker trigger) and E1117 (invalid node/edge trigger) cover the gap.
  • Fix: type-checker validates typed-edge in disconnect: del a ->:T:-> b previously accepted any expression as the typed-edge T because the existing E1098 check in get_type_of_binary_operation was gated on isinstance(expr.op, uni.ConnectOp) and DisconnectOp fell through. The check is extracted into a validate_conn_type helper and is now invoked for both ConnectOp.conn_type and DisconnectOp.edge_spec.filter_cond.f_type, so del a ->:NodeA:-> b and del a <-:NodeB:<- b now report E1098 ("Connection type must be an edge instance"). Connect's assign-compr validation stays gated on ConnectOp since disconnect has no assign-compr in the grammar.
  • Fix: type-checker validates filter-compr compare operand types: filter comparisons inside edge-ref filters ([a ->:E:field<x:->], del a ->:E:field<x:-> b) only validated field existence (E1033); the comparison operand types fell through with a TODO. The FilterCompr handler now delegates each compare to the CompareExpr case via get_type_of_expression(cmp), so each (left, op, right) triple is validated against the resolved field type and emits E1110 ("Operator not supported between types") / E1111 ("right operand must support __contains__") for incompatible operands. Behavior matches statement-level CompareExpr: == / != cross-type operands stay valid (Python returns False, not TypeError); only ordered comparisons <, <=, >, >= and in / not in are flagged. Closes gap 5 of #5885.
  • Fix: typed [edge ...] traversals propagate element type: [edge a ->:T:-> b] now resolves to list[T] instead of degrading to list[<Any>], so assignments into a list[T] no longer require casts. Closes gap 6 of #5885.
  • Fix: type-checker promotes undefined names in OSP-shape positions to E-class: undefined identifiers in ConnectOp.conn_type, DisconnectOp's typed-edge f_type, and EventSignature.arch_tag_info (a +>: NoSuchEdge :+> b, a del ->:NoSuchEdge:-> b, can ... with NoSuchType entry) previously emitted only the stylistic W2001 "may be undefined" warning, so structurally broken programs slipped through jac check. The static analysis pass now walks the parent chain of each unresolved name and, when it lands in one of those three positions, emits the new E2018 instead of W2001. Tuple (A, B) and union A | B trigger forms are validated element-wise; backtick-escaped names (e.g. `root) are skipped to preserve existing behavior. Closes gap 7 of #5885.
  • Fix: type-checker rejects unparameterized connect/disconnect operands: a ++> b (and the disconnect and typed-edge forms a del --> b, a +>: T :+> b, a del ->:T:-> b) silently accepted operands typed as bare list, tuple, set, or frozenset. The operand validator in get_type_of_binary_operation returned True for unparameterized collections because the runtime accepts any collection of nodes and the checker could not verify the element type, with a long-standing TODO to tighten this in strict mode. The validator now isolates the unparameterized case via a small unparameterized_connect_collection helper and emits the new E1118 before falling through to the generic E1096 / E1097 "must be a node instance" checks. Parameterized operands (list[Place], tuple[Place, Place]) and list literals whose element types infer to a node continue to pass; is_valid_node_operand no longer returns True for unparameterized list as a recursive element, so list[list] is also rejected through the same path. Closes gap 8 of #5885.
  • Fix: type-checker assigns bool result to del (DisconnectOp) instead of Unknown: a del --> b (and its typed a del ->:T:-> b and reverse-direction a del <-:T:<- b forms) used to fall through apply_connect_or_disconnect in operations.jac without setting a result type, so every disconnect site emitted a spurious W1051 "Expression type could not be resolved" and assignments like removed: bool = a del --> b failed with E1001 (Unknown to bool). The DisconnectOp branch now returns the prefetched bool instance, matching the runtime contract that Jac.disconnect(...) returns bool. Closes gap 9 of #5885.
  • Fix: clear diagnostic for connect/disconnect with missing right operand: connect (++>, +>:T:+>) and disconnect (del -->, del ->:T:->) operators that are missing the right operand previously surfaced a misleading Unexpected token in expression: ';' trail (canonical a del -->; form) or a Missing ';' + Unexpected token in expression: '-->' cascade (non-canonical del a-->; form). Both shapes now emit a single E0026 ("Connect/disconnect operator requires a right operand") with a help line pointing at the canonical syntax. Closes gap 10 of #5885.
  • Fix: non-UTF-8 jac.toml and source files crash with confusing tracebacks: tomllib.load() does a strict UTF-8 decode and crashed deep inside the parser when a jac.toml contained cp1252 or UTF-16 bytes; a new read_toml_with_encoding helper reads via the existing read_file_with_encoding fallback chain before parsing, and several unguarded open() / read_text() calls on user content now specify encoding="utf-8" explicitly so they no longer mojibake under the Windows cp1252 locale default.
  • Fix: jac install -e <path> now resolves the target package's jac.toml correctly when run from outside the package directory. Previously the config was discovered from the current working directory (and could be served stale from a process-level cache), causing the wrong project to be installed.
  • Fix: JS codegen scopes let per sibling block in compound statements: .cl.jac -> JS codegen leaked the if-body's let-tracked names into sibling else/elif branches (and analogously into except/finally for try statements), so the alternate branch emitted a bare x = ... against an undeclared name. Strict-mode ES modules then threw ReferenceError: x is not defined whenever the alternate path ran. The pass now follows an explicit scoping model where each compound statement owns one block scope (its primary body) and alternate-branch blocks are sibling scopes that finalize the primary on entry. Closes #5930.
  • Fix: JS-style new Foo(...) now emits the migration-friendly diagnostic: After new became an ambient builtin, JS-style new Foo(...) parsed as a bare new identifier followed by a stray expression and surfaced only the generic E0002 Missing ';' error. The parser now detects the <value> = new <atom> and bare new <atom>; shapes and emits E0012, whose template recommends new(target, ...args). Legitimate first-class references like f = new; are unaffected.
  • Fix: Docstring before cl/sv/na context prefix: A """docstring""" immediately before a single-statement cl, sv, or na prefix no longer fails with E0005; the docstring attaches to the inner declaration and the prefix tags its code context as before.
  • Fix: Pipe-forward/backward expressions emit spurious W1051: The type checker had no rule for |>, :>, <|, or <: and returned UnknownType for every pipe expression, both producing W1051 noise on each use and silently letting argument-type bugs through. Pipe operators now type as the equivalent function call (with the argument-side tuple unpacked, matching codegen), so 5 |> add_one resolves to int and "oops" |> takes_int reports the same E1053 as takes_int("oops").
  • Fix: type narrowing now reaches inside comprehensions: T | None narrowing established by an enclosing guard propagates into list/dict/set/generator comprehension iterators, conditionals, and output expressions, eliminating spurious E1099 false positives.
  • Fix: JSX block comments crash the parser: A {#* ... *#} slot inside JSX previously fell into the expression branch and surfaced as a syntax error, leaving no comment-only escape hatch inside markup. The lexer now recognises { followed by #* ... *# followed by } as a dedicated JSX_COMMENT token, the parser lowers it to a JsxComment AST node that round-trips through the formatter and emits nothing in the rendered output.
  • Fix: Native compiler reports unsupported features instead of silently dropping them: Constructs that the .na.jac / na {} pipeline cannot yet lower (such as match, C-style for, walrus :=, yield, await, flow/wait, walker event abilities, and nested function definitions) previously compiled clean and produced empty function bodies that returned 0 at runtime; they now raise a structured E5090 diagnostic at compile time.
  • Type checker: JIR cache load restores stub name_spec on literal AST nodes: JirReader now fabricates the stub Name that AstSymbolStubNode._init_stub builds in postinit for MultiString / FString / ListVal / SetVal / TupleVal / DictVal / {List,Dict,Set,Gen}Compr / IndexSlice / EdgeOpRef / FilterCompr / AssignCompr / JsxElement. Without it, DeclImplMatchPass._resolve_symbols_in_subtree raised AttributeError after a cache hit whenever a literal sat at the head of an attribute chain (e.g. "x".upper()), aborting type checking of the whole module.
  • Type checker: @<name>.setter no longer reported as a duplicate method: ASTValidationPass previously emitted E0076 for the second def of any @property + @<name>.setter (or .getter / .deleter) pair. The dup-detection skip is now semantic: it collects names declared with @property in the class body and only excuses a method whose decorator targets one of those names. Cross-property patterns like @bar.setter def foo (where bar is not a property) still raise when foo collides with another def foo, and two @x.setter def x for the same property are still flagged as duplicate accessors.

Refactors#

  • Refactor: esast call-lowering helpers: Consolidates duplicated logic between EsastGenPass.exit_func_call and _generate_sv_import_stubs. Adds _param_type_map_from_ability, _wrap_to_wire_if_boundary, _extract_return_type, _make_kwargs_spread_context, _wrap_return_with_boundary, and _try_lower_server_rpc_call. Fixes a latent bug where the sv-import wrapper generator silently dropped posonly and kwonly params from the JS function signature, the __jacCallFunction args object, and the __to_wire boundary check.
  • Refactor: unify [package] into [project] config section: Merges the separate [package] TOML section and PackageConfig into a unified [project] section and ProjectConfig, combining runtime fields (entry_point, jac_version) and publishing fields (authors, maintainers, keywords, include, etc.) in one place. Updates all jac.toml files and config parsing accordingly.
  • Refactor: InnerCompr becomes a CFG node (AST foundation): Adds the UniCFGNode mixin to InnerCompr so the control flow graph can model comprehension iteration and filter chains. AST-only foundation: no edges are wired yet, so CFG output and type-checking behavior are unchanged. Updates get_head/get_tail coalescer-stop tuples and flips is_cfg=True for InnerCompr in the JIR registry.

jaclang 0.14.1#

Breaking Changes#

  • Breaking: Identity-based auth payload for /user/register and /user/login: The built-in HTTP server's auth endpoints now require {identity(ies): {type, value}, credential: {type, password}}, matching jac-scale's shape. Legacy {username, password} payloads return 400. Only identity.type == "username" is supported by jaclang's SQLite user store; email and other types are rejected with 400 until the store gains an email column. New jaclang.runtimelib.auth_models exposes IdentityType, CredentialType, IdentityInput, CredentialInput, LoginRequest, and RegisterRequest for callers that want typed validators.

New Features#

  • Lint: W3038 Auto-Convert React useState to Jac has: jac lint --fix now rewrites the React useState hook pattern into Jac native state declarations. [name, setName] = useState("") becomes has name: str = "", all setName(expr) calls throughout the function body are rewritten to direct name = expr assignments, and the unused useState import is automatically removed. The type is inferred from the initial value.
  • Feat: Native jac bundle pipeline with jac.toml: Replaces pyproject.toml with jac.toml as the project manifest for Jac packages. Adds jac bundle command that validates, precompiles .jac.jir bytecode, and builds a standards-compliant Python wheel (pip install-ready) entirely in Jac - no pyproject.toml or setuptools required.
  • Add: SSE-aware sv_service_call hookspec + streaming generator returns: The default sv_service_call impl now inspects the downstream response's Content-Type; when it's text/event-stream, the call returns a Python generator that yields parsed data: event dicts and terminates on the event: end framing (or raises RuntimeError on event: error). Lifecycle of the underlying httpx connection follows the generator (closing on exhaust, drop, or scope-exit). Pairs with a _finalize_call_response fix in runtimelib/server: the isgenerator check was on reports rather than result, so explicit generator returns from def:pub walkers/functions silently fell into the str() fallback and were never streamed. New runtimelib/sv_sse.jac exposes the consumer-side helpers.
  • Type Checker: W1051 Unresolved Expression Type Warning: The type checker now emits W1051 whenever an expression fails to resolve to a concrete type (falls back to Unknown). This surfaces gaps in type inference so users can add annotations or fix imports where type information is missing. Enum member names and CFG edge classification for if/elif/else branches were also tightened alongside this change.
  • Type Checker: Enum member declarations resolve to EnumMemberType: Enum member declaration sites (e.g. PERSONAL in enum Category { WORK, PERSONAL }) now resolve to EnumMemberType instead of falling through to the enum's ClassType or Unknown. IDE hover shows PERSONAL: <Category.PERSONAL> and per-member annotation validation (e.g. PENDING: int = "wrong") now lives alongside enum member construction. Covers both inline and impl-based enum forms. W1051 is also suppressed inside Python stub modules to avoid noise from stub-internal forward references.
  • Type Checker: Comparison Operator Validation (E1110/E1111): The type checker now validates that comparison operators (<, >, <=, >=, ==, !=) are supported between the operand types by checking for the corresponding magic methods (__lt__, __gt__, etc.). Membership tests (in, not in) now verify that the right operand supports __contains__. Previously, expressions like "hello" > 5 or 5 in 10 passed silently through jac check; they now produce clear diagnostics. Identity checks (is, is not) remain unrestricted. Chained comparisons validate each adjacent pair independently.
  • Type Checker: W2080 Warning for Undeclared obj Attributes: The type checker now detects annotated self-assignments in init/postinit bodies of obj/node/edge/walker archetypes that are not declared as has fields. A W2080 warning guides the user to add the proper has declaration. The attribute is still resolved for type checking to prevent cascading E1030 "has no attribute" errors downstream. This improves both the developer experience (actionable warning) and type checker accuracy (fewer false positives from unresolved attributes).
  • Feature: Path pattern support in .jacignore and jac check --ignore: Ignore patterns containing / are now matched against the full file path (e.g. jac-client/jac_client/plugin/cli.jac), allowing specific files in specific packages to be ignored while their same-named counterparts in other packages are checked. Simple patterns (no /) retain existing behavior of matching against individual path components.
  • Auto-Lint W3039 (getattr-to-null-ok): jac format --lintfix now rewrites getattr(x, "attr", None) call sites to the Jac-native x?.attr syntax. Only the safe 3-arg form with a literal None default is rewritten; 2-arg getattr (which raises on miss) and calls with non-None defaults are preserved. The rule covers every expression context (assignment, return, comparison, boolean op, ternary, function argument, if-condition), backed by a new _replace_child_in_parent AST helper. Gated by the existing jac-check workflow.
  • Feat: Partial Anchor Updates: Optimizes MongoDB writes by skipping full document replacement when only archetype fields change. Implements four-layer system with dirty-field tracking, selective serialization, and smart routing to targeted $set operations on changed fields, while preserving full rewrites for structural changes or first inserts.
  • New lint: W3040 filter comparison tautology: The compiler now warns when both sides of a comparison in a filter expression resolve to the same name (e.g., [?:Task, to_user == to_user]), which is always true due to node field shadowing. Suggests using a different variable name for the intended enclosing-scope value.
  • root is a reserved keyword again: bare root is the canonical form; backtick-escape (`root) to shadow it. root() is deprecated (W0062) but keeps working - the compiler lowers it to the same Jac.root() call.
  • Client error stacks resolve to .jac source: The /cl/__error__ server endpoint now source-maps both the JS stack and React's component stack onto the originating .jac files and line numbers, and drops non-source frames (node_modules, build artifacts) by default. Set [client_errors] verbose = true in jac.toml to keep them, e.g. when diagnosing a stale per-file source map.
  • JSX: W5015 Single-props Component Definition Warning: A JSX component declared with a single parameter literally named props is now flagged with W5015. The single-props shape is passed through verbatim instead of destructured, so JSX call sites cannot be validated per prop -- the type-checker keys per-attribute checking on parameter names, so <Foo title="..." /> would E1101 against any props-bundle signature. The diagnostic recommends direct named parameters (e.g. def Greeting(name: str, age: int)) and reserves the bundle shape for forwarding/HOC patterns via inline # jac:ignore[W5015] suppression. Includes a tutorial rewrite of tutorials/fullstack/components.md to lead with direct named params.
  • Runtime: PermissionDenied diagnostics on cross-user writes: Cross-user write attempts (edge create, field mutation, delete) used to silently no-op when the actor's root lacked sufficient access (HTTP 200 with no signal). The runtime now records a typed PermissionDenied (operation, target id/type, target owner, required level, actual level) on a request-scoped ExecutionContext.diagnostics accumulator and surfaces it as warnings: [...] in the response envelope, including persist-time field-write drops. A new JacRuntime.strict_permissions flag (driven by JAC_STRICT_PERMISSIONS env var) escalates denials to PermissionError. The diagnostic message names the canonical fix: grant(node, level=...) at the owner side or routing the write through a node ability. Fixes #5788.
  • Type Checker: W1036 Bare-Generic Coverage in Function Signatures: The bare-generic warning (W1036) now also fires on function/method return type annotations (e.g. def f -> list) and parameter type annotations (e.g. def f(x: dict)), in addition to the existing coverage on has fields and x: T = ... assignments. Bare-generic checks now also recurse into union branches, so list | None warns on the bare list. The warning is deduped across split decl/impl method definitions (only the decl side fires).
  • Language: Typed-Base Enum Syntax: New shorthand enum X: T { ... } declares an enum whose members are instances of T. enum X: int desugars to class X(IntEnum), enum X: str to class X(StrEnum), and enum X: T for any other T to the Python mixin form class X(T, Enum) so members behave as T instances without an explicit .value. Plain enum X { ... } keeps the existing Enum-only semantics. The seed compiler, RD parser, codegen, type checker, and unparse pass are all updated end to end. As a real-world dogfood, JacSemTokenType in jac0core/constant.jac is refactored from the verbose class JacSemTokenType(IntEnum) { with entry { ... } } form to enum JacSemTokenType: int { ... }. (flag X for IntFlag is deferred because flag is used as an identifier in 50+ sites and reserving it would be backward-incompatible.)
  • Lint: W3041 Stale has Read in Reactive Effect: jac check now warns when a reactive has field is read after being assigned in the same can with entry body in a client-side component. The pattern field = expr; if field { ... } lowers to a useState setter call followed by a closure-captured read of the previous value (the textbook React stale-closure bug); the warning surfaces it at compile time so the issue is caught in the compile-loop instead of at runtime. Suggested fix: capture the value in a local first (local = expr; field = local; if local { ... }).

Bug Fixes#

  • Type Checker: isinstance Guards with Empty Branches: Type narrowing now applies correctly when an isinstance guard has an empty branch, e.g. if isinstance(x, T) { } else { return; } or its mirror if isinstance(x, T) { return; } else { }. Previously, neither pattern narrowed x after the guard, producing spurious attribute and return-type errors on otherwise correct code.
  • Fix: Bidirectional Edge Traversal: Resolved a bug where undirected edges (created with <+:edge:+> or <++>) were only traversable from the source node to the target node. The runtime now correctly calculates an effective_dir for undirected edges, enabling bidirectional traversal regardless of the requested direction.
  • Type Checker: Walker .reports Attribute Resolution: Accessing .reports on a spawned walker no longer produces a spurious E1030 error. Walker and node archetypes now inherit from their builtin base types (Walker, Node) in the type system's MRO, so fields like reports: list[any] resolve through normal inheritance. Users can also declare has reports: list[MyType] on a walker for compile-time type checking of report statements.
  • Fix: Component has Variables Visible in impl Method Bodies: impl app.addTodo and similar nested impls now correctly resolve has variables declared in the parent component function. Previously, assignments to component state created untyped local shadows instead of resolving to the declared type, causing false Unknown types and downstream type errors.
  • Formatter: Bare GenAI Ability by and Semicolon Preservation: The Jac formatter no longer strips the by keyword and trailing semicolon from bare genai ability declarations (e.g. def classify(s: str) -> str by llm;). Previously, formatting such a declaration would silently drop by <model>;, producing invalid output. The fix inspects child tokens of the Name node in DocIRGenPass.exit_name and correctly renders the full by <name>; fragment when present. Formatting is also idempotent.
  • Fix: Component has Variables Visible in impl Method Bodies: impl app.addTodo and similar nested impls now correctly resolve has variables declared in the parent component function. Previously, assignments to component state (e.g., todos = todos + [...]) created untyped local shadows instead of resolving to the declared has todos: list[dict], causing false Unknown types and downstream E1055 errors.
  • Fix: False E1001 on Annotated Self-Assignments: Fixed a bug where annotated self-assignments like self.x: int = val produced false E1001 "Cannot assign int to int" errors. The type annotation was resolving to the class type instead of the instance type, causing same-type assignments to be rejected. Real type mismatches (e.g., self.x: int = "bad") continue to be caught correctly.
  • Fix: Suppress spurious W1051 warnings on member-access attribute names: The type checker no longer emits false W1051 ("Expression type could not be resolved") warnings for attribute names in member-access expressions (e.g. strip in x.strip()). This eliminates ~68% of W1051 false positives across the codebase, particularly for builtin method calls like str.strip(), dict.get(), list.append(), and Path.read_text().
  • Fix: Suppress spurious W1051 for keyword argument names and type annotation tags: The type checker no longer emits false W1051 warnings for keyword argument names in function calls (e.g. name= in Thing(name="x")) or for names inside type annotation SubTags (e.g. : int). Combined with the previous member-access fix (#5619), this eliminates ~97% of false W1051 warnings across the codebase.
  • Fix: Implement optional chaining (?.) type resolution: The type checker now properly handles the ?. (null-safe) operator. When accessing an attribute via ?. that doesn't exist on the base type, the checker returns NoneType instead of emitting a false E1030 ("has no attribute") error. This implements the previously-stubbed TODO for <expr>?.member type resolution.
  • Fix: Prevent cascading Unknown from partial union member access: When accessing an attribute on a union type where some (but not all) members have the attribute, the type checker now returns the resolved type from matching members instead of Unknown. The E1099 diagnostic is still emitted for the missing members, but downstream expressions no longer cascade into false E1002/E1053/W1051 errors. This eliminates ~17% of errors in union-heavy code (e.g. 35 → 29 in the root causes test fixture).
  • Fix: isinstance narrowing now propagates through AND conditions: When isinstance(x, T) appears as the left operand of an and expression, the right operand now sees x narrowed to type T. This enables common patterns like if isinstance(nd, IfStmt) and nd.body where nd.body requires the narrowed type. Previously, the RHS saw the original unnarrowed type, producing false E1030 errors.
  • Fix: byllm Sem Strings Dropped for Subpackage Imports: MTIR entries are now stored under the module's dotted import name, so byllm resolves .jac types imported from any subfolder depth. Previously only top-level imports matched the compile-time file-stem key.
  • Fix: inherited members on classes that share a name with a member: Calls like datetime.datetime.fromisoformat(...) no longer report a false "has no attribute" error when the child class defines a member with the same name as one of its base classes.
  • Fix: super() calls in methods: super().method(...) inside an archetype method now resolves to the parent class's method with the correct return type, so chained calls like base = super().to_dict() no longer trigger spurious type errors.
  • BugFix: Fixed false E1053 errors for @property members with optional return types inside truthiness guards by applying CFG-based narrowing to the property resolution path.
  • Fix: Concurrent Server Requests No Longer Block on Sync IO: Walker and function endpoints now run concurrently under load. Sync user code is offloaded so blocking operations (sleep, network calls, byllm, heavy CPU) do not stall the event loop; async user code continues to run natively on the loop. Request-lifecycle persistence (user resolution, context setup, post-walker commit) is wired through an end-to-end async path so concurrent requests no longer serialize on shared setup or commit. Each request now executes inside its own forked ExecutionContext pushed into a task-local _request_context, which isolates user_root / entry_node mutations so concurrent requests from different users can no longer overwrite each other's identity. Three narrower follow-ups remain tracked as separate issues: per-request SQLite connection pool (removes the stopgap UserManager._lock), SqliteMemory Unit-of-Work write batching, and byllm async ainvoke surface.
  • BugFix: Resolved all 15 type errors in cfg_build_pass by restructuring subtype attribute access patterns for proper type narrowing.
  • Fix: Concurrent walker edge loss: SQLite persistence now uses BEGIN IMMEDIATE transactions and delta-based edge merging to prevent concurrent walkers from overwriting each other's edge changes. SqliteMemory.put() is deferred to sync() and close() flushes pending writes. Added NodeAnchor.edge_delta() for computing per-request edge additions/removals.
  • Fix: generator yield/return type-checking: yield x and return x inside a generator function are now validated against the appropriate type parameter of the declared return (Generator[Y, S, R] / Iterator[Y] / Iterable[Y] / async counterparts / AwaitableGenerator) instead of the whole return type. Resolves spurious E1093 and E1002 errors on valid generator definitions.
  • Fix: "User profile not found" race after walker completion (SQLite): SqliteMemory.put() is now deferred to sync() so atomicity is per-walker instead of per-anchor, eliminating the cross-anchor race reintroduced by #5644 that caused subsequent walkers to see partial state on local/SQLite dev.
  • Fix: Suppress spurious W1051 on intrinsic JSX tag names: The type checker no longer emits false W1051 (Expression type could not be resolved) warnings on intrinsic HTML tag names in JSX (e.g. div, button, p). Name nodes under a JsxElementName are resolved by the JsxElement evaluator (lowercase tags bind against _JsxIntrinsicElements, uppercase component parts resolve normally); exit_name now skips them, matching the existing treatment of AtomTrailer.right, KWPair.key, and SubTag. Fixes six spurious warnings per <div>...</div>-style element in examples like mini_todo/main.jac.
  • Fix: Resolved type errors in jaccomplete: Shell completion script generation functions (_static_bash, _static_zsh, _static_fish) now have proper type annotations on the commands parameter. Previously the untyped list caused 17 downstream type errors on tuple destructuring loops.
  • Fix: JSX HTML entity decoding: HTML entities (&amp;, &gt;, &#62;, &#x3e;, etc.) in JSX text content and string attribute values are now decoded at compile time, matching the JSX specification and standard implementations (React, Babel, Preact, Solid).
  • Fix: Infinite recursion in LiteralString→str type assignability check: Added a guard to prevent infinite recursion when both source and destination types are LiteralString in the assign_type delegation path. This latent bug could surface when ClassDetailsShared equality semantics change in future fixes.
  • Fix: Correct type annotations in PyastGenPass sv-to-sv stubs: Fixed binding, boundary_types, and info parameters that were typed as object or bare dict, causing false type checker errors. Also typed InteropBinding.param_names and param_types as list[str] and removed an invalid get_weather stub.
  • Fix: Jac any annotation typing: The type checker now treats Jac's lowercase any builtin type annotation as Any instead of resolving it as Python's built-in any() function.
  • Type Checker: Classmethod Calls Returning Self: Calling a classmethod with -> Self and chaining attribute access (e.g. datetime.now(tz).isoformat()) no longer raises a spurious E1031 "Cannot access attribute X for type Self". The receiver class is now bound at member-access time, so the result resolves correctly even when the bound method is captured into a variable.
  • Fix: Type narrowing through parenthesized OR in and conditions: if x and (f(x) or g(x)) now correctly narrows x inside the OR's operands. Previously the wrapping CFG node around the nested boolean expression appeared as a phantom predecessor and forced narrowing to widen back to the declared type, producing spurious E1053 errors for T | None and similar union types.
  • Fix: decorated class inside a function body now works correctly: A class with a decorator (e.g. @dataclass) declared inside a function body was not recognised as a class by the type checker. Calling it returned NoneType and accessing any field gave attribute errors. This is now fixed.
  • Native: Garbage Collector Bug Fixes: Fixed multiple bugs in the jac-native runtime garbage collector.
  • Parser: friendlier error for JavaScript => arrow syntax: Writing e => fn(e) in expression position (JSX slots, assignments, f-strings) now produces a single E0025 diagnostic with a lambda hint instead of a cascade of unrelated parse errors.
  • Fix: Go-to-definition for bare any keyword: Bare any (lowercase Jac keyword) in type annotations now resolves to typing.Any for LSP go-to-definition, jumping to the class definition in typing.pyi. Previously the symbol was not attached and the LSP returned no location.
  • Fix: Stop leaking typing-internal names as ambient builtins: jac_builtins.pyi now uses __all__ to declare its public surface. The TypeEvaluator's merge loop honors it, so Any, Callable, and Protocol (imported only for use in this stub's own annotations) are no longer silently re-exported as ambient names. Bare Any / Callable / Protocol in user code without import from typing { ... } now correctly emit W2001 ("may be undefined"), matching every other typing-module name.
  • Fix: jac format whitespace bugs in lambda and if inline blocks: Fixed three formatting issues where if blocks inside multi-statement lambda bodies stayed inline instead of expanding to multi-line, inline single-statement lambda bodies were missing a space before }, and the flat-capable if path produced a double space before }. The formatter is now idempotent for these cases.
  • Type Checker: Enum Iteration Yields Enum-Instance Type: Iterating an enum class in a for loop or comprehension now resolves the iteration variable to an instance of that enum, so .name and .value access type-check correctly. Previously the variable fell through to Unknown (Python's EnumMeta.__iter__ is on the metaclass, not in the enum's MRO), or for StrEnum inherited a misleading __iter__ from str and yielded LiteralString, producing cascading false E1032 / W1051 diagnostics on patterns like [i.name.lower() for i in JacSemTokenType].
  • Fix: Type narrowing inside while loop bodies: Resolved a false-positive type error where a variable guarded by if x is not None inside a while loop was still treated as T | None in the loop body.
  • Fix: JSX preserves spaces between adjacent expression children: <div>{a} {b}</div> now renders with a literal space between values instead of dropping the separator (e.g. "3 tasks remaining" instead of "3tasks remaining").
  • Fix: Typed-edge ref [expr->:Type:cond:->] parses after a starting expression: [root()->:Scheduled:priority>=3:->] and similar typed-edge filter chains now parse the same way the bare-start form already did.
  • Fix: Plugin-load failures now surface as warnings: When an installed jac plugin fails to load (most often because one of its own runtime dependencies isn't installed), jaclang now prints a warning to stderr naming the plugin and the underlying error, instead of silently falling back to a degraded feature set. For example, a missing sqlalchemy install used to make jac-scale's enhanced API server (with /docs and /graph routes) silently disappear; now the user sees the cause and what to do about it.
  • Fix: Typed-edge filter predicates were silently dropped by the topology-index fast path: Queries like [root()->:Scheduled:priority>=3:->] previously returned every Scheduled-connected node when a jac.toml was present (which makes the topology index default to enabled), because plan_query extracted only the type name from the filter lambda and discarded the predicate. The planner now bails out and lets the linear path evaluate the lambda whenever a destination's edge filter has compares -- or a non-final hop's node filter has compares -- so the result matches the non-indexed semantics. Type-only filters and final-hop node predicates still go through the index unchanged.
  • Fix: Spurious W1051/W2001 warnings on sem Foo.field targets: Dotted-path targets in sem declarations no longer produce W1051 "Expression type could not be resolved" or W2001 "may be undefined" warnings on their path segments.
  • Fix: Remove duplicate Transform.emit and _is_suppressed impl blocks: Both methods were duplicated verbatim in transform.impl.jac, causing type checker errors. The redundant second copy has been removed.
  • Fix: Functions with fewer parameters now satisfy callables expecting more: Assigning a zero-arg handler to a JSX prop like onClick={handler} (or any Callable[[MouseEvent], None] slot) is accepted, matching TypeScript's contravariant-arity rule.
  • Fix: Callable[..., T] accepted as a | union arm: TypeAlias unions ending in Callable[..., Any] (e.g. typeshed's inspect._SourceObjectType) no longer collapse to UnionType | type[Any] and reject every caller with E1053.
  • Fix: Never / NoReturn resolved to NeverType and accepted as union arms: Annotations using Never or NoReturn (alone or as a | arm such as int | Never) no longer fall back to _SpecialForm and collapse the surrounding union; Never-returning calls are also assignable wherever a value is expected (PEP 484 bottom type).
  • Fix: is not and f-string format specs in JS codegen: is not None now compiles to !== null instead of being silently inverted to ===, and f-string format specs like f"{x:.2f}" are honored via the _jac.builtin.format runtime helper instead of being dropped.

Refactors#

  • Refactor: Sqlite memory type narrowing: Tightened internal type annotations in runtimelib/impl/memory.impl.jac. No runtime behavior change.
  • Console Proxy Typing: Annotated the lazy console global in jaclang.cli.console as JacConsole (with a cast over the _ConsoleProxy instance) so static type checkers and IDEs resolve console.print(...), console.error(...), console.spinner(...), etc. against the real interface instead of the Any returned by _ConsoleProxy.__getattr__. No runtime change.

Documentation#

  • Standardized any Type Documentation: Replaced all instances of capitalized Any with lowercase any across the documentation for consistency with the built-in type.
  • any Built-in Function Convention: Standardized the use of the backtick prefix (`any) for the built-in function to distinguish it from the any type in documentation.
  • Redundant Import Cleanup: Removed redundant import from typing { Any } statements across the documentation.
  • Strict Data Model Enforcement: Jac now enforces a strictly declarative data model for all objects (obj, node, edge, walker). All instance attributes must be declared using the has keyword.
  • Dynamic Assignment Warned: Assigning attributes to an instance that were not declared in its has block is now explicitly documented as an anti-pattern.
  • Future Compliance: Future versions of the Jac compiler will strictly forbid dynamic attribute assignment to ensure portability across server, client, and native codespaces.
  • Best Practice: Use by postinit for attributes that require initialization logic beyond simple defaults.

jaclang 0.14.0#

  • Fix: byllm Provider Config Ignored from jac.toml: The byllm plugin now correctly reads the provider and model from [plugins.byllm.model] in jac.toml. Previously, PluginConfigBase resolved project_dir via cwd, causing the config lookup to miss the project's jac.toml and fall back to the OpenAI default. PluginConfigBase now derives the project directory from JacRuntime.full_target_path (set by jac run before compilation).
  • 1 internal refactor.
  • Feat: to cl: / to sv: / to na: Section Headers: Module-level section headers set the default client/server/native context for every following statement until the next header; the legacy cl { ... } / sv { ... } / na { ... } braced blocks now emit a deprecation warning.
  • Format: to cl: / to sv: / to na: Section Headers: jac format now emits section headers on their own line with a blank-line separator and the body dedented to module scope, and the parser models them as implicit ClientBlock / ServerBlock / NativeBlock so existing codegen and analysis passes work on sections unchanged.
  • Type Checker: Unannotated Variables Widen on Reassignment: The classic x = None; ...; x = value; sentinel pattern no longer errors with E1001: Cannot assign T to NoneType. Reassigning any unannotated variable now widens its inferred type, matching Pyright. Variables with an explicit annotation (x: T = ...) still enforce the declared type.
  • Fix: W3025 Identical-Branch Lint False Positives: jac format --lintfix no longer flags branches that only look alike at the statement-type level (e.g. both branches assign to a subscript target or both call the same function with different kwargs); identical-branch detection now compares canonical unparsed source rather than a shallow type-name key.
  • Type Checker: Property-Aware Assignment Checking: Assigning to a @cached_property attribute (e.g., self.archetype.__jac__ = self) no longer produces a false E1001 against the raw function type. The type checker now delegates to get_property_write_type in the evaluator, which classifies the target in a single decorator walk: @cached_property assignments type-check against the getter's return type (non-data descriptor semantics), @property with a @name.setter uses the setter's value-parameter type, and @property without a setter emits a new E1005 "Cannot assign to read-only property" diagnostic instead of the misleading "Cannot assign T to \<function>" message. Setter detection walks names_in_scope_overload in the enclosing class scope to find sibling @name.setter methods.
  • Type Checker: Solve Self from Call Arguments: Methods returning Self now have their return type specialized at the call site by inferring Self from the call's first argument. Fixes unloaded = object.__new__(self.__class__) (and similar patterns) where unloaded was previously typed as the unbound Self TypeVar, causing spurious E1031 errors on subsequent member access. The fix synthesizes the implicit cls: type[Self] / self: Self annotations that typeshed routinely omits, runs TypeVar binding for every FunctionType call (not only those with explicit type_params), and distinguishes constructor calls (ClassName(args), where cls is implicit) from direct staticmethod-style __new__ calls (where cls is explicit). Also fixes a related latent bug where unbound method calls (Class.method(instance, ...)) misaligned positional arguments against generic parameters in _collect_type_var_bindings.
  • Feat: jac eject Command: New jac eject compiles a Jac project to a self-contained output folder containing only .py and .js files (zero .jac files). Server-side .sv.jac becomes plain Python via gen.py, client-side .cl.jac becomes plain JavaScript via gen.js, impl files merge automatically, and the generated backend/serve.py boots the existing JacAPIServer request handler without invoking the Jac compiler at runtime. Also emits requirements.txt, package.json, vite.config.js (with backend proxy), index.html, and a run.sh that starts both processes. Ejected backends still depend on pip install jaclang for the runtime helpers (walker dispatch, auth, persistence).
  • Refactor: Public Helpers for jac eject Plugin Authors: jaclang.cli.commands.impl.eject now exports resolve_eject_output(src, output) and load_eject_project_metadata(src) as public helpers. Plugin pre/post hooks that extend jac eject (e.g., a hypothetical jac eject --scale) can call these to stay in sync with the default command's output-directory fallback and jac.toml parsing logic instead of duplicating it.
  • Fix: jac eject no longer chokes on the .jac/ build cache: _collect_jac_files used Path.rglob("*.jac"), which matches both files and directories whose name ends in .jac. Any project containing the .jac/ build cache (i.e. any project that had been built before ejecting) was passing the cache directory itself to JacProgram.compile, failing with [Errno 21] Is a directory: '<src>/.jac' and aborting the eject. Non-files are now skipped at the top of the loop so eject is idempotent regardless of build state.
  • Feat: jac eject Runs Full-Stack Apps End-to-End: jac eject now produces more robust output that runs a complete fullstack Jac app (validated on sophisticated backend + SPA + streaming RAG chat) from ./run.sh alone.
  • Feat: SV-to-SV Eager Auto-Spawn: jac start consumer.jac now brings up every sv import-ed provider (including transitive ones via BFS) at consumer startup, before the first request arrives; the env var path stays for cross-host providers.
  • Feat: SV-to-SV Microservice Interop: sv import between two server modules now generates Python HTTP client stubs that route calls through jaclang.runtimelib.sv_client instead of loading the provider in-process.
  • Fix: ES Codegen Method Dispatch on .cl.jac Files: Operator-side primitive dispatch now walks the receiver's MRO, so "a" in name.lower() lowers to .includes() instead of a runtime-crashing JS in. When the receiver's static type isn't a JS-native primitive, Python method idioms (lower, upper, strip, lstrip, rstrip, append, extend, pop) now route through _jac.poly.* runtime helpers that typeof-dispatch to the native JS operation, instead of leaking Python identifiers into the JS output.
  • Fix: Console BrokenPipeError in Sidecar Mode: Console print() and flush() now catch BrokenPipeError/OSError, preventing crashes when stdout is closed (e.g., Tauri sidecar after port discovery).
  • Fix: Scheduler Null Safety: stop() and wait() now guard against None on _stop_event, _done_event, and _thread, preventing AttributeError during early shutdown.
  • Fix: JAC_DATA_PATH Environment Variable for Read-Only Deployments: UserManager and get_db_path() now honor the JAC_DATA_PATH env var, redirecting writable runtime data (database, .jac/data/) to a specified path. Enables deployments where the base path is read-only (e.g., AppImage).
  • Cleanup: Removed Unused Client Error Handler: Removed _handle_client_error() and _client_error_logger from the server runtime.
  • Fix: UTF-8 Encoding for Windows: Added explicit encoding="utf-8" to open() calls in compiler passes and CLI commands. Prevents charmap codec errors on Windows where the default encoding is cp1252.
  • Type Checker: Improved Narrowing for AND/OR and Ternary Expressions: Type narrowing now works correctly in nested ternary expressions, AND/OR chains, and isinstance on unknown-typed variables.
  • Native: Bug Fixes and Stability Improvements: Fixed several issues in the jac-native compilation pipeline, including silent failures when type-checker errors occur during .na.jac compilation, incorrect ordering of default/non-default has attributes in native structs, and transitive C-library import resolution for imported .na.jac modules.
  • Native: jac-gdb Debugger Support: Added jac-gdb, a GDB-based debugger integration for native Jac programs. The LLVM module ID is now set from the source file name so GDB can locate source files, and optional DWARF debug metadata (function locations, compile-unit info) is emitted when JAC_NATIVE_DEBUG=1 is set.
  • Fix: ES Codegen new Expression for Any-Typed Callees: Calling a variable typed as Any no longer emits new handler(payload) in the generated JavaScript.
  • Type Checker: Expression-Level CFG Narrowing: Type narrowing in and/or chains and ternary expressions now uses the same CFG backward walk as if/assert narrowing. Fixes edge cases in nested ternaries and compound boolean guards.
  • Type Checker: Fix object.__new__() Direct Calls: object.__new__(cls) no longer raises a false E1051 "Too many positional arguments" error. __new__ is now correctly modeled as a staticmethod (matching CPython) instead of a classmethod, so its cls parameter is not auto-stripped when called directly. Constructor resolution (MyClass(...)) is unaffected; _validate_constructor_method now explicitly strips self/cls before argument matching rather than relying on the implicit classmethod stripping.
  • Serializer: ref_mode for Shared and Circular Object Graphs: Serializer.serialize(..., ref_mode=True) now emits {"$ref": "<uuid-hex>"} for any Jac object seen more than once, instead of inlining it repeatedly. This prevents both data bloat from shared references and infinite recursion on circular graphs (e.g. alice.friend = bob; bob.friend = alice). The first occurrence is always fully inlined; subsequent occurrences become a compact $ref. _deserialize_value automatically resolves $ref dicts back to live archetypes (via context memory) or lazy NodeAnchor stubs. jid() on plain obj archetypes now also registers them into context memory so $ref round-trips work without graph traversal.
  • Persistence: Schema Drift Just Works: Editing a node/obj's fields no longer forces wiping .jac/data/. SqliteMemory now stores anchors as JSON, stamps each row with a per-archetype fingerprint, tolerates added/removed fields, coerces primitive type changes (str <-> int/float/bool, ISO strings -> datetime/date/time, str -> UUID, list <-> tuple, Enum, Optional), and quarantines truly unloadable rows in a new anchors_quarantine table instead of deleting them. Class renames are handled by the @archetype_alias("old.module.OldName") decorator. Legacy pickle DBs auto-migrate on first open. See Persistence & Schema Migration.
  • CLI: jac db Operator Toolkit: New jac db command group: inspect, quarantine list/show, alias list/add/remove (DB-resident rescue aliases, no code deploy needed), and recover/recover-all (with live class re-stamping). Backend-agnostic: the PersistentMemory interface grew eight new abstract methods that jac db dispatches through, so SqliteMemory (default) and any plugin-provided backend (e.g. jac-scale's MongoBackend) work through the same commands. See CLI -> Database Operations.
  • Fix: Python-Compatible Scoping for Assignments in Non-Scoping Blocks: Unannotated assignments inside try/except/finally, if/else, for/while, and match blocks now bind in the nearest enclosing Python scope instead of creating a shadow symbol in the block's faux scope. This eliminates spurious W2003 "defined but never used" warnings when the same name is assigned across sibling branches (e.g., category in try { ... } except { ... }), and also fully resolves the former "string slice loses type" type-checker root cause: while chomp.startswith('.') { chomp = chomp[1:]; ... } no longer degrades str to Unknown because the re-assignment updates the single outer symbol instead of forking a branch-local one. Mirrors the existing walrus (:=) handling and matches Python semantics exactly.
  • Fix: by postinit Symbol Resolution: Fields declared with by postinit no longer show a false W2001 ("'postinit' may be undefined") warning and go-to-definition now works correctly on them.
  • Fix:: jac0 bootstrapper no longer emits orphan or duplicate impl methods in generated Python output.
  • Fix: Reject impl for methods that already have a body (E2016/E2017): decl_impl_match_pass now checks needs_impl before overwriting a declaration's body. Providing an impl for a method with an existing body raises E2016. Providing a duplicate impl for a stub that already has one raises E2017.
  • Fix: Symbol Resolution for Standalone Constructs in .impl.jac Files: DeclImplMatchPass now resolves Load-context names across the full subtree of every annex module, not just TypeAlias / GlobalVars / ModuleCode items. Standalone def / obj / node / edge / walker / enum / class constructs defined directly in a .impl.jac file (with no matching decl in the primary) had their body reads left with .sym = None, which (a) suppressed type inference on every local (e.g. system = platform.system(); showed as untyped, even though platform.system() -> str is correctly stubbed) and (b) emitted spurious W2001: Name 'x' may be undefined warnings for every downstream use of a local. The hand-picked container whitelist is gone; resolution is now uniform across primary, impl, and variant modules.
  • Fix: Remove Unsound TYPE_CHECKING Auto-Demotion: PyastGenPass no longer rewrites annotation-only imports into if typing.TYPE_CHECKING: blocks. Dataclass, Pydantic, attrs, and anything else that resolves string annotations at runtime needs names like ClassVar / Protocol / TypedDict importable, and the classifier couldn't see those uses (immediate trigger: JacTestCheck.test_suite_path crashing dataclass with mutable default <class 'dict'>). Explicit with entry { if TYPE_CHECKING { ... } } blocks in Jac source continue to work and remain the right way to break import cycles.
  • Feat: {**expr} Dict-Spread in JSX Attributes: JSX spread attributes now support the Pythonic {**props} syntax alongside the existing {...props} form. {**props} is the preferred Jac idiom; using {...props} emits a W0063 style warning.
  • Fix: Type Narrowing Through BoolExpr Inside isinstance Guards: isinstance narrowing now works correctly when the guarded block contains and/or expressions. Previously, the short-circuit CfgExpr sub-graph created for a standalone BoolExpr was disconnected from the enclosing CFG, so the backward narrowing walk could not reach the isinstance condition. The CFG build pass now links standalone BoolExpr operand chains to their enclosing flow node (matching the existing ternary handling), and the narrowing walker treats CfgExpr predecessors as transparent when they don't mention the target symbol, instead of prematurely marking the path as unnarrowed.
  • Fix: Assignment Is a Narrowing Barrier: Re-assigning a variable inside a narrowed scope (e.g. inside a case body) now correctly resets its type for every access that follows.
  • Fix: Narrowing Through Parenthesized and/or Conditions: if (a and b): now narrows types the same way as if a and b:.
  • Fix: Tuple-Unpacking Assignment Is a Narrowing Barrier: (x, _) = f() now resets type narrowing on the reassigned names, just like a plain assignment does.
  • Fix: Starred-Unpack Assignment Is a Narrowing Barrier: (head, *rest) = f() now resets type narrowing on the starred name too. Closes a leak where prior narrowing on rest could survive past the unpack.
  • Fix: Ternary Narrowing Through and/or Conditions: x if a and b else y now narrows its branches the same way as the equivalent if a and b: statement.
  • Fix: Pre-Statement Narrowing Reaches Inside Ternary Expressions: A ternary's branches now see the narrowing established by earlier statements (e.g. if x is None: return), so access on the narrowed type works the same inside x.m() if cond else x.m() as it does in a plain if/else.
  • Fix: Destructuring Assignment Narrows by RHS Element Type: (x, _) = (5, "ok") now narrows x: int | None to int by extracting the element type at position 0 from the concrete tuple RHS, matching pyright. Works for tuple literals, -> tuple[T1, T2] function return types, and nested destructuring like ((a, b), c) = .... Falls back to the previous conservative reset when the RHS isn't a concrete tuple or the extracted element type doesn't strictly narrow the declared type.
  • Fix: Hover on LHS Assignment Shows Post-Write Type: Hover on an assignment target (flat or destructuring) now reflects the post-write effective type instead of the pre-write declared type, matching pyright/pylance.
  • Fix: NamedTuple Destructure Narrowing: Destructuring a NamedTuple ((x, _) = point) now narrows each target to its field type, matching the behavior already available for plain tuple literals.
  • Removed: W2052 Broad Exception Warning: Removed the W2052 warning that flagged except Exception as overly broad. Catching Exception is a legitimate and common pattern at system boundaries (e.g., LLM calls, network I/O), and the warning produced false positives in these cases.
  • Type Checker: JSX Reserved Props and Intrinsic Tag Resolution: key and ref are now recognized as framework-reserved JSX attributes and no longer produce false E1101 errors on user-defined components. Intrinsic HTML tag names (div, button, etc.) are resolved against _JsxIntrinsicElements from the type stubs, eliminating false W2001 "may be undefined" warnings and enabling IDE hover/goto-definition on tag names; unrecognized intrinsic tags emit a dedicated W1050 warning.
  • Type Checker: JSX Prop Validation and Event Handler Types: JSX attributes are now type-checked against the component's function signature (<Button onClick={42}/> errors) and intrinsic HTML element prop schemas (<button onClick={42}/> errors). Includes bidirectional lambda inference so <Button onClick={lambda e { e.clientX; }}/> infers e as MouseEvent, spread-prop validation rejecting non-dict spreads, ambient event handler aliases (MouseEventHandler, KeyboardEventHandler, etc.), and intrinsic prop classes for common HTML elements.
  • Fix: Callable, Any, Protocol Resolve in User Code: Fixed a bug where Callable[[X], Y] annotations in function parameters resolved to Unknown. The builtins merge now correctly overrides typeshed-internal imports with the jac_builtins.pyi re-exports, so ambient typing names pass the lookup_symtab import filter.
  • Fix: Formatter JSX/Lambda Inline + CLI Bracket Stripping: jac format -s no longer strips bracketed Jac syntax like [root()-->] or todos + [t] under the Rich-backed console (missing markup=False, highlight=False on console.print). JSX elements with only text/expression children (<button>Add</button>) and single-statement if bodies inside lambdas (lambda e: T { if cond { stmt; } }) now stay flat when they fit the print width instead of force-breaking; statement-level if still always breaks.
  • Fix: Formatter Width Accounting and Multi-Statement Lambdas: Deeply-nested JSX opening tags could render past the 88-column limit (e.g. <p style={{...}}> two levels inside a <div> rendering at 90 columns) because print-width accounting drifted by one column per level of nesting. Lines now respect the width budget at any nesting depth. Multi-statement lambda bodies (lambda -> T { stmt1; stmt2; stmt3; }) also now render each statement on its own indented line instead of collapsing them onto a single logical line with no separators.
  • CLI: jac jac2js (and jac js deprecated): Added jac jac2js as the canonical Jac→JavaScript transform command, mirroring jac jac2py. jac js still works but now emits a deprecation warning on stderr and forwards to jac jac2js; it will be removed in a future release. Also fixed a pre-existing bug where JacSuperConsole.warning wrote to stdout instead of stderr.
  • Type Checker: Block-Body Lambda Return Type Inference: Block-body lambdas (lambda e: T { stmts; }) now infer their return type from return statements instead of defaulting to Unknown. Lambdas with no return statement correctly resolve to NoneType, fixing false E1103 errors on JSX event handlers like onClick={lambda e: MouseEvent { doStuff(); }}. Mixed-return paths (some branches return a value, others fall through) produce a union that includes NoneType.
  • Type Checker: Relative .pyi Re-Exports and ParamSpec Parity: Three related improvements that jointly fix cases like current_path.split(os.pathsep) where os.pathsep collapsed to <Unknown> and surfaced as a misleading E1054 "No matching overload found". (1) The module resolver's _candidate_from now probes .pyi (and __init__.pyi) alongside .py/.jac, so relative imports inside typeshed packages (e.g. from .path import (pathsep as pathsep, ...) in os/__init__.pyi) resolve to the actual sibling stub instead of returning an unextended path that fails exists(). (2) is_typevar_class / is_paramspec_class / is_type_var_tuple_class now fall back to a canonical-name match gated to stdlib stubs (is_from_stdlib_stub), so typing.ParamSpec and typing_extensions.ParamSpec -- distinct classes in typeshed -- both count as ParamSpec and _P = ParamSpec("_P") from either module produces a proper TypeVarType(is_param_spec=True). (3) Attribute access .args / .kwargs on a ParamSpec TypeVarType returns AnyType (gradual), which is the minimum needed to type-check signatures like def submit(self, fn: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs) -> Future[_T] without spurious E1031; full ParamSpecArgs / ParamSpecKwargs modelling is left as future work.
  • Type Checker: @jac/runtime Virtual Module Resolution: import from "@jac/runtime" { ... } no longer collapses to <Unknown> during static analysis. The @jac/* alias was previously only resolved by an AST-node method consulted at bundle time, so the type evaluator's resolve_relative_path_list (which routes through the module-level resolve_module) emitted W1100 "Module not found" and every imported symbol (e.g. jacIsLoggedIn -> bool) decayed to Unknown, producing spurious E1001 "Cannot assign <Unknown> to bool" errors on perfectly correct client code. @jac/* resolution is now centralized in modresolver.jac and consumed uniformly by the resolver, the type checker, and the ECMAScript bundler, so static analysis agrees with the runtime.
  • Type Checker: Relative Import Resolution and Unresolved-Name Diagnostics: Fixed relative imports like import from .components.TodoItem { TodoItem } silently decaying to <Unknown>. The path-offset correction in get_type_of_module now applies to Jac namespace packages (not just Python __init__.py parents), the package-dir fallback recognizes .cl.jac/.sv.jac/.na.jac, and added W1101 "Cannot import name '{name}' from module '{module}'" so unresolved symbols surface instead of silently flowing into JSX prop validation as Unknown.
  • LSP: Goto-Definition on JSX Component Tags: Clicking a <MyButton> tag in a .cl.jac file now jumps to the component's function declaration.
  • 2 small refactors/changes.

jaclang 0.13.5#

  • Native: Lambda Expressions and Capturing Closures: Added lambda expression support in the na (native LLVM) codespace. Simple lambdas compile to anonymous LLVM IR functions returned as function pointers. Capturing closures -- lambdas that reference variables from the enclosing scope -- pass captured values as hidden extra parameters, with automatic injection at call sites. No heap allocation required for captures. Leverages the existing indirect function pointer call infrastructure.

jaclang 0.13.4#

  • Native: Lambda Expressions: Added lambda expression support in the na (native LLVM) codespace. Lambdas compile to anonymous LLVM IR functions returned as function pointers. Leverages the existing indirect function pointer call infrastructure for invocation.
  • Type Checker: Expression-Based CFG Narrowing for Dotted Paths: The type checker now narrows dotted attribute expressions (e.g., obj.field) through the CFG backward walk, not just simple variable names. Patterns like if obj.speed is None { obj.speed = 0; } return obj.speed; now correctly resolve obj.speed to int instead of int | None. Introduced NarrowingTarget abstraction to generalize the CFG narrowing pipeline for both symbols and expression keys, extended affected_symbols to track dotted paths, and removed scope-based narrowing for dotted paths in favor of the more precise CFG analysis that accounts for assignments inside branches.
  • Refactor: Remove Scope-Based Narrowing for If/Match (CFG Handles It): Removed the redundant scope-based push/pop_narrowing_scope from if-statement and match-case handlers. The CFG backward walk now fully handles these via _find_predicate_for on IfStmt/MatchStmt predecessors. Also removed promote_inverted_narrowings and _if_body_always_terminates, as the CFG join logic naturally handles early-exit guard patterns. This fixes a false positive where Any | str was being mistyped as int by the old scope narrowing, and correctly reports previously-suppressed errors like attribute access on base types after isinstance guard returns.
  • Refactor: Move AND/OR and Ternary Narrowing from Pass to Evaluator: Progressive narrowing for AND/OR expressions (x is not None and x.bark()) and ternary branch narrowing (x if isinstance(x, T) else y) have been moved from TypeCheckPass enter/exit handlers into the type evaluator's expression evaluation. The pass now delegates via prune() + get_type_of_expression(), keeping all narrowing logic in one place. The scope stack is still used internally by the evaluator for these intra-expression narrowing cases, since the statement-level CFG cannot see between sub-expression operands.
  • Type Checker: Ambient DOM Types for JSX Event Handlers: Added dom_types.pyi with 39 ambient types covering DOM elements (HTMLElement, HTMLInputElement, HTMLFormElement, etc.) and events (Event, ChangeEvent, KeyboardEvent, MouseEvent, FocusEvent, DragEvent, etc.). These are available without import in all Jac modules, replacing the e: any workaround in JSX event handler lambdas with proper typed alternatives like e: ChangeEvent and e: KeyboardEvent.
  • Fix: Filter Comprehension Type Inference: [root-->][?:Todo] and [-->[?:Todo]] now correctly infer as list[Todo] instead of Unknown. Previously, the FilterCompr handler in the type evaluator was missing a return statement, and AtomTrailer had no branch for filter comprehension trailers, causing false E1002 return-type errors and cascading E1032 attribute-access errors on the filtered results.
  • Type Checker: Centralized Ambient Builtins via jac_builtins.pyi: All user-facing ambient names (llm, jid, jobj, printgraph, grant, revoke, allroots, save, commit, store, destroy, permission constants, restspec, schedule, ScheduleTrigger, APIProtocol) are now declared in jac_builtins.pyi as the single source of truth. The type checker resolves these through the builtins scope chain, eliminating false "undefined name" warnings (e.g., llm in by llm() expressions). Removed the redundant TYPE_CHECKING variable declarations from builtin.jac.
  • Feat: Topology Index Persists Across Sessions: The topology index (topology_index_data on root NodeAnchor) now survives Serializer round-trips via base64 encoding, enabling DB-backed graphs (MongoDB, SQLite) to retain the adjacency index across sessions instead of rebuilding from scratch. The index is excluded from _compute_hash dirty-checking since it's a derived cache, and serialization is scoped to root anchors only to avoid overhead on regular nodes.
  • Type Checker: Bare Generic Iteration Defaults to Any: Iterating over bare generic types like list, dict, or set (without type arguments) no longer produces false E1032 "Type is Unknown" errors on attribute access. Bare generics are now treated as list[Any], dict[Any, Any], etc. per PEP 484, so iteration variables resolve to Any instead of Unknown. A new W1036 warning is emitted to nudge users toward adding explicit type arguments (e.g., list[Todo] instead of bare list).
  • Lint: W3037 Auto-Fix for Unnecessary -> None: jac lint --fix now auto-removes redundant -> None return type annotations from functions and lambdas that have no return statement. Handles both regular abilities (def add -> None { ... }def add { ... }) and paren-less lambdas (lambda e: ChangeEvent -> None { ... }lambda e: ChangeEvent { ... }).
  • Fix: False W2003 Warning on by llm() Parameters: Parameters in GenAI abilities (def foo(x: str) -> T by llm()) no longer trigger spurious "defined but never used" warnings. The static analysis pass now recognizes is_genai_ability alongside needs_impl when skipping parameter usage checks.
  • Lint: Lint Warnings in Compile/LSP Pipeline and -> None Check: The linter now runs as part of the standard compile and LSP pipeline via a new JacLintCheckPass (report-only mode), so lint warnings like unnecessary pass, mutable defaults, and repeated conditions appear as editor warnings without modifying the AST. Added W3037 (unnecessary-none-return): warns when a function or lambda has an explicit -> None return type annotation but no return statement in its body, since functions implicitly return None. The jac lint report also now displays warnings alongside errors.
  • Fix: False W2003 Warning on cl def Has Variables: Assignments to has-declared variables inside nested abilities of a cl def (e.g., can with entry { items = get_items(); }) no longer trigger spurious "defined but never used" warnings. The symbol table build pass now recognizes these as state mutations of the enclosing component's reactive has variables rather than new local variable definitions. Bare assignments to has names inside obj methods still correctly warn (use self.x instead).
  • Type Checker: callable() Type Narrowing with Unified Condition Dispatch: callable(x) now narrows union types in both true and false branches -- e.g., Callable[[int], str] | str narrows to Callable[[int], str] inside if callable(x) and to str in the else branch. Works with not callable(), early returns, explicit else, and multi-type unions including None. The narrowing system was also refactored into a unified condition dispatch: a single classify_condition classifier and NarrowingKind enum replace the duplicated if/elif chains that previously existed across 5 methods (process_condition, process_negated_condition, _find_predicate_for, collect_narrowing_symbols, plus leaf extractors). Adding a new narrowing kind now requires ~25 lines across 4 touch-points instead of copy-pasting AST matching across 5 scattered methods. The core exclude_type_from_union and _type_matches_isinstance utilities were also generalized to handle non-ClassType members (e.g., FunctionType) via identity comparison, so all narrowing kinds flow through the standard _apply_narrowing path without special flags.
  • Type Checker: TypeAlias Resolution for Type Narrowing: TypeAlias-annotated definitions (glob MyType: TypeAlias = Foo | None) are now resolved to their underlying types, enabling isinstance(), callable(), and None-check narrowing to work through type aliases. Detection uses symbol-based verification (_SpecialForm shared instance + name) rather than string matching. Expression RHS is evaluated directly; string RHS (forward references like 'Callable[[A], B] | C') is parsed via Python's ast module and recursively resolved against the Jac symbol table. Stub module (.pyi) TypeAlias evaluation suppresses diagnostics since stubs may reference types the Jac resolver cannot fully handle. Previously, parameters typed with a TypeAlias received the _SpecialForm class type instead of the aliased union, making all narrowing produce <Unknown>.
  • 1 small refactor/change.
  • Cleanup: Remove Outdated __specs__ from littleX Examples: Removed deprecated obj __specs__ { static has auth: bool = False; } blocks from load_user_profiles walker in both littleX.jac and littleX_single.jac.
  • ES Codegen: jid() Moved to Client Runtime: jid() is now a proper runtime function (_jac.builtin.jid()) instead of an inline property access (x._jac_id). This provides clear, actionable error messages when called on null (e.g. server returned an error) or non-node objects, with stack traces pointing to the .jac source line. The assert_no_jac_keywords test was also improved to strip string literals before scanning, preventing false positives from English words in error messages.

jaclang 0.13.3#

  • ES Codegen: jid() Builtin for Unified Node Identity: Added jid() as a builtin function in the ES codegen, providing a consistent API for accessing node identity across both server and client code. On the client side, jid(node) emits node._jac_id in the generated JavaScript. The previous implicit .id alias (this.id = this._jac_id) on generated node class constructors has been removed in favor of the explicit jid() call.
  • Fix: Windows Client Bundle Compilation: Fixed client bundle compilation failing on Windows with Client function 'app' not found error. Added cross-platform path normalization for module hub lookups to handle Windows case-insensitivity and path separator differences. Extracted helper functions to the client bundle module for consistent path handling across the bundle builder and module introspector. The ES pass is now explicitly triggered when generated JavaScript is empty, ensuring code generation completes on Windows where the pass may be skipped during initial compilation due to re-entrancy guards.
  • Fix: Windows Console Unicode Encoding: Fixed codec encoding crashes on Windows cmd/PowerShell when printing Unicode characters (emojis, symbols). The console now detects Windows legacy terminals and replaces unencodable characters with safe fallbacks. Windows Terminal continues to use full Unicode support.
  • Fix: JIR Cache Preserves code_context for .cl.jac / .sv.jac / .na.jac Modules: The JIR binary cache now serializes the per-node code_context field (CLIENT, SERVER, NATIVE) via a new overlay byte (bit 3 of overlay flags). Previously, code_context was a postinit field not included in the JIR spec, so it always defaulted to SERVER on cache reload. This caused .cl.jac modules loaded from cache as transitive dependencies to lose their CLIENT context, making the ES code generator filter out all statements and produce empty JavaScript, breaking the admin UI and other client builds on second compilation.
  • Perf: Remove Redundant _recalculate_parents Tree Traversals: Eliminated 1–2 redundant O(n) recursive AST traversals per compiled file. UniNode.postinit() already sets parent pointers at construction time and set_kids() fixes them on mutation, making the standalone _recalculate_parents() call after every rd_parse and inside _coerce_module purely redundant. For .cl.jac/.sv.jac/.na.jac files this removes 3 full tree walks down to zero extra traversals.
  • Fix: ES Codegen Await Precedence and List Concatenation: Fixed two ECMAScript code generation bugs affecting full-stack apps. (1) Await in member access: AwaitExpression used as the object of a MemberExpression (e.g., (await fn()).map(...)) was not parenthesized, causing .map() to bind to the Promise instead of the resolved value. The ES unparser now parenthesizes AwaitExpression, YieldExpression, and other low-precedence expressions when they appear as member access targets. (2) List + in impl files: When the type evaluator cannot determine the operand type (common in .impl.jac files), list + [item] fell through to the generic BinaryExpression('+') handler, producing JS string concatenation instead of array concatenation. A new heuristic in exit_binary_expr detects ArrayExpression operands and emits [...left, ...right] spread syntax as a safe fallback.

jaclang 0.13.2#

  • Typed Interop: dict[K,V] Return Hydration and Walker Spawn Serialization: The ES codegen now supports dict[str, T] return types with automatic value hydration (Object.fromEntries(Object.entries(...).map(...))), wraps list[T] returns at call sites with .map(x => T.__from_wire(x)), and serializes typed walker has fields with __to_wire() when spawning from client code. The interop analysis pass also now correctly extracts multi-parameter subscript types (e.g., dict[str, Metric] was previously reduced to bare dict).
  • Fix: ES Codegen RecursionError on Walker/Typed Arg Fixtures: Guarded an unprotected get_type_evaluator() call in exit_func_call that caused infinite recursion when compiling fixtures with walker spawns or typed function arguments.
  • 2 small refactors/changes.
  • Type Checker: 8 Root-Cause Fixes for False Positive Errors: Eliminated several classes of false positive type errors across Jac codebases. (1) Raise/return guard narrowing: After if obj.attr is None { raise; }, dotted attribute paths like obj.attr now stay narrowed for the rest of the scope. (2) Cascading Unknown suppression: When an attribute access produces an Unknown type, subsequent accesses on that result no longer emit redundant E1032 errors. (3) Bare Callable support: Callable without type arguments now resolves to a gradual callable type (Callable[..., Any]) instead of _SpecialForm, fixing __name__ access and parameter passing. (4) Assignment in narrowed branches: if obj.field is None { obj.field = value; } now type-checks the assignment against the declared field type, not the narrowed NoneType. (5) Structural protocol matching: SupportsIndex, Sized, Hashable, Iterable, and 10 other typing protocols are now matched structurally -- any class implementing the required dunder method satisfies the protocol. Includes a str subscript fallback for __getitem__ overload resolution. (6) Any in unions: Attribute access on union types containing Any (e.g., Any | Foo) no longer reports missing attributes. (7) TypeVar in impl blocks: Module-level TypeVars used in function signatures within impl blocks are now recognized as valid generic parameters. (8) Special form type generalization: _get_special_form_type return type widened to TypeBase to support returning non-ClassType special forms like gradual callables.
  • Fix: enum.auto() Support: Using auto() in IntEnum class definitions now works correctly. Previously produced false type errors.
  • Fix: JIR Cache Preserves Impl-Defined Instance Variables: Instance variables defined in .impl.jac files (e.g., self.x in init) are now preserved after JIR cache reload. Previously, these symbols were lost because impl_mod is not serialized in JIR.
  • Fix: SyntaxWarning During Bootstrap: Fixed invalid \# escape sequence in the ECMAScript private identifier generator that produced a SyntaxWarning on every jac purge / startup.
  • Fix: JIR Cache Computes MRO for Subtype Checking: When modules are loaded from JIR cache, class types may have empty MRO (Method Resolution Order) lists since type information is not serialized. The is_sub_class check now computes MRO on demand, ensuring subtype relationships (e.g., Derived extends Base) are correctly recognized on cached module loads.
  • Fix: Scope Narrowing Leak Between impl Bodies: Guard-pattern narrowings (e.g., if x is not None { return; } promoting x to NoneType) no longer leak from one impl body into subsequent impl bodies that share the same local variable name. Previously, promote_inverted_narrowings accumulated on the parent scope stack because exit_ability traversed ImplDef bodies without isolating the narrowing scope, causing false E1030 errors like Type "NoneType" has no attribute "mem".
  • Fix: Memory Base Interface Missing Methods: Promoted get_mem, get_gc, and remove_from_gc to the abstract Memory base interface. These methods are implemented by all concrete memory types (VolatileMemory, SqliteMemory) and were being called through the base Memory type on ExecutionContext.mem, producing false E1030 errors.
  • Fix: Generic Function TypeVar Resolution: Generic functions like find_parent_of_type[T](typ: type[T]) -> T | None now correctly resolve TypeVar bindings at call sites. Previously returned Unknown because function-level type parameters were never stored, T | None collapsed to Unknown due to is_any_type() short-circuiting, and type_params were dropped during method cloning.
  • Refactor: Compiler Passes Converted from class to obj with postinit: All compiler pass classes now use the obj style with has declarations and postinit methods instead of class with custom init. This aligns passes with Jac's dataclass-like initialization pattern.
  • Refactor: Native Pass has Declarations and Duplicate Cleanup: NaIRGenPass now declares all 83 instance attributes in has with proper defaults. Removed ~500 lines of duplicate method declarations and stub implementations from primitives_native.
  • Refactor: TypeEvaluator Converted to obj Style with has and postinit: TypeEvaluator now uses explicit has declarations for all 24 instance attributes with proper defaults, replacing the manual init method with postinit.
  • Fix: Project Dependencies Now Available to Subprocesses: Packages installed via jac install (stored in .jac/venv/) are now accessible to subprocesses spawned from your code. Previously, running subprocess.Popen(["jac", "mcp", ...]) failed because the venv's bin/ directory wasn't in PATH. Now add_venv_to_path() also prepends the venv's bin/ directory to os.environ["PATH"], so commands like jac mcp work correctly when jac-mcp is installed as a project dependency in jac.toml.

jaclang 0.13.1#

  • Fix: MRO Resolution for Classes Imported Through Python __init__.py Re-exports: Inheriting from a class imported through a Python package's __init__.py re-export (e.g., from pkg import Base where pkg/__init__.py does from .submod import Base) now works correctly. Previously, the base class resolved to UnknownType, breaking the MRO and causing false "has no attribute" errors on inherited members.
  • Fix: ExecutionContext Field Types Corrected to Non-Optional: Changed system_root, user_root, and entry_node fields on ExecutionContext from NodeAnchor | None to NodeAnchor. These fields are always initialized in postinit (defaulting to system_root), so the | None was incorrect and forced unnecessary null-guard workarounds throughout access validation and anchor persistence code.
  • Fix: TypeVar Annotations in self.attr Assignments: Type annotations on self-member assignments in generic class init methods (e.g., self.value: V = input) now correctly propagate through inheritance chains. Previously, accessing such attributes in subclasses produced false "has no attribute" errors because the TypeVar type wasn't stored on the declaration node.
  • Fix: Ternary Expression Type Narrowing: The type checker now applies branch-specific narrowing inside ternary (if-else) expressions. The walker manually traverses the true branch with narrowing from the condition and the false branch with inverse narrowing, preventing false-positive type errors when isinstance guards are used in ternary expressions.
  • Fix: CFG Symbol Propagation Through Already-Linked Nodes: link_bbs now propagates newly added affected_symbols via iterative BFS to already-linked internal CFG nodes. This fixes cases where exit_if_stmt linked body nodes before _link_sequential added upstream symbols, causing the backward CFG walk to miss narrowing predicates.
  • Fix: Runtime Null Safety for user_root and visit Expressions: check_access_level now returns NO_ACCESS when user_root is None instead of crashing, and visit gracefully handles expressions that are neither NodeArchetype nor EdgeArchetype by producing an empty traversal list instead of failing.
  • Fix: Scope Narrowing for AtomTrailer Nodes: The pre-cache scope narrowing check in get_type_of_expression now handles AtomTrailer nodes (attribute access like obj.attr) in addition to Name and NameAtom nodes. Previously, attribute access expressions inside and chains and ternary branches returned stale cached types, bypassing truthiness and isinstance narrowing. This eliminates 12 false positive errors in runtime.impl.jac.
  • Fix: add_scope_narrowing Union Replacement: When an existing scope narrowing is a UnionType (e.g., from truthiness excluding None) and a more specific type arrives (e.g., from isinstance), the specific type now replaces the union instead of being silently dropped.
  • Fix: Compound or Guard Narrowing: _find_predicate_for now handles inverted predicates in or conditions (e.g., not x or not x.attr). Previously it bailed out when any or operand was inverted, preventing DeMorgan narrowing on the false branch of compound or guards.
  • Fix: String method transpilation on local vars and chained calls in .cl.jac: .lower(), .upper(), .strip() now correctly map to JS equivalents on local variables and chained calls, not just typed has variables.
  • Fix: Ternary Narrowing for Function Args, Attribute Access, and Generic Types: The type checker now eagerly evaluates IfElseExpr nodes in enter_if_else_expr so the type evaluator's narrowing scopes are active before the walker visits child nodes. Previously, walker callbacks like exit_atom_trailer and exit_func_call fired before narrowing was set up, producing false positive errors on attribute access and function arguments inside ternary branches. Additionally, when inside ternary/boolean expression contexts, scope narrowing now takes priority over CFG narrowing in the AtomTrailer handler, since CFG operates at statement granularity and cannot see ternary-internal branches. This resolves known gaps B and C in ternary narrowing and fixes false errors like isinstance(expr, list) failing to exclude list[T] variants in the else branch.
  • Fix: Stdlib Star Re-export Resolution: The type checker now follows star imports (from .submod import *) in stub files when resolving imported symbols. Previously, symbols like asyncio.Queue (defined in asyncio.queues but re-exported via star import in asyncio/__init__.pyi) resolved to Unknown, cascading into false errors on method calls like .empty() and .get_nowait(). This fixes code importing star-re-exported stdlib symbols.
  • Fix: isinstance Narrowing Preserves Generic Type Parameters: isinstance(x, list) on x: list[int] | str now narrows to list[int] instead of bare list. The scope-based isinstance narrowing paths (process_condition and IfElseExpr handler) now use _refine_isinstance_type to filter the variable's declared union type, keeping only members that match the isinstance class while preserving their type arguments. This eliminates false Unknown element types when iterating over narrowed generic containers.
  • Fix: Type Narrowing Through try/finally and while Loops: The type checker now correctly propagates narrowing through try/finally blocks and while loops. Previously, variables narrowed before a try block (e.g., via if not x { x = Foo(); }) would lose their narrowed type inside the finally block, producing false positive errors. This also fixes narrowing for the common ContextVar.get(None) / reassign pattern used in the runtime.
  • Fix: OR-Expression Result Type Narrowing: The type evaluator now excludes NoneType from non-last operands of or expressions, following Pyright's short-circuit semantics. For x or default where x: T | None, the result type is now T | type(default) instead of T | None | type(default), because None (always falsy) causes or to short-circuit to the next operand. This enables CFG assignment-based narrowing for the common x = x or [] idiom, eliminating false type errors when the narrowed variable is later used as a non-optional parameter.
  • Portability Check Pass (W6001–W6004): New compiler pass detects JS-idiomatic usage patterns that break cross-codespace portability. Warns on JS-specific globals (console, Math, JSON, Array, Promise, etc.), JS-idiomatic methods (.push(), .forEach(), .indexOf(), .splice(), etc.), JS-specific keywords (undefined, typeof, void), and JS-specific property access (.length instead of len()). Each warning suggests the portable Jac alternative.
  • 1 small change/refactor.
  • Fix: Narrowing Cache Key Uses Symbol Identity: The CFG narrowing cache in _compute_narrowed_at now uses id(symbol) instead of sym_name in its cache key. When a variable is reassigned inside a branch (e.g., if x is None { x = make_ctx(); }), Jac creates a separate symbol for the inner assignment. The old name-based key caused stale narrowing results from one symbol to be returned for the other, preventing the post-join type from reflecting the reassignment.

jaclang 0.13.0#

  • Performance: Batch Edge Prefetch: Edge traversal (-->, ->:E:->, etc.) now collects all edge/target IDs up front and loads them in two batch queries instead of one-at-a-time. No syntax changes; same --> operators, just fewer DB round-trips under the hood.
  • First-Class Fixed-Width Numeric Types: i8, u8, i16, u16, i32, u32, i64, u64, f32, and f64 are now first-class builtin types, on par with int and float. They are recognized as keywords by the lexer, parsed as BuiltinType AST nodes, and prefetched by the type evaluator -- eliminating prior special-case handling where they were resolved as plain identifiers.
  • Type Checker: TypeVar Union and Overload Resolution: Fixed three root causes that made generic method return types resolve to <Unknown>: (1) TypeVarType.is_any_type() short-circuited the | operator so _VT | None in typeshed stubs became UnknownType instead of a proper union; (2) is_annotation_type() did not recognize TypeVarType as valid for union creation; (3) added TypeVar constraint solving to overload resolution -- method-level TypeVars (e.g., _D in ContextVar.get(default: _D) -> _D | _T) are now inferred from call arguments with bounds, constraints, and consistency validation via the existing assign_type_to_type_var infrastructure. This fixes dict[K,V].get(), ContextVar[T].get(), and similar generic methods returning <Unknown> for user-defined types.
  • Type Checker: typing.cast() Special-Form: cast(T, val) now returns T instead of matching the object overload and returning Any. The type checker detects typing.cast calls and extracts the target type directly.
  • Type Checker: match/case Narrowing: The match subject type is now narrowed inside case bodies. case Dog(): narrows the subject to Dog, enabling access to subclass-specific attributes without false errors.
  • Fix: Enum Static Method Resolution: Static methods on class-based enums (e.g., class Color(Enum)) are no longer misidentified as enum members. Regenerated precompiled bytecode to include the updated build_enum_members filter that excludes Ability nodes.
  • Fix: jac start --dev Watchdog Installation: Ensures watchdog is automatically installed properly in the active environment or project's environment when running jac start --dev, preventing failing with No module named 'watchdog'.
  • Error: Explicit self in obj/node/edge/walker Methods (E2015): The type checker now reports an error when a method in an obj, node, edge, or walker archetype explicitly declares self as a parameter. In these archetypes, self is implicitly injected by the compiler; declaring it explicitly results in a duplicate parameter. Python-style class definitions are unaffected.
  • Parallel jac format: jac format now processes multiple files in parallel using ProcessPoolExecutor, significantly reducing CI time for large codebases (e.g., 11 min → ~2 min).
  • Fix: sem strings silently dropped for functions starting with 's', 'e', or 'm' (#5233): SemDefMatchPass used lstrip('sem.') to strip the sem. prefix, which strips individual characters from the set {s, e, m, .} rather than the prefix string. This corrupted function names like summarize_textummarize_text, causing the symbol table lookup to fail and the sem string to be silently discarded. Fixed by replacing lstrip('sem.') with removeprefix('sem.').
  • Hash-based anchor dirty checking: Replaced __setattr__/is_updated flag with hash-based change detection. Anchors are now snapshot-hashed on load and compared at sync time, eliminating unnecessary writes on read-only requests and automatically detecting all mutation types including in-place mutations (list.append(), dict[k]=v, set.add(), nested objects).
  • Type Checking Enabled by Default: All user modules are now type-checked during compilation. Bootstrap modules skip type checking automatically to avoid circular imports.
  • Type Checker: Enum .value/.name Resolution: Accessing .value or .name on enum instances now returns the correct type. For plain enums, the value type is inferred from members.
  • Fix: Static Analysis False Positive on Attribute Access: The "Name may be undefined" warning (W2001) no longer fires on attribute-access names (e.g., obj.value), which are member lookups, not standalone name references.
  • Type Checker: Walrus Operator Narrowing: if (x := expr) is not None: and if isinstance((x := expr), T): now correctly narrow x inside the block.
  • Fix: jac run Python Parsing Edge Cases: Resolved crashes and translation issues involving decorated classes (e.g., @torch.compile()), f-string format specifiers, and super().__init__() calls by ensuring proper AST traversal and restoring __init__ when targeting super().
  • Type Checker: type[T] Member Access: Accessing class-level members (e.g., ClassVar) on type[T] parameters now works correctly. cls: type[MyClass]cls.my_class_var resolves to MyClass's members.
  • Type Checker: Property Support: @property and @cached_property now correctly type-check. Accessing obj.my_property returns the property's return type instead of FunctionType.
  • Fix: Property on Union Types Returns Function Type: Accessing a @property on a union type (eg:, after isinstance(x, (Dog, Cat))) now correctly returns the property's return type. Previously, the union type code path skipped property unwrapping and returned the raw FunctionType.
  • Fix: Property Return Type Self Resolution: Properties returning Self (e.g., Path.parent) now correctly resolve to the owning class instance type. Previously, the raw Self TypeVar was returned without specialization, causing false Cannot access attribute errors on chained access like Path(...).parent.parent.
  • Fix: list(Generator) Type Inference: list(Generator[Item, None, None]) now correctly returns list[Item] instead of list[NoneType]. TypeVar resolution through multi-level generic inheritance (Generator -> Iterator -> Iterable) now works correctly.
  • Fix: Static Methods on Class-Based Enums: Static methods on IntEnum/StrEnum classes now correctly return their declared type instead of <Unknown>.
  • Type Checker: Ternary isinstance Narrowing: [x] if isinstance(x, T) else x now correctly narrows x to T in the true branch and to the complement type in the else branch. Hover info shows the narrowed type.
  • Fix: Generic Constructor Type Inference: set(my_list), enumerate(items), and similar generic constructors now correctly infer type parameters from iterable arguments (e.g., set(list[Item])set[Item]). Previously returned unparameterized types.
  • Type Checker: Generic Inheritance & Bidirectional Inference: Added MRO-aware type argument resolution (specialize_for_base_class, build_solution_from_specialized_class) so multi-level generic inheritance chains are properly tracked. _assign_class now validates type args through the MRO with covariant/contravariant/invariant variance. type[X] annotations are now covariant (type[SubClass] assignable to type[BaseClass]). FunctionType.specialize and specialize_member_type use transitive TypeVar resolution. infer_type_args walks inherited constructors. Container literals (list, dict, set, tuple) now support bidirectional type inference via expected_type propagation from return statements, assignments, and function arguments, with element-level validation before adopting the expected type.
  • Type Checker: Parameterized Types in Error Messages: ClassType.__str__ now displays type arguments for parameterized types (e.g., list[int], dict[str, bool]) instead of bare class names. Error messages like Cannot return <class list>, expected <class list> now read Cannot return list[Dog | Cat], expected list[int], making type mismatches immediately actionable.
  • Client-Side Error Reporting: Unhandled JavaScript errors and promise rejections in Jac client apps are now automatically captured and forwarded to the server via POST /cl/__error__, where they are logged through both the jaclang.client_errors logger and the dev console. Global error handlers (window.onerror, unhandledrejection) are installed at app initialization, and the ErrorBoundary fallback component also reports caught errors. Works with both the stdlib HTTP server and jac-scale (FastAPI).
  • Centralized Source Mapping: Added source_mapping module to runtimelib with VLQ encode/decode, source map generation/parsing, and two-layer resolution (bundle → compiled JS → .jac). Server error endpoints now resolve JS stack traces back to .jac file locations with exact line numbers. The ES unparse pass tracks output-line → source-line mappings per-node via es_to_js_with_map(), with proper newline accounting in gen_block_statement for accurate nested statement mapping. Includes SourceMapper caching with mtime-based invalidation, ErrorRateLimiter for deduplication (10s window) and rate capping (20/min), and read_source_snippet() for diagnostics integration.
  • Centralized Source Mapping: Added source_mapping module to runtimelib with VLQ encode/decode, source map generation/parsing, and two-layer resolution (bundle → compiled JS → .jac). Server error endpoints now resolve JS stack traces back to .jac file locations. The ES unparse pass tracks output-line → source-line mappings via es_to_js_with_map().
  • Automatic TYPE_CHECKING Import Guards: The compiler now detects imports that are only used in type annotations (parameter types, return types, field types) and automatically wraps them in if typing.TYPE_CHECKING: guards in the generated Python. This eliminates the need for manual if TYPE_CHECKING { ... } blocks in Jac source. Mixed imports (some items type-only, some runtime) are automatically split. Existing manual TYPE_CHECKING blocks continue to work.
  • Fix: jac check and LSP Silently Swallowed Errors for Jaclang-Internal Files: Files inside the jaclang/ package directory (e.g., compiler test fixtures) had their type errors routed to the compiler's internal_program instead of the caller's JacProgram, causing jac check to report PASSED and the LSP to miss diagnostics. Added force_target_program option to CompileOptions so user-initiated operations always record errors on the correct program. Removed the LSP's internal_program error aggregation workaround.
  • Fix: Formatter Inline Comment Swallows Next Argument: Fixed a bug where jac format would merge the next function argument into an inline comment when the comment appeared after a comma in a multi-line call (e.g., candidate[:-4], # strip trailing .jac would absorb the following os.path.abspath(base_dir) into the comment text). The CommentInjectionPass now upgrades soft line breaks to hard line breaks when they follow an inline comment, ensuring subsequent tokens always start on a new line.
  • Fix: Diagnostic Underlines for Multi-Line Spans: Fixed jac check rendering absurdly wide ^^^^^ underlines when a diagnostic pointed at a node spanning multiple lines (e.g., W2052 on an entire except block). The W2052 warning now points at the exception type name token instead of the whole block, and the underline renderer clamps caret width to the end of the source line for any multi-line span.
  • Fix: Native Cross-Module Method Calls: Calling a method on a struct type imported from another .na.jac module (e.g., lx.next_token(), c.increment()) was silently dropped, leaving the target variable as a null pointer and producing runtime crashes. Methods on imported struct types are now correctly resolved and emitted.
  • Lintfix Improvement: Add lintfix format to context menu in VS Code.
  • Improve: jac format Grouped Error Summary for Syntax Errors: jac format now displays a grouped FAILURES section (file path + error messages) and a failed files summary when files have syntax errors, consistent with jac check output. Previously, errors were printed inline without grouping.
  • Fix: Garbled Emojis and Markup in jac --version Banner: Non-ASCII characters and emojis now render correctly in the version banner.
  • CLI: jac run --diagnostics Flag: The -e / --diagnostics flag on jac run now accepts a verbosity level: error (default -- fail on errors with full details, suppress warnings), all (show errors and warnings), or none (suppress all diagnostics). By default, jac run now fails with exit code 1 when compilation errors are detected, printing full diagnostic details. This can be configured project-wide via [run].diagnostics in jac.toml. Replaces the previous --show-errors boolean flag.
  • root Removed as Language Keyword: root is no longer a reserved keyword (KW_ROOT) in the Jac grammar. It is now an ambient built-in name resolved through the runtime builtin module's lazy __getattr__ mechanism (same as jid, jobj, save, etc.), returning Jac.root(). Existing code using root continues to work unchanged. Backtick escaping (`root`) is no longer necessary when using root as an identifier.
  • Fix: Impl Matching with Forward Declarations: impl MyClass.method now correctly matches declarations when MyClass has forward declarations or is reassigned elsewhere. Previously failed with E2009.
  • Fix: Goto Definition for Import Paths and Imported Items: Goto definition now works correctly on all parts of an import statement. Previously, intermediate path segments failed to resolve because each was resolved independently; now the full dotted path is resolved once and intermediate paths are derived by walking up the directory tree.
  • Fix: TypeVar Deduplication and Transitive Propagation in Generic Specialization: Fixed two bugs in the type checker's generic TypeVar resolution. (1) _extract_type_params now deduplicates TypeVars by name when a class inherits from multiple generic bases with overlapping TypeVars (e.g., Mapping(Collection[_KT], Generic[_KT, _VT_co]) previously collected _KT twice, causing positional mismatches). (2) build_type_var_solution now propagates TypeVar bindings transitively through the base_classes chain, so renamed TypeVars across inheritance levels (e.g., _VT_co in Mapping mapped through _VT in MutableMapping) are correctly resolved when specializing methods like dict[str, int].get().
  • Fix: Generic[] Base Class Canonical TypeVar Ordering: _extract_type_params now processes Generic[...] base classes first to establish the canonical type parameter order (PEP 484). Previously, TypeVars were collected in encounter order across all bases, so dict_values(ValuesView[_VT_co], Generic[_KT_co, _VT_co]) produced [_VT_co, _KT_co] instead of [_KT_co, _VT_co], swapping key and value types for dict.keys() and dict.values().
  • Fix: No E1004 for Ellipsis Stub Bodies: Functions with ellipsis-only bodies (...;) are now recognized as stubs and no longer trigger E1004.
  • Fix: jac check on .impl.jac / .test.jac Files Produced False Errors: Running jac check directly on an annex file (e.g., runtime.impl.jac) compiled it as a standalone module, bypassing the annex pipeline (DeclImplMatchPass, scope linking) and producing hundreds of false type errors.
  • Fix: JIR Cache Corrupted Function Signatures: The decl/impl matching pass replaced parameter nodes with references outside the AST tree, causing JIR serialization to drop all parameter info. Imported functions loaded from cache appeared to have zero parameters, producing false argument-count errors and incorrect semantic highlighting.
  • Fix: JIR Cache Lost Generic Class Fields: Generic type inference (e.g., Box[int].get() returning int) failed on cached modules because Archetype.body was serialized with the wrong field kind and restored as None. Now works correctly on both fresh and cached runs.
  • Fix: JIR Cache Not Invalidated on Compiler Version Change: is_module_cache_valid only checked file mtimes, so stale .jir caches written by a buggy compiler persisted across upgrades. The cache now reads the JIR header and rejects files whose jaclang_version_hash or Python version doesn't match the running compiler.
  • Fix: False "Name may be undefined" on Keyword Arguments: The static analysis pass (W2001) incorrectly flagged keyword argument names (e.g., name= in func(name="check")) as potentially undefined variables. Keyword argument keys are now skipped.
  • Fix: User Module JIR Cache Now Project-Local: User module .jir cache files are now stored in the project's .jac/cache/ directory (or .jac/cache/ next to the source file when outside a project) instead of the global ~/.cache/jac/jir/modules/ directory. Compiler and bootstrap caches remain global.
  • Enforce Type Annotations on Function Parameters (E0052): The ASTValidationPass now reports an error when a function or method parameter is missing a type annotation. Lambda parameters are exempt (types are inferred). This catches missing annotations early during AST validation, before the type checker runs.
  • Fix: IntEnum/StrEnum Member Type Checking: IntEnum and StrEnum members now work correctly as function arguments and variable assignments. Previously, code like check_level(AccessLevel.READ) incorrectly failed with "Cannot assign int to AccessLevel".
  • Fix: Lambda Return Type False Positive: return inside a lambda now type-checks against the lambda's own return type, not the enclosing function's. Previously, lambda -> dict { return {...}; } inside a -> JsxElement function would incorrectly error with "Cannot return dict, expected JsxElement".
  • Fix: JIR Cache Lost List Iteration Types: Iterating over a typed list (e.g., for anchor in node.edges) resolved correctly on the first compile but produced Type is Unknown errors on subsequent cached runs. The IndexSlice.Slice inner class was not a UniNode, so the JIR serializer could not assign it a node ID and silently dropped all slice data. Slice is now a proper UniNode, ensuring IndexSlice.slices survives cache round-trips.
  • Error: Duplicate Method Definitions in Class Body (E0076): The ASTValidationPass now reports an error when a class (obj, node, edge, walker, or class) body contains two or more methods with the same name. Previously, the second definition silently shadowed the first with no diagnostic.
  • Topology Index for Graph Query Optimization: Edge-op chains with type filters (-->:EdgeType:-->[?:NodeType]) now use a compact adjacency index stored on root nodes to resolve which nodes match before fetching from the database. This avoids loading all intermediate edges and non-matching nodes, reducing L3 (SQLite) fetches by up to 91% and improving query latency up to 7.6x on large graphs. The index is maintained automatically on connect/disconnect/destroy and encoded as a compact binary blob on the root anchor. Enabled by default; can be disabled with [run] topology_index = false in jac.toml.
  • Post-Hoc Filter Fusion for Topology Index: Post-hoc type filters ([root-->:Edge:->][?:NodeType]) are now fused into the preceding edge-op's refs() call at codegen time, so the topology index can optimize them the same way as inline filters (root-->:Edge:->[?:NodeType]). Both forms now produce identical results and benefit from index-accelerated queries.
  • Fix: typing.Any Attribute Access: Accessing attributes on Any-typed values no longer incorrectly raises E1030.
  • Fix: object Type No Longer Treated as Any: Accessing unknown attributes on object-typed values now correctly raises E1030. Previously, object was treated like Any, allowing arbitrary attribute access.
  • Fix: Final[T] Attribute Access: Accessing members on Final[T]-typed values now works correctly (e.g., Final[list[int]].count()).
  • Type Checker: Function Type Attributes: Accessing __code__, __name__, __module__, and other function attributes on Callable types now works correctly. Previously returned <Unknown>.
  • Type Checker: OR Short-Circuit Type Narrowing: The type checker now narrows types through or short-circuit evaluation. In patterns like not x or x.method() and x is None or x.method(), the right-hand operand is evaluated in a context where x is known to be truthy (not None), so attribute access and method calls are accepted without false errors. Also works for self.field (AtomTrailer) narrowing and not isinstance(x, T) or x.method() patterns. Narrowing is scoped to the or expression and does not persist past it.
  • Fix: Walrus isinstance Narrowing Lost After Nested Control Flow: isinstance((x := expr), T) narrowing was lost after nested if/while blocks inside the guarded scope. The scope-based narrowing path failed because get_narrowing_key did not unwrap AtomUnit (parentheses) around the walrus expression, so _extract_isinstance_info returned None and no scope narrowing was stored. Added AtomUnit unwrapping to get_narrowing_key.
  • Type Checker: Ternary Else-Branch Type Narrowing: The type checker now applies inverse narrowing in the else branch of ternary (if-else) expressions for is None, is not None, and truthiness conditions. Previously only isinstance conditions were narrowed in the else branch. [] if (x is None) else x now correctly narrows x to exclude None in the else branch, and default if (not x) else x narrows x to non-None in the else branch.
  • Compiler: Unified Backend Analysis via UniTreeEnrichPass: Backend-neutral analysis (native compatibility detection, type tags for LLVM codegen, JS declaration hints for ECMAScript codegen) is now pre-computed once by UniTreeEnrichPass during IR generation and stored on AST nodes. Backend passes (NativeCompatCheckPass, NaIRGenPass, EsastGenPass) consume these pre-computed values instead of re-deriving them, eliminating redundant AST walks. NativeCompatCheckPass is now a pure read-and-promote pass with no analysis logic.
  • Type Checker: ParamSpec and TypeVarTuple Support: P = ParamSpec('P') and Ts = TypeVarTuple('Ts') now produce proper TypeVarType entries in the symbol table (with is_param_spec=True) instead of <Unknown>. Callable[P, T] no longer emits a false E1071 error. ParamSpec and TypeVarTuple are tracked via the prefetch mechanism (same as TypeVar) so identity checks use type-system identity rather than string matching.
  • Fix: Module-Level Dunder Variables: __name__, __file__, __doc__, __package__, and __spec__ are now declared in jac_builtins.pyi with their correct types. Previously, getLogger(__name__) and similar calls produced E1053 because __name__ resolved to <Unknown>.
  • 8 small refactors/changes.
  • Request-Scoped Execution Context:Introduced request-scoped execution contexts using ContextVar in JacRuntime.get_context(), enabling isolated per-request state and L1 caches in web environments while preserving global context behavior for CLI and tests.
  • Type Checker: Warn on Return Value in Event-Driven Abilities (W2014): The type checker now emits warning W2014 when an event-driven ability (can X with Y entry) uses return <value>. Since walker-triggered abilities ignore return values, the warning guides developers to update node or walker fields directly instead. Plain return; (no value) and return None; are not flagged.
  • Fix: jac py2jac Performance on Large Files: Fixed exponential slowdown when converting large Python files to Jac. A 10k-line file now converts in ~10s instead of 37s (3.9x faster), and a 20k-line file in ~16s instead of ~3 minutes (11x faster).

jaclang 0.12.2#

  • Fix: os.path.dirname() Type Check Error: Calling os.path.dirname() no longer fails with "No matching overload found". This also fixes other stdlib functions accessed through wildcard imports (e.g., os.path, datetime) that have multiple @overload signatures.
  • Type Checker: Constrained TypeVar Support: TypeVars with explicit constraints (T = TypeVar("T", Foo, Bar)) now validate operations against all constraint types.
  • Fix: Generic Iterator Type Inference: enumerate, zip, and other generic iterators now correctly infer element types from their arguments. Previously, version-dependent overloads in typeshed caused type resolution to fail.
  • Scheduling: DYNAMIC Trigger Support: @schedule(trigger=DYNAMIC) now attaches a spec and delegates execution to a registered _dynamic_schedule_handler (e.g. jac-scale) instead of raising NotImplementedError.
  • 2 small refactors/changes.
  • Fix: Formatter Comment Displacement in Multi-Assignment Globals: Fixed a bug where jac format would displace comments inside multi-assignment glob declarations to the end of the file. The CommentInjectionPass now handles GlobalVars nodes (which use Align layout) alongside ArchHas nodes. Added safety error E5051 that blocks saving when comment displacement is detected.
  • Fix: jac test Robustness and --test_name Space Support: jac test myfile.jac --test_name "my test name" previously ran 0 tests silently. It now works the same as --test_name my_test_name. Also improved error messages when the file is missing or --test_name is used without a filepath.
  • Inline Diagnostic Suppression: Added # jac:ignore[CODE] syntax to suppress specific diagnostics on individual lines, giving developers fine-grained control over which warnings and errors to silence.
  • Fix: Improved Error for Assigning to Built-in References: Assigning to root or super (which compile to function calls, not variables) now emits a clear error E0024 with help text suggesting backtick escaping, instead of crashing with a cryptic Python ValueError. Bytecode compilation failures are also now caught and reported as E5043 with file and line context.
  • ASTValidationPass: Post-Parse Semantic Checks: Moved 12 semantic checks from the parser into a dedicated ASTValidationPass that runs after parsing, following the modern compiler pattern of "parse permissively, validate later". The parser now focuses purely on tree construction while the validation pass provides consistent, context-rich error messages for structural violations (e.g., pass/new keyword usage, parameter ordering, empty match/switch/enum/try bodies, walrus operator LHS). All validations emit structured diagnostic codes (E0010, E0020, E0023, E0031, E0040–E0051) with inline suppression support via # jac:ignore[CODE].
  • Bracket Filter Comprehension Syntax [?:Type, cond]: Introduced new bracket-based filter comprehension syntax [?:Type, cond] as a replacement for the parenthesized (?:Type, cond) form. The new syntax works uniformly in all contexts: standalone (my_list[?:Foo, val<3]), after edge traversals ([root-->][?:A]), and inside edge ref chains ([root-->[?:A]]). The old (?:...) syntax is now deprecated and emits warning W0061 with guidance to migrate. The formatter automatically normalizes old syntax to the new bracket form on jac format.
  • Fix: Layout Pass Warnings Report Correct Source Locations: Layout-compatibility warnings (W5031, W5032) now point to the actual field declaration instead of line 1 of the file. The _validate_obj_fields method was not passing the field AST node to emit, causing it to fall back to the module root location.
  • 24 New Diagnostic Checks Across 3 Passes: Added 24 new compiler diagnostics covering context validity, semantic analysis, and lint patterns. ASTValidationPass (7 checks): yield outside function (E0055), break/continue outside loop (E0058), nonlocal at module level (E0062), duplicate keyword argument (E0065), positional after keyword argument (E0066), bare except not last (E0071), duplicate base class (E0075). StaticAnalysisPass (8 checks): division by zero (W2070), None comparison with ==/!= (W2058), self-assignment (W2063), f-string without placeholders (W2074), redundant boolean comparison (W2075), overly broad except (W2052), return in finally (E2055), disengage outside walker (E2083). JacAutoLintPass (9 checks): unnecessary pass (W3020), unnecessary else after return (W3021), nested if-to-elif (W3022), return-bool simplification (W3023), repeated condition (W3024), identical branches (W3025), too many parameters (W3030), is with literal (W3035), mutable default argument (W3036).

jaclang 0.12.1#

  • Automatic Jac Import Hook via .pth File: Installing jaclang now automatically registers a lightweight lazy import finder at Python startup via a .pth file. This means .jac modules can be imported from Python without needing import jaclang first. Jac imports Just Work. The lazy finder adds ~0.1ms to non-Jac Python startup and only triggers the full jaclang bootstrap on first .jac import.
  • Fix: TOML Serializer Preserves Special Chars and Env Vars: Fixed two bugs in the jac.toml serializer triggered when programmatically saving config changes: (1) Table headers containing special characters (e.g., [plugins.client.npm.auth."//npm.pkg.github.com/"]) now retain proper quoting instead of being written unquoted, and (2) Environment variable placeholders like ${NODE_AUTH_TOKEN} are preserved as-is instead of being interpolated to their runtime values. Previously, these bugs would corrupt jac.toml files when dependencies were auto-updated.
  • Fix: With Statement Alias Type Inference: with open(...) as f now correctly types f instead of Unknown. The type binding was moved from exit_with_stmt to enter_with_stmt so the alias type is set before the body is visited.
  • Fix: Tuple Unpacking in For Loops: for (a, b, c) in list[tuple[A, B, C]] now correctly infers types for unpacked variables instead of UnknownType.
  • Refactor: GUEST Constant for Guest Username: Added a GUEST = '__guest__' constant to Constants enum and replaced hardcoded '__guest__' strings in the stdlib HTTP server with Con.GUEST.value for improved maintainability and consistency.
  • Fix: Native Cross-Module Global Variable Access: Module-level globals declared in one .na.jac file are now correctly accessible from importing modules. Previously, accessing such a global caused a segfault at runtime.
  • 16 small refactors/changes.
  • Fix: HMR Recursive recompilation: Fixed client-side code recursive recompilation process, preventing cyclic recompilation, and ensuring that all dependencies are up to date.
  • Fix: Errors in .impl.jac Files Now Reported by jac check and jac run: Undefined names and unreachable code inside .impl.jac files were previously silently ignored. They are now correctly reported as warnings, pointing to the exact file and line.
  • Fix: HTTP Server Authentication for Imported :pub Functions: Fixed server incorrectly requiring authentication (401) for imported :pub functions. The server now inspects source file ASTs to determine access levels for imported function endpoints, matching the existing behavior for imported walkers.
  • Fix: Python Package Imports: Fixed two import bugs. (1) import from mypkg { MyClass } now works when mypkg/__init__.py re-exports via from .mymod import * - previously the type checker couldn't find MyClass. (2) import from mypkg { subpkg } now correctly types subpkg as a module - previously it failed when subpkg is a sub-package inside mypkg.
  • Fix: Type Checker JIR Cache Compatibility & ClassVar Support: Fixed symbol resolution and _SpecialForm detection for JIR-cached modules. Added ClassVar[T] unwrapping. For-loops over Any/object/TypeVar no longer produce false iterable errors.
  • class def Syntax and Self Type for Classmethods: Added class def as a first-class classmethod modifier (alongside def and static def), with Self as a polymorphic type reference that resolves to the enclosing archetype. Self maps to cls in classmethods, type(self) in instance methods, and the class name in type annotations. Works across all archetype kinds (obj, node, edge, walker), generics, impl blocks, and inheritance chains. Existing @classmethod decorator usage remains supported.
  • Compiler Warns on @classmethod/@staticmethod in obj Definitions: Using @classmethod or @staticmethod inside obj, node, edge, or walker now emits a warning. Use the static keyword instead, or class def for classmethods. Compilation warnings are now also surfaced during jac run.
  • Refactor: Console Uses Raw ANSI Codes: Replaced jacpretty markup parsing with direct ANSI escape codes. User data passes through unchanged; colors auto-disable in CI/pipes/non-TTY environments.
  • Fix: Union Type Member Access Errors: x.attr on a union type now errors when the attribute is missing from any variant (previously silently returned UnknownType). Reports which variant(s) lack the attribute.
  • Type Checker: TypeVar Validation: Added TypeVar checks - name must match the variable (e.g. T = TypeVar("T"), not T2 = TypeVar("T3")), must be assigned to a simple name (not d["k"] = TypeVar("T")), and must have zero or 2+ constraints (not TypeVar("T", str)).
  • Type Checker: TypeVar Scope Validation: TypeVars are now validated at usage sites. A TypeVar must be declared via Generic[T] on the enclosing class, or appear in the enclosing function's signature. Using a TypeVar at module level, in a nested class that re-declares an outer TypeVar, or in a runtime context (e.g. list[T]()) is now an error.
  • Type Checker: TypeVar Variance Validation: Covariant TypeVars (covariant=True) are now rejected as direct method parameter types when class-scoped (e.g. def f(self, a: T_co) is an error). Contravariant TypeVars (contravariant=True) are rejected in method return types, including inferred returns (e.g. def f(self) -> T_contra is an error). Both checks are skipped when the TypeVar is not class-scoped, and nested positions like list[T_co] are allowed.
  • Type Checker: Unbounded TypeVar Operation Validation: Operations on unbounded TypeVar values are now errors. Attribute access (except universal ones like __class__), calls, subscripts, binary/unary ops, augmented assignment, await, and iteration (for x in t) all produce errors when the operand is an unbounded TypeVar (e.g. a + 1 where a: T is an error).
  • Type Checker: Bounded TypeVar Operation Validation: TypeVars declared with bound= (e.g. T = TypeVar("T", bound=Foo)) now use the bound type for all operation checks. Attribute access, calls, subscripts, binary/unary ops, augmented assignment, await, and iteration are validated against the bound type's methods - so a.var1 is ok if Foo has var1, but a.var2 errors if Foo has no var2. Union bounds (e.g. bound=Union[Foo, Bar]) are not yet supported and are treated as unbounded for now.
  • Fix: Module-Level Overload Resolution: math.floor(), math.ceil() and other module-level overloaded functions now correctly resolve all @overload signatures instead of only the first.
  • Fix: Parameter Type Highlighting and Go-to-Definition: Types used in function parameters (e.g. uni.Module) now highlight correctly and support go-to-definition in .jac declaration files.
  • Stdlib Protocol Detection: Added Pyright-style ModuleSourceFlags for production-grade stdlib type detection. Protocol types like _SupportsFloor and _SupportsTrunc are now properly recognized, enabling math.floor(3.7) and math.trunc(4.9) to type-check correctly.
  • Fix: Native Cross-Module Method Calls: Calling a method on a struct type imported from another .na.jac module (e.g., lx.next_token(), c.increment()) was silently dropped, leaving the target variable as a null pointer and producing runtime crashes. Methods on imported struct types are now correctly resolved and emitted.
  • Fix: jac format --lintfix File Deletion on Parse Errors: Fixed a critical bug where jac format --lintfix would completely wipe out file contents when encountering parse errors. The formatter now preserves the original file when parse/lex errors are present, while still allowing files with type errors (but valid syntax) to be formatted normally. Added a safety check in format_single_file() that prevents writing empty formatted output to disk.
  • CFG Build Pass Rewrite: Rewrote the control flow graph construction pass from a fragile to_connect worklist design to a clean entry/exit visitor pattern with type-specific handlers (exit_if_stmt, exit_while_stmt, etc.). The new pass is stateless (no mutable pass-level state), explicitly handles 20 control flow constructs (vs 6 previously), and adds short-circuit boolean wiring for and/or conditions. Promoted MatchCase, SwitchCase, Test, and BoolExpr to CFG nodes for proper edge modeling. Fixes missing edges for try/except/finally, with, switch/case, match/case, and nested if-without-else inside compound statements. Unreachable code after raise/disengage is now correctly disconnected.
  • Fix: Raw ANSI Codes in Error Output: Fixed [0;31m escape fragments appearing as literal text in terminal error messages. pretty_print(colors=True) was injecting raw ANSI codes that conflicted with the Rich-based console from jac-super. Error formatting now delegates all styling to the console layer.
  • Stricter RD Parser Enforcement: The recursive-descent parser now enforces missing semicolons as errors instead of silently accepting them. Docstrings inside function/test bodies that belong at element level are reported and must be placed before the signature as docstring_target. Keywords used as parameter names now produce clear error messages suggesting backtick escaping (e.g., `default). Builtin type names (str, int, float, list, tuple, set, dict, bool, bytes, any, type) are treated as contextual identifiers and can be used as parameter names without escaping.
  • Native: Function Pointer Support for C FFI Callbacks: Jac def functions can now be passed as raw function pointers to C library calls in native code, enabling callback-based C APIs (e.g. libuv timers, async I/O) to be driven directly from Jac.
  • Fix: Match Case Crash on Empty Wildcard Body: Fixed list index out of range crash when a match/case block has a bare ; (empty statement) as its only body, e.g. case _: ;.
  • Improved Internal sv Compiler Error Diagnostics: Helper added that raises a structured ICE with source file, line, column, and node type instead of a bare list index out of range.
  • Fix: Type Checker Crash on Final[UnionType]: Fixed crash when type checking Final[int | str] annotations. Unwrapping Final[T] now correctly handles union types instead of failing with 'UnionType' has no attribute 'shared'.

jaclang 0.12.0#

  • 27 small refactors/changes.

  • Fix: Formatter Semicolon & Decorator Spacing: Fixed spacing bugs in the formatter where @ decorators produced @ decorator instead of @decorator, and statement semicolons produced raise ; instead of raise;.

  • Fix: Type Checker Validates Args Against Parameterless init: The type checker now correctly reports an error when arguments are passed to a constructor whose init takes no parameters. Named args raise Named argument does not match any parameter and extra positional args raise Too many positional arguments. Calling with no args (MyObj()) remains valid.
  • Automatic Port Fallback for jac start: When starting the built-in HTTP server, if the specified port is already in use, the server now automatically finds and uses the next available port instead of crashing with "Address already in use". A warning message displays when using an alternative port. The on_ready callback signature updated to Callable[[int], None] to pass the actual bound port.
  • Fix: LSP Impl File Diagnostics: Editing .impl.jac or .test.jac now shows errors correctly across all related files.
  • Fix: Type Checker Support for __getattr__: Classes defining __getattr__ no longer produce false "has no attribute" errors. Dynamic attribute access now correctly resolves to the __getattr__ return type, and Any is callable (enabling proxy patterns like console.error("msg")). IDE hover shows dynamic attributes as (dynamic attribute) name: type.
  • HMR Terminal Output Cleanup: Styled HMR logs with console.success/error/warning and stripped absolute paths from compile errors.
  • Fix: Implicit run Not Detecting Flags Before Filename: jac --autonative file.jac failed to insert the implicit run subcommand because the detection only checked if the first argument was a .jac file. Now scans all arguments for a .jac/.py file, so flags like --autonative and --no-cache before the filename are correctly passed through to jac run.
  • Unified JIR Cache: Single Binary Cache File Per Module: Introduced JIR (Jac IR), a compact binary format that unifies all per-module caches -- type-checked AST, bytecode, MTIR, LLVM IR, and interop metadata -- into a single .jir file under ~/.cache/jac/jir/. The format uses a 32-byte header, zlib-compressed AST payload, and optional TLV sections for each artifact type. On subsequent jac check or jac run invocations, cached modules are deserialized directly instead of being re-parsed, scope-built, and type-inferred, yielding 1.8-2.5x speedup on real compiler files. Cache entries are invalidated automatically via mtime comparison against source, impl, and variant files. The old DiskBytecodeCache and precompiled.jac mechanisms have been removed in favor of this single unified format. A new jac gen-jir-registry command (itself a Jac module) auto-generates the 141-type node registry used for binary serialization, with a --verify mode for CI enforcement.
  • AST Declarative Field Conversion: Converted all 143 AST node classes in unitree.jac from manual def init constructors to declarative has field declarations, enabling proper dataclass field inheritance across the node hierarchy. Token hierarchy classes retain manual init with direct field setup to avoid __post_init__ MRO dispatch issues. Bootstrap transpiler (jac0.py) updated with by postinit parsing support, postinit to __post_init__ mapping, and conditional kw_only=True for subclassed nodes.
  • Type System Improvement: Fixed type narrowing not working correctly inside while loops, for loops with break/continue, and loop else blocks.
  • Fixed-Width Numeric Types Promoted to Language Level: i8, u8, i16, u16, i32, u32, i64, u64, f32, and f64 are now recognized as built-in types across all backends, not just native codespace. The type checker resolves them, IDE hover/autocomplete works, and arithmetic between fixed-width types follows width-based promotion rules (e.g., i8 + i32 promotes to i32). On the Python backend they behave as int/float; on the native backend they map to exact LLVM IR types.
  • Fix: Match-Case & Walrus Type Narrowing: Variables inside match/case blocks now correctly narrow to the matched type. Walrus operator (if (x := get_optional())) now narrows x to exclude None in the true branch.
  • Fix: Formatter Comment Injection for na {} Blocks: Fixed a bug where jac format would orphan comments inside na {} (native) blocks, dumping them at the end of the file.
  • Fix: Native Empty Dict/List {} in Struct Constructor Null Pointer: Passing an empty dict or list literal as a keyword argument in a struct constructor (e.g. Container(d={})) no longer stores a null pointer in the field. The compiler now falls back to helpers["new"]() when _codegen_dict_val/_codegen_list_val returns None for an empty literal, matching the existing fix for global variable initializers.
  • Native Primitives: Set Algebra & Dict setdefault: Implemented 6 new native LLVM emitters -- set.symmetric_difference, set.update, set.intersection_update, set.difference_update, set.symmetric_difference_update, and dict.setdefault. Native primitive coverage rises from 47% → 49% implemented (147/299) and 45% → 47% tested (140/299), with SetEmitter at 61% and DictEmitter at 81%.
  • Native Primitives: Set Operators & Builtins: Added full set operator dispatch (|, &, -, ^, ==, !=, <=, <, >=, >) and augmented assignments (&=, -=, ^=) to the native LLVM backend, plus sum, any, all, and divmod builtins. SetEmitter reaches 100% native coverage (31/31). Overall native primitive coverage rises from 49% → 55% implemented (163/299) and 47% → 52% tested (156/299).
  • Native Primitives: List Operators, Frozenset & Sorted/Reversed Builtins: Added list * int repetition, and all six list comparison operators (==, !=, <, >, <=, >=) to the native LLVM backend. frozenset[T] now resolves to the same LLVM struct as set[T], giving it full operator and method coverage transparently. sorted() and reversed() builtins are now implemented (copy + in-place sort/reverse).
  • Native Primitives: 80% Coverage Milestone: Native LLVM primitive coverage reaches 80% (240/299 operations implemented and tested), up from 55%.
  • Native Primitives: 84% Coverage: Added 6 native emitters (int.to_bytes, int.from_bytes, float.fromhex, str.format, dict.items, ascii) with static method dispatch for class-level calls (int.from_bytes(), float.fromhex()).
  • Native slice() Builtin & List/String Slicing: slice() constructor, .start/.stop/.step attribute access, and lst[1:3]/s[1:3] slice subscript syntax now work in the native LLVM backend.
  • Implicit self in impl Signatures & Generic Ability Support: Updated the bootstrap transpiler (jac0.py) to skip generic type parameters (def foo[T, E](...)).
  • Fix: Native for k in dict[K,V] Loop Body Elided: Fixed for k in d over a dict function parameter emitting no loop IR - the dict/set parameter type was never registered in var_dict_type, causing _codegen_for to silently skip the body.
  • Native Codegen: del d[k] and d.remove(key) for Dicts: del d[key] and d.remove(key) now correctly remove entries from native dicts. Previously both operations were silently dropped, leaving the dict unchanged. Works for both dict[int, int] and object-value dicts (dict[int, T]).
  • Zero-Copy Native Struct Marshalling: Native-to-Python interop no longer deep-copies struct fields into Python dicts. NativeStructView and NativeListView use ctypes.Structure.from_address() to create zero-copy views over native memory -- field reads go directly through ctypes descriptors, and string fields are decoded on demand via ctypes.string_at().
  • Native Multiple Inheritance with C3 MRO: The native LLVM backend now supports multiple inheritance with Python-compatible C3 linearization. Struct layouts flatten fields from all ancestors in MRO order with diamond deduplication. Vtables merge method slots from all parents, and method resolution follows C3 semantics (first implementation in MRO wins). Non-primary parent methods are re-compiled against the child's struct layout for correct field access. All MRO computation happens at compile time with zero runtime overhead - dispatch remains O(1) vtable lookup. Includes tests for two-parent inheritance, diamond patterns, MRO method resolution, and three-mixin composition.
  • Native Performance: Bump Allocator & Value-Type Tuples: Replaced per-object malloc in the native LLVM backend with a bump allocator using chained 128MB arenas, eliminating malloc/free overhead for the ~30M heap allocations typical of complex programs. Tuples are now emitted as LLVM insertvalue/extractvalue value-type structs instead of heap-allocated pointers, with automatic boxing only when stored in containers. Destructors skip individual free calls for arena-allocated memory; arenas are reclaimed on process exit.
  • Fix: Native Cross-Module Struct Type Resolution: Importing a user-defined obj from another .na.jac module and using it as a function parameter type no longer crashes the compiler. The importing module now walks the imported module's AST to fully register imported archetypes (struct layout, field types, field indices), and interop binding type strings resolve against both primitive types and struct types.
  • Replace Vendored LSP Stack with Custom jaclang/lsp/ Package: Removed ~32,700 lines of vendored Python code (pygls, lsprotocol, cattrs, attrs) and replaced them with a lightweight ~1,400-line Jac-native jaclang/lsp/ package providing LSP 3.17.0 types, JSON-RPC transport, UTF-16 position encoding, and workspace/document management.
  • Fix: IDE Hover Types for Comprehension Variables: Iteration variables in comprehensions (p in [x for p in pool], any(... for p in pool)) now display their inferred type on hover.
  • Fix: Union[] and Optional[] Special Forms: Union[int, str] and Optional[str] from typing now work correctly for type checking, narrowing.
  • NamedTuple Type Checking: Classes extending NamedTuple now support proper constructor validation (no false "Too many positional arguments" errors), IDE hover for field-related variables including tuple unpacking ((x, y) = point) and for-loop iteration (for coord in point), and correct field type inference.
  • Fix: py2jac BinOp operator precedence: (a - b - c) // 2 was incorrectly converted to a - b - c // 2. Fixed by wrapping same-op chains in AtomUnit so parent operators bind to the whole group.
  • New: jacpretty: Implment an new library for enhanced CLI colors and designs.
  • Type Checker Soundness Fixes (9 bugs): Fixed is/in operators returning UnknownType instead of bool (missing return), list literal type inference only using the first element instead of the union of all elements, MRO linearization using DFS instead of C3, protocol conformance checking only method names instead of full signatures, missing type inference for dict/tuple/set literals, and/or expressions returning only the last operand's type instead of the union, missing handlers for comparison and ternary expressions (both returned UnknownType), and generic type argument checking in class assignment (list[int] was assignable to list[str]).
  • Stricter UnknownType Discipline: UnknownType is no longer treated as Any -- assigning an Unknown-typed value to a typed variable (e.g., x: int = unknown_val) now raises a type error. Unknown destinations (unresolved type aliases) still accept any source. Functions without return annotations now infer None instead of Unknown.
  • Fix: List/Dict Subscript Type Inference: list[Foo][0] and similar subscript expressions now correctly resolve element types. Previously, the IndexSlice AST node was passed directly to __getitem__ overload resolution instead of extracting the inner expression, causing all overloads to fail and returning Unknown.
  • New: Decorator Support on test Blocks: test blocks now accept decorators using the same @decorator syntax as abilities. Example: @skip @timeout(5000) test "slow operation" { ... }. This enables patterns like @skip, @timeout, @tag, or any custom decorator on tests.
  • Fix: String Literal Type Checking: Fixed false positive errors for str.split()[0] + " suffix", string reassignment (msg += " world"), LiteralString with len(), and byte string (b"...") type inference.
  • Centralized Type Layout & Symbol Resolution: Extracted class hierarchy computation (C3 MRO, field layout, vtable structure) and symbol resolution utilities into shared modules (layout_pass.jac, symbol_utils.jac) that all backends query. The native LLVM backend and ES backend now delegate to these centralized implementations instead of maintaining independent copies of the C3 linearization algorithm, hierarchy extraction, symbol lookup, and field collection logic (~128 lines of duplicated code removed across backends).
  • CType Struct Contract for obj: Formalized obj archetypes as CType-compatible structs with a strict layout contract. All obj fields must use layout-compatible types: primitives (int, float, bool, str, bytes, fixed-width i8u64, f32, f64), other obj types (as pointers), enums, typed collections (list[T], dict[K,V], set[T]), optional types (T | None), or function pointer signatures. The layout pass now validates field types at compile time with warnings for non-compatible types (e.g., Any, untyped fields). Added function pointer type resolution in the native LLVM backend (FuncSignatureir.FunctionType.as_pointer()), extended NativeFieldInfo with function pointer metadata (is_func_ptr, func_param_types, func_ret_type), and updated the zero-copy ctypes marshalling layer to wrap function pointer fields as callable CFUNCTYPE objects on access.
  • Fix: jac-check wanings not printing to CLI: jac-check was not printing warnings fixed by minor if statement/for loop changes.
  • Type Checker Gaps #46–#50: Fixed 5 type checker gaps: standalone .impl.jac files now resolve their parent module's scope (Gap #46); dynamic self.attr assignments on obj/node/edge/walker archetypes are now flagged as errors since Jac requires explicit has declarations (Gap #47); ClassType.lookup_member_symbol no longer produces false positives when checking imported class self-members (Gap #48); string annotations used as forward references under TYPE_CHECKING guards now resolve correctly instead of being treated as Literal (Gap #49); by postinit fields are now validated to ensure the postinit method body assigns to all declared fields (Gap #50). Also fixed an LSP go-to-definition assertion for 0-indexed line numbers.
  • Type Checker Soundness Fixes (9 more shortcomings): Removed unsound setfrozenset implicit coercion. Added iterability checks for destructuring RHS ((a, b) = 5 now errors) and for-loop collections (for x in 42 now errors). Added type inference for list/set/dict/generator comprehensions, lambda expressions, and bool literals (all previously returned UnknownType). Added exception type narrowing in except clauses so the bound variable carries the declared exception type. Added raise statement validation to reject non-exception types. Added context manager protocol checking in with statements (verifies __enter__ exists). Added missing-return detection for functions with non-None return type annotations that may implicitly return None.
  • Fix: Native Cross-Module Glob Initializers: glob declarations with initializers (e.g., glob x: list[T] = []) in imported .na.jac modules are now correctly initialized before first use, preventing segfaults on access.

jaclang 0.11.3#

  • Static Analysis Pass: Unused Variables, Undefined Names, Unreachable Code: Added a new StaticAnalysisPass to the type-check pipeline that detects three classes of issues: (1) variables defined but never referenced, (2) name references that fail to resolve, and (3) code following return/raise/break/continue statements. All diagnostics surface as warnings in both jac check output and LSP (IDE squiggles). The pass runs after TypeCheckPass and respects conventional skip patterns (_-prefixed names, has fields, imported symbols, abstract ability parameters, archetype/ability definitions).
  • Overload Resolution: lookup_all() Symbol Table Method: Added UniScopeNode.lookup_all(name, deep) which returns the primary symbol plus all overloads for a given name. The type evaluator and ClassType.lookup_member_symbol now use this centralized method instead of directly accessing the internal names_in_scope_overload dict, improving encapsulation and consistency of overload handling.
  • Remove Dead expr_type String Type Representation: Eliminated the legacy Expr._sym_type string field and expr_type property which were initialized to 'NoType' and never written to. All consumers (tree printer, clean_type, native IR gen fallback) now use the structured TypeBase object (Expr.type) set by the type checker, providing accurate type display in AST dumps and IDE hover.
  • Exception Hierarchy for NA and CL Backends: Added hierarchy-aware exception matching to both native (LLVM) and client (ES) codegen. except ArithmeticError now correctly catches ZeroDivisionError, except LookupError catches KeyError/IndexError, etc. NA backend OR-chains descendant type IDs at compile time; CL backend uses instanceof checks against a full Error subclass hierarchy (_jac.exc.*). All runtime error throws in the JS runtime now use typed exceptions (e.g., _jac.exc.ValueError, _jac.exc.KeyError) instead of plain Error. Includes cross-backend equivalence tests for both explicit raise and implicit runtime errors (division by zero, index OOB, missing keys).
  • Native Memory Management: Reference Counting Replaces Boehm GC: Replaced the external Boehm GC (libgc) dependency with a self-contained reference counting scheme. All heap allocations use an 8-byte RC header (rc_alloc), container data arrays use plain malloc/free, and type-specific destructors are emitted for lists, dicts, sets, and archetypes. String literals are copied into RC-managed memory on use. Old values are released on variable reassignment and container growth paths free old data arrays. This eliminates the libgc system dependency entirely -- the native compiler only requires libc.
  • Fix: string escape : Support string escape decoding.
  • Fix: Py2Jac String Escapes: Fixed py2jac to correctly preserve escape sequences in strings, including hex (\x1b), octal (\033), and standard escapes (\n, \t). Previously these were being lost or corrupted during conversion.
  • jac nacompile Mach-O Support (macOS arm64): Extended jac nacompile to produce standalone Mach-O executables on macOS arm64, in addition to the existing ELF support on Linux. Includes a pure-Python Mach-O linker (macho_linker.jac) that handles GOT construction, stub generation, rebase/bind opcodes, and ad-hoc code signing with SHA-256 page hashes. Platform is auto-detected -- the same jac nacompile program.na.jac command works on both Linux and macOS.
  • Type Narrowing Improvements: Fixed scope-based narrowing for AND expressions (isinstance(x, T) and x.member), assignment narrowing (a = 90 narrows a: int|None to int), guard patterns (if not isinstance(x, T): return), member access chains, truthiness checks, and assert statements. Else-branch no longer incorrectly inherits true-branch narrowings.
  • Union Type Member Access: x.name where x: Dog | Cat now resolves member types and enables go-to-definition.
  • IDE Hover Types: Function parameters and has vars now display types on hover.
  • Fix: Bug Fix: Stop appending lint warnings to py2jac converted files.
  • Structured GitHub Issue Forms: Replaced blank markdown issue templates with guided YAML forms, making it easier to submit well-structured bug reports, feature requests, and docs issues.
  • 1 Minor refactor/change.
  • Native Codegen: Split-File Chess Engine & Major IR Gen Fixes: Enabled complex multi-file native applications (declaration .na.jac + implementation .impl.jac) by fixing 10+ IR generation bugs.
  • Native Auto-Promotion (--autonative): Regular .jac modules can now be automatically promoted to native (LLVM JIT) execution without requiring the .na.jac extension.
  • Native Compiler: Constructor init Method Auto-Invocation: Fixed a bug where object constructors with positional parameters were not automatically calling the init method. Now, init is properly invoked with constructor arguments during object instantiation, enabling proper initialization of objects with parameterized constructors.
  • jac nacompile accepts .jac files: jac nacompile now auto-promotes compatible .jac files to native compilation, with a clear error message when a file uses unsupported constructs.
  • Native sys.argv and sys.exit() Support: Native programs can now access command-line arguments via import sys; args = sys.argv; and exit with a status code via sys.exit(code). sys.argv is a list[str] where argv[0] is the program/binary name. Works with both jac run --autonative and standalone binaries compiled via jac nacompile.
  • Native Compilation Reference Documentation: Added a comprehensive reference page to the docs covering inline na {} blocks, Python-native interop, standalone binaries via jac nacompile, --autonative auto-promotion, the type system, all supported language features, C library interop sys.argv/sys.exit(), and platform support.
  • Fix: Native Global Empty Dict/List Init Null Pointer: Declaring a module-level global with an empty dict or list literal (glob x: dict[str, int] = {}) no longer leaves the global as a null pointer. Any subsequent dict/list operation would previously segfault; the compiler now falls back to helpers["new"]() to produce an initialised empty container.
  • Fix: Native Codegen Crash on Omitted Default Parameters: Calling a method or free function while omitting trailing default-valued parameters (e.g., obj.method(x) where method declares param: int = 0) no longer crashes the compiler with list index out of range in builder.call. Missing arguments are now filled from the AST default expressions before the call is emitted.
  • Fix: Native str.replace(old, new, count) Count Argument Ignored: The third count parameter to str.replace is now respected. A remaining count phi is threaded through the replacement loop and decrements on each substitution; when it reaches zero the rest of the string is copied unchanged. Omitting the argument (or passing a negative value) retains the replace-all behaviour.
  • Fix: Native for (k, v) in d.items() Iteration: Dict .items() iteration in native codegen was silently elided. Fixed by adding a __dict_get_val index helper (mirroring the existing __dict_get_key) and a dedicated items-loop path in _codegen_for that detects the d.items() method-call pattern, binds both loop variables, and emits a standard index-based loop.
  • Fix: Native for c in str String Iteration: for c in str in native codegen previously crashed with a type-mismatch error (%"List.ptr"* != i8*) because the string variable was misclassified as list[ptr] and routed through the list helpers. Fixed by adding a dedicated string-iteration branch in _codegen_for that detects i8* collections, calls strlen for the loop bound, and yields each character as an RC-managed single-char string via the existing _codegen_string_index helper.

jaclang 0.11.2#

  • Improved Memory Efficiency for Large Graphs: Jac now uses lazy loading for graph data in MongoDB/Redis, nodes and edges are fetched only when accessed, instead of loading the entire graph upfront.
  • Fix: Impl File Import Resolution: Impl files (.impl.jac) can now access imports from their parent .jac file without requiring duplicate import statements. Also fixed internal builtins imports (like SupportsAdd, types) incorrectly being visible to user code.
  • Fix: Union of Subclasses Assignable to Base Class: Fixed type checker rejecting valid assignments where a union of subclasses (e.g., Dog | Cat) is passed to a parameter expecting the base class (e.g., Animal). This commonly occurs after match statement narrowing and now works correctly.
  • Fix: Compound AND Narrowing: Multiple isinstance checks in the same AND expression now narrow to the most specific type. Example: isinstance(x, BaseNode) and isinstance(x, CFGNode) correctly narrows x to CFGNode inside the if block.
  • Fix: Progressive Narrowing in AND Expressions: Earlier isinstance checks in an AND expression now narrow the type for subsequent parts. Example: isinstance(x, CFGNode) and x.bb_out works correctly because x.bb_out sees x as CFGNode.
  • Fix: Assert isinstance Type Narrowing: assert isinstance(x, T) now narrows the type of x to T for subsequent statements. Example: assert isinstance(x, CFGNode); x.bb_out; works correctly.
  • Fix: Type Narrowing for Inheritance-Based isinstance: Fixed isinstance(nd, SubClass) not narrowing the type when the variable is declared as a base class (e.g., nd: BaseNode). Previously, type narrowing only worked with union types; now single-class types are correctly narrowed to their subclass after isinstance checks.
  • Fix: Native Global Pointer Variables Collected by GC: MCJIT global variables (e.g. glob WHITE_SYMBOLS: dict[...]) live outside Boehm GC's scanned memory, causing global dicts/lists/objects to be freed after enough allocations trigger a collection. Fixed by emitting GC_add_roots calls for every pointer-typed global after initialization.
  • Fix: Native Dict Tuple Key Comparison: Dict key comparison for tuple/struct pointer types used pointer equality instead of structural comparison, so two separately-allocated tuples with the same values would never match. Fixed by using memcmp for tuple keys, matching the existing pattern in set helpers.
  • Match Case Type Narrowing: The type checker now narrows variable types inside match cases based on the pattern being matched. For example, case MyClass(): narrows the matched variable to MyClass, and union patterns like case A() | B(): narrow to A | B.
  • Fix: jac create --use with jac-scale endpoints: Fixed jac create --use <URL> failing when the URL points to a jac-scale @restspec endpoint. The template loader now unwraps the TransportResponse envelope ({data: {result: {...}}}) before parsing the jacpack JSON.
  • Fix: Formatter Line-Breaking, Comment Spacing, and DocIR Generation: Improved jac format line-breaking by accounting for trailing sibling width when deciding group breaks, fixed budget tracking after newlines, preserved original source spacing for inline comments, added proper indentation for ternary (if-else) continuation lines, among others.
  • jac nacompile -- Standalone ELF Binaries: New jac nacompile CLI command compiles .na.jac files to standalone ELF executables with no external compiler or linker required. Uses llvmlite's code generator to emit object code and a pure-Python ELF linker (elf_linker.jac) to produce dynamically-linked ELF binaries against libc/libgc. The _start entry point is written in pure LLVM IR (zero inline assembly), making the entire pipeline architecture-agnostic. Includes automatic GC fallback (rewrites GC_malloc to malloc at the IR level when libgc is unavailable). Usage: jac nacompile program.na.jac or jac nacompile program.na.jac -o mybin.
  • Fix: py2jac docstring conversion: Fix py2jac to correctly convert Docstrings with escape sequences.
  • 2 Minor refactors/changes

jaclang 0.11.1#

  • Perf: Type Narrowing Optimization: Fixed exponential slowdown in jac check with many if statements (~1 min → ~2s). Member access now uses narrowed types and reports errors for invalid attribute access on None.
  • Import Path Alias Resolution: The module resolver now supports path aliases configured in [plugins.client.paths] in jac.toml. Aliases like @components/Button are resolved to their filesystem paths before standard module lookup, enabling cleaner imports in client-side Jac code.
  • Native Codegen: C Library Import Syntax (import from "lib" { def ...; }): Added first-class parser and IR generation support for importing C shared libraries. Declarations inside the braces are parsed as extern function signatures (no body), producing LLVM declare statements that MCJIT resolves from the loaded .so/.dylib. Includes fixed-width C-compatible types (i8, u8, i16, u16, i32, u32, i64, u64, f32, f64, c_void) and automatic type coercion (i64↔i32, f64↔f32) at call boundaries.
  • Native Codegen: C Struct Value-Type Coercion: C structs declared inside import from "lib" { obj Color { has r: u8, g: u8, b: u8, a: u8; } } blocks are used as normal Jac objects (heap-allocated, pointer semantics) but automatically coerced to C value semantics at call boundaries. Small integer-only structs (<=64 bits) are ABI-coerced to register-sized integers (e.g., Color to i32), matching the x86_64 SysV calling convention.
  • Fix: jac format Unicode Error on Windows: Fixed 'charmap' codec can't encode character error when formatting files with emojis or non-ASCII text on Windows.
  • Remove Vendored pluggy and interegular: Replaced the vendored pluggy library (~1,700 lines) with a lightweight custom plugin system (jaclang/plugin.py, ~200 lines) that provides the same hook spec/impl/dispatch API. Removed the unused vendored interegular library (~2,200 lines).
  • Scheduler Support for Walkers and Functions: Added a built-in scheduler that enables time-based execution of walkers and functions using the @schedule decorator. Supports three scheduling modes: one-shot (date), recurring (interval), and cron-based (cron). Scheduling is classified as either static or dynamic via the trigger parameter (ScheduleTrigger.STATIC or ScheduleTrigger.DYNAMIC). Static scheduling (the default) runs tasks on a background daemon thread during jac start and automatically discovers @schedule-decorated walkers and functions. Dynamic scheduling requires jac-scale. Scheduled walkers and functions are not exposed as API endpoints.
  • Enhanced jac check output: The jac check command now provides a more detailed and user-friendly output format, including file progress, failure details, and timing information.
  • Fix: Grammar Extraction Well-Formedness: Improved jac grammar to produce well-formed EBNF by pruning unreachable rules, detecting savepoint-backtrack patterns, suppressing duplicate guard tokens, and handling broader condition forms (check_name, nested boolean exprs, or chains).
  • LSP: ReadWriteLock for Concurrent Queries: Replaced the single RLock in the language server with a writer-priority ReadWriteLock, allowing hover, completion, go-to-definition, and other read operations to run concurrently without blocking on type checking. Also fixed several race conditions where shared state (mod.hub, sem_managers) was accessed without any lock.
  • Fix: Self-Member Attributes in Impl Files: self.x = value assignments in impl blocks (including separate .impl.jac files) now correctly register as archetype attributes. Go-to-definition and type checking work seamlessly without requiring explicit has declarations.
  • 3 Minor refactor
  • Fix: Unparenthesized Lambda with Keyword Parameter Name: Fixed parse_lambda_param rejecting keyword tokens (e.g., props, root, here) as unparenthesized lambda parameter names. The parser now correctly accepts special var ref keywords and emits a targeted error for other keywords with an escape hint.
  • Refactor: Merge JacSerializer into Serializer: Removed the JacSerializer wrapper class from runtimelib.server and merged its API-response behavior into Serializer via a new api_mode: bool = False parameter. Call Serializer.serialize(obj, api_mode=True) to get clean API output with _jac_type, _jac_id, and _jac_archetype metadata on Archetype objects (previously done by JacSerializer). Storage backends continue to use Serializer.serialize(obj, include_type=True) unchanged. Import from jaclang.runtimelib.serializer. This eliminates a redundant wrapper class with no unique serialization logic. Added social_graph.jac example fixture in jac-scale demonstrating native persistence and db.find_nodes() for querying persisted nodes with MongoDB filters.

jaclang 0.11.0#

  • Automatic Endpoint Caching: The compiler now statically analyzes walker and server function bodies to classify endpoints as readers or writers, and propagates this metadata (endpoint_effects) through the ClientManifest to the client runtime. Reader endpoints are automatically cached on the client side, and writer endpoints auto-invalidate overlapping reader caches based on shared node types -- zero developer configuration required.
  • HMR Server-Side Reloading Refactor: Improved HMR functionality with better handling of .impl.jac files and optimized caching to avoid unnecessary recompilations during development
  • Builtin llm Name: llm is now a builtin name in the Jac runtime, enabling by llm() syntax without requiring an explicit import or glob llm declaration. The runtime provides a stub default that plugins (e.g. byllm) override with a fully configured LLM model.
  • Fix: Impl Block Variables Lose Type Info Across Files: Fixed a bug where variables declared with has (e.g., has tasks: list = []) in a component lost their type annotation when referenced from a separate .impl.jac file. The shadowed symbol fixup was also optimized from O(N*M) to O(N+M) by batching lookups and traversing impl body nodes in a single pass.
  • Fix: Duplicate __jacCallFunction Import in .cl.jac with .impl.jac: Fixed the ES codegen emitting duplicate import { __jacCallFunction } from "@jac/runtime" when both a .cl.jac file and its .impl.jac annex use sv import. Child module imports are now deduplicated by source path during merge.
  • Variant Module Annexing (.sv.jac, .cl.jac, .na.jac): A module can now be split across variant files that are automatically discovered, compiled, and merged. Given main.jac, any sibling main.sv.jac, main.cl.jac, or main.na.jac files are annexed as variant modules with their respective code contexts (SERVER, CLIENT, NATIVE).
  • Fix: Bare Impl Files Not Matched to Variant Modules: Fixed discover_annex_files rejecting bare annex files (e.g., foo.impl.jac) when the source is a variant (e.g., foo.cl.jac) with no plain foo.jac head. Bare annex files now match any variant source unless a bare .jac head exists that would claim them.
  • Fix: Bare-Dot Relative Import (from . import x) Not Resolved: Fixed import from . { x } silently resolving to UnknownType. The import path is now computed directly from the current file's directory, ensuring sibling modules are correctly found and type-checked.
  • Fix:: update the jac-check command to print the file names of the files that failed to have clean error message.
  • 2 Small refactors/changes.
  • ES Codegen: Near-Complete Primitive Test Coverage (92%): Added cross-backend equivalence tests for 110 additional primitive emitter interfaces (275/299 total), covering float operators, complex arithmetic, bytes methods (with full _jac.bytes runtime namespace), set/frozenset algebra and operators, and extra builtins. Fixed column-aware expandtabs for str and bytes, complex.pow/complex.eq mixed-type handling, and ascii() quoting to match Python semantics.
  • Refactor: Native Jac Generics in Primitives: Replaced Python-style Generic[(V, C)] with native Jac bracket syntax [V, C] across all emitter classes in primitives.jac and removed unused TypeVar/Generic imports.
  • Fix: jac format Misplaces Comments Around Generic Type Params: Fixed jac format moving section comments (e.g., # === String Types ===) into the [V, C] brackets of the preceding class. The parser was generating synthetic comma tokens between type parameters with incorrect source locations; parse_type_params now preserves the real comma tokens from the source.
  • 4 Minor refactors/chages
  • Native Codegen: Expanded Primitive Coverage: Added 45 new LLVM IR emitter implementations and inline codegen across 8 emitters.
  • Native Codegen: Expanded Primitive Coverage: Added 17 new LLVM IR emitter implementations across 6 emitters: IntEmitter (conjugate, bit_length, bit_count), FloatEmitter (conjugate, is_integer, op_floordiv), StrEmitter (title, rfind, ljust, rjust, zfill), SetEmitter (clear, discard, copy), ListEmitter (extend, insert), and BuiltinEmitter (bool). Fixed string comparison operators (<, >, <=, >=) via strcmp and added float floor division (fdiv + floor). Fixed rfind infinite loop on empty substring. 108/299 implemented (36%), 105/299 tested (35%).
  • ES Codegen: Comprehension Support (Set, Dict, Nested Loops): Added SetComprnew Set(...), DictComprObject.fromEntries(...), and nested loop → .flatMap() chain support to the ES transpile pass. Fixed arrow function parenthesization for destructuring params (([k, v]) => ...). Includes 17 new test cases covering all comprehension types.
  • Fix: Walker result.reports in CLI Mode: Fixed report keyword not populating result.reports when running walkers via jac run or jac test.
  • jac format Support for Generic Syntax: jac format now correctly handles native generic type parameters (class Foo[V, C]) and type aliases (type Result[T, E] = T | E;). Previously, formatting would lose type parameter names and produce broken output.
  • Bootstrap Compiler (jac0) Native Generic Support: Extended the jac0 bootstrap transpiler to parse and emit PEP 695 generic class syntax (class Foo[T, V](Base)) and type alias statements (type Alias[T] = Expr), enabling jac0core infrastructure files to use native Jac generics instead of Python-style Generic[T]/TypeVar patterns.
  • Migrate jac0core and Compiler Passes to Native Generics: Replaced Generic[(T, V)] inheritance and TypeVar declarations with native [T, V] syntax across transform.jac, unitree.jac, base_ast_gen_pass.jac, and all Transform[(X, Y)] subscription sites in the compiler pipeline.

jaclang 0.10.5#

  • Fix: sv import of def:pub Functions Generates RPC Stubs: Fixed sv import from module { func } in .cl.jac files not generating for def:pub server functions.
  • Fix: Type Narrowing Infinite Loop on Large Files: Fixed jac check hanging indefinitely on large .jac files (e.g. standalone .impl.jac modules). The backward CFG walk in _compute_narrowed_at had no depth bound, causing combinatorial explosion when the module-level CFG contained hundreds of basic blocks. Added a depth limit to the walk; narrowing beyond the limit conservatively returns the declared type.

jaclang 0.10.4#

  • jac check/lint --ignore Multi-Arg & Wildcard Support: Enhanced --ignore flag to accept multiple space-separated patterns (--ignore dir1 dir2 dir3) instead of comma-separated strings. Added wildcard support using glob patterns (e.g., --ignore "jac-*" test) for flexible directory matching.
  • CI: Type Check All Jac Files: Updated CI workflow to run jac check on all .jac files (excluding test fixtures and error cases) in preparation for removing .jacignore.
  • Fix: _jac ES Runtime Correctness: Fixed str.split with maxsplit to keep the remainder (matching Python behavior), dict.eq to compare key-by-key instead of order-dependent JSON.stringify, and builtin dispatch (e.g., sorted(key=lambda...)) to correctly pass keyword arguments to the runtime.
  • Fix: Remove Dead abs Prefix Modifier: Removed the unused abs prefix on archetypes (abs obj Foo { }) from the grammar and parser. The prefix was parsed but silently discarded; archetype abstractness is computed from contained abstract abilities. The abs keyword remains valid only as an ability body terminator (can foo() abs;).
  • ES Codegen: Expanded Primitive Coverage: Added bool() with Python truthiness semantics (empty list/dict/set are falsy), range() builtin (supports for i in range(n)), slice() constructor, bytearray() constructor, dedicated BoolEmitter for correct &/|/^ bool-returning bitwise ops, enhanced format() with format-spec support (f, d, b, o, x, e, %, width, alignment), and fixed int() to handle booleans and floats correctly via Math.trunc(Number(x)).
  • Fix: Lexer Infinite Loop on Malformed JSX: Fixed three infinite-loop scenarios where the lexer would hang forever when hitting EOF inside a non-NORMAL mode (JSX content, JSX tag, or f-string). Added a stuck detector in tokenize() that forces EOF when the lexer stops advancing or overshoots the source, preventing jac run, jac start, and jac js from hanging on malformed input (e.g., unterminated JSX like <div>hello with no closing tag).
  • Fix: Bare < in JSX Content No Longer Hangs Lexer: A < character in JSX content that does not start a valid tag (e.g., <--) is now consumed as text instead of causing an infinite loop. The text scanner only breaks on < when the next character forms a real JSX construct (</, <>, or < + identifier).
  • Fix: Grammar Extraction Accuracy: Fixed multiple issues in jac grammar output: atomic_chain and jsx_attributes now show * repetition, compare no longer duplicates operators, assignment_with_target correctly extracts ternary expressions, excessive top-level ? wrapping is stripped from multi-branch rules, f-string tokens use proper quoting, and added GPlus (one-or-more +) grammar expression type.
  • 1 Minor refactors/changes

jaclang 0.10.3#

  • Fix: Type Narrowing in Loops: Fixed type narrowing loss in loops and also improved CFG accuracy.
  • Fix: Config Discovery from Target File Path: Fixed jac start commands to discover jac.toml from the target file's directory instead of the current working directory when using absolute/relative paths.
  • Fix: Unbound Method Call Type Checking: Fixed "Parameter already matched" error when calling parent class methods with explicit self in inheritance patterns (e.g., ParentClass.init(self, name=name)). The type checker now correctly handles unbound method calls on obj/node/walker/edge types where self is implicit.
  • Enhanced Type Narrowing: Extended CFG-based type narrowing to support additional patterns: parenthesized isinstance (isinstance(x, T)), NOT expressions not isinstance(x, T), compound AND/OR conditions, isinstance with tuple of types isinstance(x, (A, B)), truthiness narrowing if x: (excludes None), literal equality x == "lit", and inheritance-aware isinstance that correctly narrows to subclasses in unions.
  • Fix: Bare Callable Type Annotation: Using Callable without type parameters (e.g., fn: Callable) no longer causes type errors.
  • Type Inference for Tuple Unpacking: The type evaluator now infers element types for variables in tuple/list unpacking assignments (e.g., (row, col) = pos; where pos: tuple[int, int]), eliminating the need for explicit pre-declarations before unpacking. Types that cannot be inferred still require explicit annotations.
  • Fix: Display detailed syntax error messages: Display detailed syntax error messages in jac run and jac start commands instead of generic import errors.
  • Enum Type Checking: Enums now have proper type checking. Accessing .name returns str, .value returns the correct type based on your enum values (int or str). Passing wrong types to functions expecting enums now shows type errors.
  • Fix: False type errors on class-based enums: Classes inheriting from StrEnum, IntEnum, or IntFlag no longer produce false type errors.
  • Fix: LSP features in nested impl blocks: Go-to-definition, hover, and syntax highlighting now work correctly for symbols inside if/while/for statements within impl blocks.
  • Fix: JS useState scope bug: Fixed has vars incorrectly triggering setState() in sibling functions with same variable name.
  • Fix: Inherited field default override: Fixed false "missing required parameter" error when a child class provides a default for a parent's required field.
  • parametrize() Test Helper: Added a parametrize(base_name, params, test_func, id_fn=None) runtime helper that registers one test per parameter via JacTestCheck.add_test().
  • Generic Primitive Emitter Interface: Refactored the primitive codegen emitter contracts. Emitters are now stateless singletons with per-call context, and dispatch uses typed instance methods instead of static class-as-namespace calls.
  • Human-Readable Tokens in Errors and Grammar Spec: Parser error messages and jac grammar output now display actual token text ("{", "if", ";") instead of internal names (LBRACE, KW_IF, SEMI), making syntax errors and the grammar specification much more readable.
  • Support Bare type Parameter Assignment: Functions with type parameter annotations now correctly accept class types as arguments (e.g., process(cls: type) can be called with process(MyClass)).
  • Operator Primitive Dispatch for ES Codegen: Wired type-aware operator dispatch into the JavaScript code generation pass. Binary, comparison, unary, and augmented assignment operators now query the type evaluator and delegate to the appropriate primitive emitter, producing correct JS semantics for Python-style operators (e.g., list + list emits spread concatenation [...a, ...b], str * n emits .repeat(n), x in list emits .includes(x)).
  • 3 Minor refactors/changes.
  • Fix: Lexer < Comparison vs JSX Tag Disambiguation: Fixed an infinite loop where i<points in a for-loop caused the lexer to enter JSX tag mode. The lexer now tracks the previous token to distinguish < as a comparison operator (after values) from a JSX opening tag (after keywords like return, operators, or delimiters).
  • Fix: Quoted JSX Text Produces Invalid JS: Fixed JSX text containing quote characters (e.g., <p>"text"</p>) generating invalid double-double-quoted JavaScript (""text""). Inner quotes are now properly escaped in the emitted JS string literals.
  • Fix: unittest.mock.patch Compatibility in Jac Tests: Fixed unittest.mock.patch not intercepting calls in Jac test blocks.
  • Modern Generics: type Aliases & Inline Type Parameters: Added PEP 695-style type alias statements (type JsonPrimitive = str | int | float | bool | None;) and inline generic type parameters on archetypes (obj Result[T, E = Exception] { ... }). Supports bounded type vars (T: Comparable), default type values, and recursive type aliases. Compiles to native Python 3.12 ast.TypeAlias and ast.TypeVar nodes.
  • Perf: Precompiled Bytecode for Zero Cold Start: Ship precompiled .jbc bytecode per Python version (3.12, 3.13, 3.14) inside each Jac package wheel. Cold start (jac purge && jac --help) drops from ~29s to <1s.
  • jac run Script Arguments: jac run now passes arguments to the script using Python-like semantics - everything after the filename goes to the script (e.g., jac run script.jac arg1 arg2), accessible via sys.argv[1:]. Jac flags like --no-cache must come before the filename, just like python -O script.py.
  • Fix: Operator Precedence for Bitwise vs Logical Operators: Fixed operator precedence so bitwise operators (|, ^, &, <<, >>) bind tighter than logical operators (or, and, not), matching Python's semantics. Previously 3 & 1 == 1 was parsed as 3 & (1 == 1) instead of (3 & 1) == 1.
  • Fix: _jac Primitive Runtime for ES Codegen: Fixed unification of compiled JavaScript that uses Python-like primitive operations (list.sort(key=...), list.count(), str.capitalize(), int % int, etc.).
  • 2 Minor refactors/changes.

jaclang 0.10.2#

  • Unified Primitive Codegen Interface: Added abstract emitter contracts (primitives.jac) for all Jac primitive type methods and builtin functions. Each compilation backend (Python, ECMAScript, Native) must implement these interfaces, ensuring consistent primitive support across all code generation pathways. Python, JS, and Native backend implementations provided.
  • Pytest Plugin for Native Jac Tests: Added a pytest11 entry-point plugin (jaclang.pytest_plugin) that discovers and runs test blocks in .jac files alongside Python tests with zero configuration. Migrated ~79 language integration tests and 8 compilation tests from Python to native Jac test keyword.
  • Perf: Bootstrap Bytecode Cache: Cache the jac0-transpiled bytecode for jac0core modules on disk, eliminating ~200ms of repeated transpilation on every invocation. jac purge -f clears both caches.
  • Perf: Cache len() in Lexer/Parser Hot Paths: Cached source and token list lengths in the jac0 bootstrap transpiler and the RD parser/lexer, eliminating ~1.8M redundant len() calls per startup.
  • 3 Minor refactors/changes.
  • Fix: jac grammar Command Broken Path: Fixed the jac grammar CLI command.
  • Grammar Extraction Pass Improvements & Spec Snapshot Test: Improved jac grammar extraction accuracy for negated-check loops, optional dispatch branches, while True parse-and-break patterns, and standalone match_tok calls. Added a golden-file snapshot test (jac.spec) that validates extracted grammar rules against a checked-in spec, catching unintended grammar drift on every CI run.
  • Black-style Grammar Formatting: Replaced alignment-based jac grammar formatting with Black-style fixed 4-space indentation, blank lines between rules, and 88-char line width. Uses a recursive tree-based formatter instead of the previous string-based wrapping.
  • RD Parser Spec Convergence: Improved strictness of jac parser and specification.
  • 4 Minor refactors/changes.

jaclang 0.10.1#

  • Stale Persistence Cache Handling: Resolved an issue where running different Jac applications sequentially caused NodeAnchor [UUID] is not a valid reference! errors due to stale anchors persisting in SQLite/MongoDB/Redis backends. The runtime now validates that an anchor’s archetype still exists before loading it and automatically removes invalid entries. This removes the need for manual cache deletion in normal workflows.
  • jac purge Command: Added jac purge to clear the bytecode cache. Works even when the cache is corrupted.
  • format_build_error Plugin Hook: Added format_build_error(error_output, project_dir, config) hook to JacMachineInterface, allowing plugins to provide custom error formatting for client bundle build failures. The default implementation returns raw error output; plugins like jac-client can override to display structured diagnostics.
  • Fix: MTIR scope key uses file stem for portability: Fixed MTIR scope key generation to use only the file stem (filename without extension) instead of path-relative module names. This ensures consistent scope keys across different execution environments (local vs Docker) and enables compiled bytecode to be portable across different directory structures.
  • Fix: False Type Errors in Nested Functions: Fixed incorrect type checking errors when using nested functions inside impl blocks. Nested functions now correctly validate their return statements against their own return type instead of the outer function's return type.
  • Fix: jac start Output Ordering: Server startup messages now appear after compilation completes instead of before, ensuring users see build progress first and "Server ready" only when the server is actually accepting connections. Added on_ready callback parameter to JacAPIServer.start() for consistent startup message handling across stdlib and jac-scale servers.
  • jac format --check Mode: Added a --check flag to validate formatting without modifying files. Exits with status 1 if files need formatting or have syntax errors.
  • Fix: jac start Output Ordering: Server startup messages now appear after compilation completes instead of before, ensuring users see build progress first and "Server ready" only when the server is actually accepting connections.
  • PWA Build Detection: The stdlib server now detects existing PWA builds and serves Vite-hashed client files (client.*.js) correctly.
  • Fix: Serve JSON and JS files as static assets: Added .json and .js to the list of recognized asset extensions, fixing PWA manifest.json and sw.js serving.
  • Code refactors: Backtick escape, TS cleanup, etc.
  • Bootstrap Compiler (jac0): Added a single-file Python transpiler (jac0.py, ~1900 lines) that compiles the Jac subset produced by py2jac into equivalent Python source code. This closes the bootstrap loop.
  • RD Parser: Broad Grammar Parity Fixes: Fixed 16 grammar gaps in the recursive descent parser, raising walk-check match rate from 95.3% to 98.7%.
  • jac --version Shows Installed Plugins: The version banner now lists all installed Jac plugins with their versions, making it easy to see the full environment at a glance.
  • Support Go to Definition for Inherited Members: "Go to Definition" now works correctly for inherited methods and attributes on classes without an explicit parent class.
  • Type Checker Improvements:
  • Callable Type Annotation Support: Added full support for Callable[[ParamTypes], ReturnType] type annotations. Includes gradual callable form (Callable[..., T]), parameter contravariance, return type covariance, automatic self/cls filtering for methods and classmethods, and validation that extra source parameters have defaults.
  • Fix: Type Checker Crashes: Fixed crashes when type-checking default/star imports (import from mod { default as X }) and walker entry/exit handlers.
  • Fix: LiteralString Type Support: Added LiteralString class to the type checker, improving binary operator chain handling and ensuring type compatibility between LiteralString and str types.
  • Type Checking for super.init() Calls: Added validation for super.init() calls, catching argument errors against parent class initializers with proper MRO resolution.
  • Fix: Native Code Cache False Positive: Fixed a bug where "Setting up Jac for first use" appeared on every run instead of only the first time.
  • Fix: LiteralString String type Compatibility: LiteralStrings and Strings are now type compatible with type checker.
  • Lark Parser Removal: Replaced the Lark-based parser with a hand-written recursive descent parser as the default. Deleted jac_parser.py, jac.lark, lark_jac_parser.py, and the vendored lark/ directory. All 110 language tests, 438 format tests, and 15 LSP server tests pass with the new parser.
  • 1 Small Refactors
  • Docs update: return type any -> JsxElement
  • Fix: Spurious Write Access Warning on System Root During Sync
  • 3 Small Refactors

jaclang 0.10.0#

  • KWESC_NAME syntax changed from <> to backtick: Keyword-escaped names now use a backtick prefix (`node) instead of the angle-bracket prefix (<>node). All .jac source files, the lexer, parser, unparse/DocIR passes, and auto-lint rules have been updated accordingly.
  • Remove Backtick Type Operator: Removed the backtick (`) TYPE_OP token and TypeRef AST node from the language. The Root type is now referenced directly by name (e.g., with Root entry instead of with `root entry). Filter comprehension syntax changed from (`?Type:field==val) to (?:Type, field==val). Root is automatically imported from jaclib when used in walker event signatures.
  • APIProtocol Builtin Enum: Added APIProtocol enum (HTTP, WEBHOOK, WEBSOCKET) as a builtin, replacing the boolean webhook flag in RestSpecs with a typed protocol field. Use @restspec(protocol=APIProtocol.WEBSOCKET) directly without imports.
  • Native Compiler: Cross-Module Linking: Native .na.jac modules can now import and call functions from other .na.jac modules. The compiler performs LLVM IR-level linking enabling modular native code organization with import from module { func1, func2 } syntax.
  • LSP Debounced Type Checking: The language server now waits for a brief pause in typing (300ms) before starting analysis, eliminating lag during rapid edits.
  • Fix: LSP Multi-File Race Condition: Fixed a race condition when switching between files that could cause stale diagnostics.
  • jac grammar Command: Added a jac grammar CLI command that extracts the Jac grammar directly from the recursive descent parser's AST and prints it in EBNF or Lark format. Use jac grammar for EBNF output, jac grammar --lark for Lark format, and -o <file> to write to a file. Powered by a new GrammarExtractPass compiler pass that analyzes parse_* method implementations to reconstruct grammar rules from token-consumption patterns and control-flow structures.
  • Type Checker Improvements: Fixed several systemic issues in the type checker: UnionType operands are now handled correctly in connection operations (++>, -->) and instance conversion; the _get_enclosing_class helper no longer silently crashes due to a variable name bug; unannotated functions no longer produce false return-type errors; and a new rule requires return type annotations on top-level functions that return a value (bare return and return None remain annotation-free).
  • Support custom Vite Configurations to dev mode: Added support for custom Vite configuration from jac.toml.
  • Native Compiler: Multi-Parameter Print Fix: The print() builtin in native code now correctly handles multiple arguments, printing them space-separated on a single line (e.g., print("x =", 10, "y =", 20) outputs x = 10 y = 20). Previously only the first argument was printed.
  • Fix: HMR server-side sys.modules refresh: Imported modules now correctly reload in --dev mode by clearing stale sys.modules entries across the project.
  • Auto-install watchdog for --dev mode: jac start --dev automatically installs watchdog if missing, eliminating the manual jac install --dev step.
  • Trim Redundant Parser Test Fixtures: Removed 46 redundant entries from the micro parser test suite by eliminating exact duplicates across directories, near-identical examples, and files that add no unique syntax coverage, reducing the fixture list from 481 to 435 files.
  • RD Parser Grammar Gap Fixes: Fixed 9 coverage gaps in the recursive descent parser to match the Lark grammar spec: skip statement, @= operator, na context blocks, typed context blocks (-> Type { ... }), is separator in sem definitions, impl inside archetype bodies, raw f-strings (rf"..."), parenthesized yield expressions, and *args/**kwargs in lambda parameters.
  • Refactor: Centralized NormalizePass: Extracted the ~104 normalize() methods from individual AST node classes in unitree.py into a dedicated NormalizePass implemented in Jac (normalize_pass.jac + impl/normalize_pass.impl.jac). This keeps AST classes purely structural and follows the same self-hosted pass pattern used by UnparsePass and other tool passes.
  • RD Parser: Yield in Assignments & Grammar Extraction Improvements: The RD parser now correctly handles x = yield expr in assignments. The jac grammar extraction pass was improved to accurately display binary operator rules (e.g., logical_or now shows logical_and (KW_OR logical_and)* instead of the incorrect logical_and KW_OR*).
  • RD Parser: Async, Impl & F-String Gap Fixes: Fixed 6 more coverage gaps in the recursive descent parser: async with statements, async comprehensions (list/set/gen), async for token in AST kid lists, impl with event clause missing with token, impl by-expression extra by token, and nested {expr} inside f-string format specs (e.g., f"{value:{width}}").
  • RD Parser: Enum & Match Pattern Gap Fixes: Fixed 3 more coverage gaps: multistring (concatenated string literals) in match literal patterns, py_code_block (inline Python) in enum blocks, and free_code (with entry blocks) in enum blocks.
  • Fix: IsADirectoryError on dotted imports: Fixed a crash where the compiler attempted to read directory paths as files when resolving dotted module imports (e.g., include impl.imps).
  • CLI Dependency Command Refactor: Redesigned the jac install, jac add, jac remove, and new jac update commands for cleaner, more consistent behavior. jac install now syncs all dependency types including plugin-provided ones (npm, etc.). jac add requires package arguments (no longer silently falls through to install) and errors on missing jac.toml. When no version is specified, jac add queries the installed version and records a ~=X.Y compatible-release spec instead of >=0.0.0. The new jac update [pkg] command updates all or specific dependencies to their latest compatible versions and writes ~=X.Y specs back to jac.toml.
  • Fix: Fixed config.save() to correctly persist dependency removals and git dependencies to disk.
  • RD Parser: Strictness Parity with Lark: Tightened the RD parser to reject constructs that the Lark grammar also rejects, closing 7 permissiveness gaps.

jaclang 0.9.15#

  • Fix: Type Errors in Impl Files Now Show Correct Location: Type errors in .impl.jac files now point to the actual error location instead of the declaration in the main file.
  • First-Run Progress Messages: The first time jac is run after installation, it now prints clear progress messages to stderr showing each internal compiler module being compiled and cached, so users understand why the first launch is slower and don't think the process is hanging.
  • jac add Dependency Resolution: jac add now installs all Python dependencies in a single batch, allowing pip to resolve compatible versions across interdependent packages and preventing runtime errors caused by version mismatches.
  • Self-Hosted Recursive Descent Parser: Added a hand-written recursive descent parser and lexer implemented entirely in Jac (jac/jaclang/compiler/parser/). The parser is designed for native compilation with no runtime reflection - grammar rules are encoded directly in AST type definitions. Features include a 28-level expression precedence chain matching the Lark grammar, contextual lexing for f-strings and JSX, and comprehensive pattern matching support. This lays the groundwork for a fully self-hosted Jac compiler.
  • LSP Responsiveness During Rapid Typing: Improved editor responsiveness when typing quickly by properly cancelling outdated type-check operations.
  • Native Compiler: Dictionaries and Sets: The native backend now supports dict and set types with full codegen for literals, len(), key/value access, subscript assignment, in membership testing, set.add(), and iteration over dict keys. Both integer and string keyed dictionaries are supported. Global-scope dict and set declarations are also handled. Validated with a comprehensive dicts_sets.na.jac test suite.
  • Native Compiler: Comprehensions: Added code generation for list, dict, and set comprehensions including nested for clauses and if filters. List comprehensions with conditions, dict comprehensions mapping positions to pieces, and set comprehensions collecting move targets all compile to native LLVM IR.
  • Native Compiler: Tuples: Tuples are now a first-class type in the native backend. Supports tuple literals, tuple indexing, tuple unpacking assignments (e.g., (row, col) = pos;), and tuples as dict keys and set elements. Positions throughout the chess test case are now represented as tuple[int, int].
  • Native Compiler: Inherited Method Wrappers: The native backend now generates wrapper functions for inherited methods, enabling vtable-based virtual dispatch to correctly resolve methods defined on base classes when called through subclass instances.
  • Native Compiler: Bitwise and Extended Operators: Full support for bitwise operators (&, |, ^, ~, <<, >>), power operator (**), and all augmented assignment variants (&=, |=, ^=, <<=, >>=, **=, //=, %=). Hex (0x), octal (0o), and binary (0b) integer literals are also handled.
  • Native Compiler: Dict/Set Comprehensions and Iteration: Dict comprehensions, set comprehensions, and for-over-dict iteration (iterating keys of a dictionary) now compile to native code. Tuple membership testing in sets (target in attacked_set) is also supported.
  • Native Compiler: Exception Handling: Full try/except/else/finally support in the native backend. Includes raise with exception type and message, multiple except clauses with type matching, bare except catch-all, as binding for caught exceptions, and nested try blocks. Exceptions use a lightweight stack-based handler model with setjmp/longjmp under the hood.
  • Native Compiler: File I/O: The open() builtin now compiles to native code, returning a File struct backed by C fopen. File methods read(), write(), readline(), close(), and flush() are all supported. NULL handle checks are generated for failed opens.
  • Native Compiler: Context Managers: with statements compile to native LLVM IR. __enter__ is called on entry, __exit__ on exit (including when exceptions occur). The as binding form (with open(path) as f) is supported. File objects implement the context manager protocol for automatic resource cleanup.
  • Native Compiler: Runtime Error Checks: The native backend now generates runtime safety checks that raise structured exceptions: ZeroDivisionError for integer and float division/modulo by zero, IndexError for list index out of bounds, KeyError for missing dictionary keys, OverflowError for integer arithmetic overflow, AttributeError for null pointer dereference, ValueError for invalid int() parsing, and AssertionError for failed assertions.
  • Native Compiler: Python↔Native Interop: Added cross-boundary function call support between Python (sv) and native (na) codespaces within the same module. Native functions can now call Python functions via LLVM extern declarations backed by ctypes callbacks, and Python code can call native functions via auto-generated ctypes stubs.
  • Fix: sv import Lost During Unparse in .cl.jac Files
  • Fix: ESM Script Loading in Legacy Runtime: Added type="module" to the generated <script> tag in the legacy runtime HTML renderer, matching the same fix applied in jac-client.

jaclang 0.9.14#

  • Fix: jac format No Longer Deletes Files with Syntax Errors: Fixed a bug where jac format would overwrite a file's contents with an empty string when the file contained syntax errors. The formatter now checks for parse errors before writing and leaves the original file untouched.
  • jac lint Command: Added a dedicated jac lint command that reports all lint violations as errors with file, line, and column info. Use jac lint --fix to auto-fix violations. Lint rules are configured via [check.lint] in jac.toml. All enabled rules are treated as errors (not warnings). The --fix flag has been removed from jac format, which is now pure formatting only.
  • CLI Autocompletion: Added jac completions command for shell auto completion. Run jac completions --install to enable autocompletion for subcommands, options, and file paths. Supports bash, zsh, and fish (auto-install), plus PowerShell and tcsh (manual).
  • Centralized project URLs: Project URLs (docs, Discord, GitHub, issues) are now defined as constants in banners.jac and reused across the CLI banner, server error messages, and help epilog instead of being hardcoded in multiple places.
  • Client bundle error help message: When the client bundle build fails during jac start, the server now prints a troubleshooting suggestion to run jac clean --all and a link to the Discord community for support.
  • Native Compiler Buildout: Major expansion of the native binary compilation pipeline for .na.jac files. The native backend now supports enums, boolean short-circuit evaluation, break/continue, for loops, ternary expressions, string literals and f-strings, objects with fields/methods/postinit, GC-managed lists, single inheritance with vtable-based virtual dispatch, complex access chains, indexed field assignment, string methods (strip, split, indexing), builtins (ord, int, input), augmented assignment operators (+=, -=, *=, //=, %=), and with entry { ... } blocks. All heap allocations use Boehm GC. Validated end-to-end with a fully native chess game.
  • jac run for .na.jac Files: Running jac run file.na.jac now compiles the file to native machine code and executes the jac_entry function directly, bypassing the Python import machinery entirely. Native execution runs as pure machine code with zero Python interpreter overhead at runtime.
  • LSP Semantic Token Manager Refactor: Refactored the language server's SemTokManager for production robustness. Deduplicated ~170 lines of shared symbol resolution logic.
    • Automatic Version Pinning in jac.toml: When running jac add <package_name> without specifying a version, the detected installed version is automatically added to jac.toml, falling back to >=0.0.0 if detection fails.
  • Configuration Profiles: Added multi-file configuration support with profile-based overrides. Projects can now use jac.<profile>.toml files (e.g., jac.prod.toml, jac.staging.toml) for environment-specific settings and jac.local.toml for developer-specific overrides that are automatically gitignored. Files are merged in priority order: jac.toml (base) < jac.<profile>.toml < [environments.<profile>] in-file overrides < jac.local.toml. Activate a profile via --profile flag on execution commands (e.g., jac run app.jac --profile prod, jac start --profile staging), the JAC_PROFILE environment variable, or [environment].default_profile in jac.toml. The jac config path command now displays all loaded config files with their priority labels. The JAC_ENV environment variable is deprecated in favor of JAC_PROFILE.
  • Configuration Profile Bug Fixes: Fixed several issues in the multi-profile config implementation: storage.type key now correctly maps to the storage_type field during profile merges, nested plugin configs use deep merge instead of shallow overwrite (preserving nested keys), apply_profile now handles all config sections (build, format, dot, cache, storage, check, project, dependencies, scripts -- previously only run, serve, test, and plugins), circular profile inheritance is detected and short-circuited instead of causing RecursionError, mutable default config_files field replaced with None sentinel to prevent cross-instance sharing, and config-to-CLI-args bridging added so profile values (e.g., serve.port) correctly override argparse defaults at runtime.
  • JsxElement Builtin Type: Added JsxElement builtin type for strict type checking of JSX expressions for client-side UI components.
  • 1 Small Refactors

jaclang 0.9.13#

  • Configurable Lint Rules: Auto-lint rules are now individually configurable via jac.toml [check.lint] section using a select/ignore model. A LintRule enum defines all 12 rules with kebab-case names. Use select = ["default"] for code-transforming rules only, select = ["all"] to enable every rule including warning-only rules, ignore = ["rule-name"] to disable specific ones, or select = ["rule1", "rule2"] to enable only listed rules.
  • No-Print Lint Rule: Added a no-print lint rule that errors on bare print() calls in .jac files, encouraging use of the console abstraction instead. Included in the "all" group; enable via select = ["all"] or select = ["default", "no-print"] in [check.lint].
  • Format Command Lint Errors: jac format --fix now reports lint errors (e.g., [no-print]) with file, line, and column info, and returns exit code 1 when violations are found.
  • ES Module Export Generation: Exports now generated at compiler level via ESTree nodes instead of regex post-processing. Only :pub declarations are exported.
  • Hot fix: call state: Normal spawn calls inside API spawn calls supported.
  • --no_client flag for jac start: Added --no_client CLI flag that skips eager client bundling on server startup. Useful when we need to run server only.
  • Enhanced Client Compilation for Development: Improved the jac start --dev command to perform initial client compilation for HMR.

jaclang 0.9.12#

  • Native Binary Compilation via na {} Blocks and .na.jac Files: Added a third compilation target to Jac using na {} context blocks and .na.jac file conventions. Code within the na context compiles to native LLVM IR via llvmlite and is JIT-compiled to machine code at runtime. Functions defined in na {} blocks are callable via ctypes function pointers. Supports integer, float, and boolean types, arithmetic and comparison operators, if/else and while control flow, recursive function calls, local variables with type inference, and print() mapped to native printf. Native code is fully isolated from Python (sv) and JavaScript (cl) codegen -- na functions are excluded from both py_ast and es_ast output, and vice versa. The llvmlite package is now a core dependency.
  • SPA Catch-All for BrowserRouter Support: The jac start HTTP server now serves SPA HTML for unmatched extensionless paths when base_route_app is configured in jac.toml. This enables BrowserRouter-style client-side routing where direct navigation to /about or page refresh on /dashboard/settings serves the app shell instead of returning 404. API paths (/functions, /walkers, /walker/, /function/, /user/), /cl/ routes, and static file paths are excluded from the catch-all. The vanilla (non-React) client runtime (createRouter, navigate, Link) has also been updated to use pushState navigation and window.location.pathname instead of hash-based routing.
  • Startup error handling improvements: Aggregates initialization errors and displays concise, formatted Vite/Bun bundling failures after the API endpoint list.
  • Venv-Based Dependency Management: Migrated jac add/jac remove/jac install from pip install --target to stdlib venv at .jac/venv/. This eliminates manual RECORD-based uninstall logic and metadata cleanup workarounds, delegating all package management to the venv's own pip. No third-party dependencies added.
  • GET Method Support: Added full support for HTTP GET requests for both walkers and functions, including correct mapping of query parameters, support for both dynamic (HMR) and static endpoints, and customization via @restspec(method=HTTPMethod.GET).
  • Enhanced Hot Module Replacement: Improved client code recompilation to handle exports comprehensively, ensuring all exported symbols are properly updated during hot reloads.
  • Rest API Specifications Supported: Rest api specifications supported from jaclang. Developers can utilize it using @restspec() decorator.
  • Ensurepip Error Handling: Added a clear error message when venv creation fails due to missing ensurepip (common on Debian/Ubuntu where python3-venv is a separate package), with platform-specific install instructions.
  • Suppress Warnings in jac check: Added --nowarn flag to jac check command to suppress warning output while still counting warnings in the summary.
  • Rest API Specifications Supported: The @restspec decorator now supports custom HTTP methods and custom endpoint paths for both walkers and functions.
  • Custom Methods: Use method=HTTPMethod.GET, method=HTTPMethod.PUT, etc.
  • Custom Paths: Use path="/my/custom/path" to override the default routing.
  • Storage Abstraction: Added pluggable Storage interface with LocalStorage default implementation. Use store() builtin to get a configured storage instance. Configure via jac.toml [storage] or environment variables.
  • Static files support HMR: Added infrastructure for Hot Module Replacement during development. The file watcher now supports static assets files such as .css and images (.png, .jpg, .jpeg) in addition to .jac files, enabling automatic reloading of client-side code changes.
  • Internal: Explicitly declared all postinit fields across the codebase.
  • Build (jacpack): .jac/.gitignore now contains only a comment (not *), so compiled assets (e.g., compiled/) aren't ignored and Tailwind builds correctly.
  • Support Go to Definition for Nested Unpacking Assignments: Fixed symbol table generation to support recursive nested unpacking (e.g., [a, [b, c]] = val) ensuring all inner variables are registered.
  • Fix: Module Name Truncation in MTIR Scope Resolution: Fixed a bug where module names ending with 'j', 'a', or 'c' were incorrectly truncated due to using .rstrip(".jac") instead of .removesuffix(".jac"). This caused MTIR lookup failures and degraded functionality when the runtime tried to fetch metadata with the correct module name but found truncated keys (e.g., test_schematest_schem).

jaclang 0.9.11#

  • MTIR Generation Pass: Added MTIRGenPass compiler pass that extracts semantic type information from GenAI by call sites at compile time. The pass captures parameter types, return types, semstrings, tool schemas, and class structures into Info dataclasses (FunctionInfo, MethodInfo, ClassInfo, ParamInfo, FieldInfo). MTIR is stored in JacProgram.mtir_map keyed by scope path.

  • MTIR Bytecode Caching: Extended DiskBytecodeCache to cache MTIR maps alongside bytecode (.mtir.pkl files). MTIR is automatically saved after compilation and restored from cache on subsequent runs, avoiding redundant extraction.

  • Reactive Effects with can with entry/exit: The can with entry and can with exit syntax now automatically generates React useEffect hooks in client-side code. When used inside a cl codespace, async can with entry { items = await fetch(); } generates useEffect(() => { (async () => { setItems(await fetch()); })(); }, []);. Supports dependency arrays using list or tuple syntax: can with (userId, count) entry { ... } generates effects that re-run when dependencies change. The can with exit variant generates cleanup functions via return () => { ... } inside the effect. This provides a declarative, Jac-native way to handle component lifecycle without manual useEffect boilerplate.

  • @jac/runtime Import Syntax: Client-side runtime imports now use the npm-style @jac/runtime scoped package syntax instead of the previous jac:client_runtime prefix notation. Write cl import from "@jac/runtime" { useState, useEffect, createSignal, ... } in place of the old cl import from jac:client_runtime { ... }. The grammar no longer supports the NAME: prefix on import paths. The core bundler inlines @jac/runtime into the client bundle automatically, so no external dependencies are needed for basic fullstack apps.

  • JSX Comprehension Syntax: List and set comprehensions containing JSX elements now compile to JavaScript .map() and .filter().map() chains. Instead of verbose {items.map(lambda item: dict -> any { return <li>{item}</li>; })}, you can now write {[<li key={item.id}>{item.title}</li> for item in items]} or use double-brace syntax {{ <li>{item}</li> for item in items }}. Filtered comprehensions like {[<li>{item}</li> for item in items if item.active]} generate .filter(item => item.active).map(item => ...). This brings Python-style comprehension elegance to JSX rendering.

  • Type Checking Improvements:

  • Permissive Type Check for Node Collections in Connections: The type checker now accepts collections (list, tuple, set, frozenset) on the right side of connection operators (++>, <++>, etc.). Previously, code like root ++> [Node1(), Node2(), Node3()]; was incorrectly rejected. This is a temporary workaround until element type inference for list literals is fully implemented.
  • Exclude by postinit Fields from Required Constructor Parameters: Fields declared with by postinit are now correctly excluded from required constructor parameters during type checking. Previously, instantiating an object like SomeObj() with by postinit fields would incorrectly report missing required arguments, even though these fields are initialized via the postinit method.

jaclang 0.9.10#

  • Formatter Spacing Fixes: Fixed extra spaces before semicolons in report and del statements, and corrected semantic definition formatting to properly handle dot notation and = operator spacing.

jaclang 0.9.9#

Breaking Changes#

  • Removed jac build Command and JIR File Support: The jac build command and .jir (Jac Intermediate Representation) file format have been removed. Users should run .jac files directly with jac run. The bytecode cache (.jbc files in .jac/cache/) continues to provide compilation caching automatically. If you have existing .jir files, simply delete them and run the .jac source files directly.

Features and Improvements#

  • Console Plugin Architecture: Refactored console system to use a plugin-based architecture, removing the rich dependency from core jaclang. The base JacConsole now uses pure Python print() for all output, keeping jaclang dependency-free. Plugins (like jac-super) can override the console implementation via the get_console() hook to provide Rich-enhanced output with themes, panels, tables, and spinners. This maintains backward compatibility while allowing optional aesthetic enhancements through plugins.

  • User Management Endpoints: Added new user management endpoints to the jac start API server:

  • GET /user/info - Retrieve authenticated user's information (username, token, root_id)
  • PUT /user/username - Update the authenticated user's username
  • PUT /user/password - Update the authenticated user's password All endpoints require authentication via Bearer token and include proper validation to prevent unauthorized access.

  • Unified User and Application Database: The jac start basic user authentication system now stores users in the same SQLite database (main.db) as application data, instead of a separate users.json file. This provides ACID transactions for user data, better concurrency with WAL mode, and simplified backup/restore with a single database file as a reference (overridden for production jac-scale).

  • Improved JSX Formatter: The JSX formatter now uses soft line breaks with automatic line-length detection instead of forcing multiline formatting. Attributes stay on the same line when they fit within the line width limit (88 characters), producing more compact and readable output. For example, <button id="submit" disabled /> now stays on one line instead of breaking each attribute onto separate lines.

  • Template Bundling Infrastructure: Added jac jacpack pack command to bundle project templates into distributable .jacpack files. Templates are defined by adding a [jacpack] section to jac.toml with metadata and options, alongside template source files with {{name}} placeholders. The bundled JSON format embeds all file contents for easy distribution, and templates can be loaded from either directories or .jacpack files for use with jac create --use.

  • Secure by Default API Endpoints: Walkers and functions exposed as API endpoints via jac start now require authentication by default. Previously, endpoints without an explicit access modifier were treated as public. Now, only endpoints explicitly marked with : pub are publicly accessible without authentication. This "secure by default" approach prevents accidental exposure of sensitive endpoints. Use : pub to make endpoints public (e.g., walker : pub MyPublicWalker { ... }).

  • Default main.jac for jac start: The jac start command now defaults to main.jac when no filename is provided, making it easier to start applications in standard project structures. You can still specify a different file explicitly (e.g., jac start app.jac), and the command provides helpful error messages if main.jac is not found.

  • Renamed Template Flags for jac create: The --template/-t flag has been renamed to --use/-u, and --list-templates/-l has been renamed to --list-jacpacks/-l. This aligns the CLI with jacpack terminology for clearer naming (e.g., jac create myapp --use client, jac create --list-jacpacks).

  • Flexible Template Sources for jac create: The --use flag now supports local file paths to .jacpack files, template directories, and URLs for downloading remote templates (e.g., jac create --use ./my.jacpack or jac create --use https://example.com/template.jacpack).

jaclang 0.9.8#

  • Recursive DFS Walker Traversal with Deferred Exits: Walker traversal semantics have been fundamentally changed to use recursive post-order exit execution. Entry abilities now execute when entering a node, while exit abilities are deferred until all descendants are visited. This means exits execute in LIFO order (last visited node exits first), similar to function call stack unwinding. The walker.path field is now actively populated during traversal, tracking visited nodes in order.
  • Imported Functions and Walkers as API Endpoints: The jac start command now automatically convert imported functions and walkers to API endpoints, in addition to locally defined ones. Previously, only functions and walkers defined directly in the target file were exposed as endpoints. Now, any function or walker explicitly imported into the file will also be available as an API endpoint.
  • Hot Module Replacement (HMR): Added --dev flag to jac start for live development with automatic reload on .jac file changes. When enabled, the file watcher detects changes and automatically recompiles backend code while Vite handles frontend hot-reloading. New options include -d/--dev to enable HMR mode, --api-port to set a separate API port, and --no-client for API-only mode without frontend bundling. Example usage: jac start --dev.
  • Default Watchdog Dependency: The jac create command now includes watchdog in [dev-dependencies] by default, enabling HMR support out of the box. Install with jac install --dev.
  • Simplified .jac Directory Gitignore: The jac create command now creates a .gitignore file inside the .jac/ directory containing * to ignore all build artifacts, instead of modifying the project root .gitignore. This keeps project roots cleaner and makes the .jac directory self-contained.
  • Ignore Patterns for Type Checking: Added --ignore flag to the jac check command, allowing users to exclude specific files or folders from type checking. The flag accepts a comma-separated list of patterns (e.g., --ignore fixtures,tests,__pycache__). Patterns are matched against path components, so --ignore tests will exclude any file or folder named tests at any depth in the directory tree.
  • Project Clean Command: Added jac clean command to remove build artifacts from the .jac/ directory. By default, it cleans the data directory (.jac/data). Use --all to clean all artifacts (data, cache, packages, client), or specify individual directories with --data, --cache, or --packages flags. The --force flag skips the confirmation prompt.

jaclang 0.9.7#

  • Unified jac start Command: The jac serve command has been renamed to jac start. The jac scale command (from jac-scale plugin) now uses jac start --scale instead of a separate command. This provides a unified interface for running Jac applications locally or deploying to Kubernetes.
  • Eager Client Bundle Loading: The jac start command now builds the client bundle at server startup instead of lazily on first request.
  • Configuration Management CLI: Added jac config command for viewing and modifying jac.toml project settings. Supports actions: show (display explicitly set values), list (display all settings including defaults), get/set/unset (manage individual settings), path (show config file location), and groups (list available configuration sections). Output formats include table, JSON, and TOML. Filter by configuration group with -g flag.
  • Reactive State Variables in Client Context: The has keyword now supports reactive state in client-side code. When used inside a cl {} block, has count: int = 0; automatically generates const [count, setCount] = useState(0);, and assignments like count = count + 1; are transformed to setCount(count + 1);. This provides a cleaner, more declarative syntax for React state management without explicit useState destructuring.
  • Consolidated Build Artifacts Directory: All Jac project build artifacts are now organized under a single .jac/ directory instead of being scattered across the project root. This includes bytecode cache (.jac/cache/), Python packages (.jac/packages/), client build artifacts (.jac/client/), and runtime data like ShelfDB (.jac/data/). The base directory is configurable via [build].dir in jac.toml. This simplifies .gitignore to a single entry and provides cleaner project structures.
  • Format Command Auto-Formats Related Files: The jac format command now automatically formats all associated annex files (.impl.jac and .cl.jac) when formatting a main .jac file. The format passes traverse impl modules in a single pass, and all related files are written together, ensuring consistent formatting across module boundaries.
  • Auto-Lint: Empty Parentheses Removal for Impl Blocks: The jac format --fix command now removes unnecessary empty parentheses from impl block signatures, matching the existing behavior for function declarations. For example, impl Foo.bar() -> int becomes impl Foo.bar -> int.
  • Enhanced Plugin Management CLI: The jac plugins command now provides comprehensive plugin management with list, disable, enable, and disabled subcommands. Plugins are displayed organized by PyPI package with fully qualified names (package:plugin) for unambiguous identification. Plugin settings persist in jac.toml under [plugins].disabled, and the JAC_DISABLED_PLUGINS environment variable provides runtime override support. Use * to disable all external plugins, or package:* to disable all plugins from a specific package.
  • Simplified NonGPT Implementation: NonGPT is now a native default that activates automatically when no LLM plugin is installed. The implementation no longer fakes the byllm import, providing cleaner behavior out of the box.

jaclang 0.9.4#

  • let Keyword Removed: The let keyword has been removed from Jaclang. Variable declarations now use direct assignment syntax (e.g., x = 10 instead of let x = 10), aligning with Python's approach to variable binding.
  • Py2Jac Robustness Improvements: Improved reliability of Python-to-Jac conversion with better handling of f-strings (smart quote switching, no keyword escaping in interpolations), match pattern class names, attribute access formatting (no extra spaces around dots), and nested docstrings in classes and functions.
  • Format Command Enhancements: The jac format command now tracks and reports which files were actually changed during formatting. The summary output shows both total files processed and the count of files that were modified (e.g., Formatted 10/12 '.jac' files (3 changed).). Additionally, syntax errors encountered during formatting are now printed with full error details.
  • Py2Jac Stability: Fixed conversion of Python code with augmented assignments and nested docstrings so generated Jac no longer redeclares targets or merges docstrings into following defs.
  • Support JS Switch Statement: Javascript transpilation for switch statement is supported.
  • F-String Escape Sequence Fix: Fixed a bug where escape sequences like \n, \t, etc. inside f-strings were not being properly decoded, causing literal backslash-n to appear in output instead of actual newlines. The fix correctly decodes escape sequences for f-string literal fragments in unitree.py.
  • Python -m Module Execution Support: Added ability for Jac modules to be executed directly via python -m module_name. When jaclang is auto-imported at Python startup (via a .pth file like jaclang_hook.pth), both single-file Jac modules and Jac packages (with __main__.jac) can be run using Python's standard -m flag.
  • Use Keywords as variable: Developers can now use any jaclang keywords as variable by using escape character <>. Example: <>from.
  • Props support: Support Component props system with Python kwargs style with props keyword. Ex: props.children.
  • Standalone .cl.jac Module Detection: .cl.jac files are now recognized as Jac modules both as standalone import targets (when no .jac exists) and as attachable client annexes.
  • Use Keywords as variable: Developers can now use any jaclang keywords as variable by using escape character <>. Example: <>from.
  • Strings supported without escaping within jsx: Strings supported without escaping within jsx. Example usage: <h1> "Authentication" App </h1>
  • Support output format for dot command: Output format for dot command is supported. Example Usage: jac dot filename.jac --format json
  • Shared impl/ Folder for Annex Discovery: Impl files can now be organized in a shared impl/ folder within the same directory as the target module. For example, impl/foo.impl.jac will be discovered and attached to foo.jac, alongside the existing discovery methods (same directory and module-specific .impl/ folders).
  • Type Checking Enhancements: Added type checking support for Final type hint.
  • Unified Plugin Configuration System: Introduced a standardized configuration interface for Jac plugins through jac.toml. Plugins can now register configuration schemas via get_plugin_metadata() and get_config_schema() hooks, with settings defined under [plugins.<plugin_name>] sections. This replaces environment variable-based configuration with a centralized, type-safe approach. Applied to jac-client, jac-scale and jac-byllm plugins.
  • Auto-Lint: hasattr to Null-Safe Conversion: The jac format --fix command now automatically converts hasattr(obj, "attr") patterns to null-safe access syntax (obj?.attr). This applies to hasattr calls in conditionals, boolean expressions (and/or), and ternary expressions. Additionally, patterns like obj.attr if hasattr(obj, "attr") else default are fully converted to obj?.attr if obj?.attr else default, ensuring consistent null-safe access throughout the expression.
  • Auto-Lint: Ternary to Or Expression Simplification: The auto-lint pass now simplifies redundant ternary expressions where the value and condition are identical. Patterns like x if x else default are automatically converted to the more concise x or default. This works with any expression type including null-safe access (e.g., obj?.attr if obj?.attr else fallback becomes obj?.attr or fallback).
  • Improved Null-Safe Subscript Operator ?[]: The null-safe subscript operator now safely handles invalid subscripts in addition to None containers (e.g., list?[10] returns None instead of raising an error; dict?["missing"] returns None for missing keys). This applies to all subscriptable types and makes ?[] a fully safe access operator, preventing index and key errors.
  • Support cl File without Main File: Developers can write only cl file without main jac files whenever main file is not required.
  • Support Custom headers to Response: Custom headers can be added by using an enviornmental variable [environments.response.headers] and mentioning the custom headers (Ex: "Cross-Origin-Opener-Policy" = "same-origin").

jaclang 0.9.3#

  • Fixed JSX Text Parsing for Keywords: Fixed a parser issue where keywords like to, as, in, is, for, if, etc. appearing as text content within JSX elements would cause parse errors. The grammar now correctly recognizes these common English words as valid JSX text content.
  • Support iter for statement: Iter for statement is supported in order to utilize traditional for loop in javascript.
  • JavaScript Export Semantics for Public Declarations: Declarations explicitly annotated with :pub now generate JavaScript export statements. This applies to classes (obj :pub), functions (def :pub), enums (enum :pub), and global variables (glob :pub), enabling proper ES module exports in generated JavaScript code.
  • Cross-Language Type Checking for JS/TS Dependencies: The type checker now supports loading and analyzing JavaScript (.js) and TypeScript (.ts, .jsx, .tsx) file dependencies when used with client-side (cl) imports. This enables type checking across language boundaries for files with client-language elements, allowing the compiler to parse and include JS/TS modules in the module hub for proper type resolution.
  • Formatter Improvements and Standardization: Enhanced the Jac code formatter with improved consistency and standardization across formatting rules.

jaclang 0.9.1#

-Side effect imports supported: side effect imports supported which will help to inject css.

  • Plugin for sending static files: Added extensible plugin system for sending static files, enabling custom static file serving strategies and integration with various storage backends.
  • Type Checking Enhancements:
  • Added type checking support for object spatial codes including the connect operator
  • Added type checking support for assign comprehensions and filter comprehensions
  • Improved type inference from return statements
  • Fixed inheritance-based member lookup in type system by properly iterating through MRO (Method Resolution Order) chain
  • Improved synthesized __init__ method generation for dataclasses to correctly collect parameters from all base classes in inheritance hierarchy
  • LSP Improvements: Added "Go to Definition" support for here and visitor keywords in the language server

jaclang 0.9.0#

  • Generics TypeChecking: Type checking for generics in vscode extension has implemented, i.e. dict[int, str] can be now checked by the lsp.
  • Plugin Architecture for Server Rendering: Added extensible plugin system for server-side page rendering, allowing custom rendering engines and third-party templating integration with transform, cache, and customization capabilities.
  • Improvements to Runtime Error reporting: Made various improvements to runtime error CLI reporting.
  • Node Spawn Walker supported: Spawning walker on a node with jac start (formerly jac serve) is supported.

jaclang 0.8.10#

  • Frontend + Backend with cl Keyword (Experimental): Introduced a major experimental feature enabling unified frontend and backend development in a single Jac codebase. The new cl (client) keyword marks declarations for client-side compilation, creating a dual compilation pipeline that generates both Python (server) and pure JavaScript (client) code. Includes full JSX language integration for building modern web UIs, allowing developers to write React-style components directly in Jac with seamless interoperability between server and client code.
  • Optional Ability Names: Ability declarations now support optional names, enabling anonymous abilities with event clauses (e.g., can with entry { ... }). When a name is not provided, the compiler automatically generates a unique internal name based on the event type and source location. This feature simplifies walker definitions by reducing boilerplate for simple entry/exit abilities.
  • LSP Threading Performance Improvements: Updated the Language Server Protocol (LSP) implementation with improved threading architecture for better performance and responsiveness in the VS Code extension.
  • Parser Performance Optimization: Refactored parser node tracking to use O(N) complexity instead of O(N²), drastically reducing parsing time for large files by replacing list membership checks with set-based ID lookups.
  • OPath Designation for Object Spatial Paths: Moved Path designation for object spatial paths to OPath to avoid conflicts with Python's standard library pathlib.Path. This change ensures better interoperability when using Python's path utilities alongside Jac's object-spatial programming features.
  • Import System Refactoring: Refactored and simplified the Jac import system to better leverage Python's PEP 302 and PEP 451 import protocols. Removed over-engineered custom import logic in favor of standard Python meta path finders and module loaders, improving reliability and compatibility.
  • Module Cache Synchronization Fix: Fixed module cache synchronization issues between JacMachine.loaded_modules and sys.modules that caused test failures and module loading inconsistencies. The machine now properly manages module lifecycles while preserving special Python modules like __main__.
  • Formatted String Literals (f-strings): Added improved and comprehensive support for Python-style formatted string literals in Jac with full feature parity.
  • Switch Case Statement: Switch statement is introduced and javascript style fallthrough behavior is also supported.

jaclang 0.8.9#

  • Typed Context Blocks (OSP): Fully implemented typed context blocks (-> NodeType { } and -> WalkerType { }) for Object-Spatial Programming, enabling conditional code execution based on runtime types.
  • Parser Infinite Loop Fix: Fixed a major parser bug that caused infinite recursion when encountering malformed tuple assignments (e.g., with entry { a, b = 1, 2; }), preventing the parser from hanging.
  • Triple Quoted F-String Support: Added support for triple quoted f-strings in the language, enabling multi-line formatted strings with embedded expressions (e.g., f"""Hello {name}""").
  • Library Mode Interface: Added new jaclang.lib module that provides a clean, user-friendly interface for accessing JacMachine functionality. This module auto-exposes all static methods from JacMachineInterface as module-level functions, making it easier to use Jac as a Python library.
  • Enhanced jac2py CLI Command: The jac2py command now generates cleaner Python code suitable for library use with direct imports from jaclang.lib (e.g., from jaclang.lib import Walker), producing more readable and maintainable Python output.
  • Clean generator expression within function calls: Enhanced the grammar to support generator expressions without braces in a function call. And python to jac conversion will also make it clean.
  • Support attribute pattern in Match Case: With the latest bug fix, attribute pattern in match case is supported. Therefore developers use match case pattern like case a.b.c.
  • Py2Jac Empty File Support: Added support for converting empty Python files to Jac code, ensuring the Py2Jac handles files with no content.
  • Formatter Enhancements: Improved the Jac code formatter with several fixes and enhancements, including:
  • Corrected indentation issues in nested blocks and after comments
  • Removed extra spaces in statements like assert
  • Preserved docstrings without unintended modifications
  • Enhanced handling of long expressions and line breaks for better readability
  • VSCE Improvements: Improved environment management and autocompletion in the Jac VS Code extension, enhancing developer experience and productivity.

jaclang 0.8.8#

  • Better Syntax Error Messages: Initial improvements to syntax error diagnostics, providing clearer and more descriptive messages that highlight the location and cause of errors (e.g., Missing semicolon).
  • Check Statements Removed: The check keyword has been removed from Jaclang. All testing functionality previously provided by check statements is now handled by assert statements within test blocks. Assert statements now behave differently depending on context: in regular code they raise AssertionError exceptions, while within test blocks they integrate with Jac's testing framework to report test failures. This unification simplifies the language by using a single construct for both validation and testing purposes.
  • Jac Import of Python Files: This upgrade allows Python files in the current working directory to be imported using the Jac import system by running export JAC_PYFILE_RAISE=true. To extend Jac import functionality to all Python files, including those in site-packages, developers can enable it by running export JAC_PYFILE_RAISE_ALL=true.
  • Consistent Jac Code Execution: Fixed an issue allowing Jac code to be executed both as a standalone program and as an application. Running jac run now executes the main() function, while jac start (formerly jac serve) launches the application without invoking main().
  • Run transformed pytorch codes: With export JAC_PREDYNAMO_PASS=true, pytorch breaking if statements will be transformed into non breaking torch.where statements. It improves the efficiency of pytorch programs.
  • Complete Python Function Parameter Syntax Support: Added full support for advanced Python function parameter patterns including positional-only parameters (/ separator), keyword-only parameters (* separator without type hints), and complex parameter combinations (e.g., def foo(a, b, /, *, c, d=1, **kwargs): ...). This enhancement enables seamless Python-to-Jac conversion (py2jac) by supporting the complete Python function signature syntax.
  • Type Checking Enhancements:
  • Added support for Self type resolution
  • Enabled method type checking for tools
  • Improved inherited symbol resolution (e.g., Cat recognized as subtype of Animal)
  • Added float type validation
  • Implemented parameter–argument matching in function calls
  • Enhanced call expression parameter type checking
  • Enhanced import symbol type resolution for better type inference and error detection
  • VSCE Improvements:
  • Language Server can now be restarted without requiring a full VS Code window reload
  • Improved environment handling: prompts users to select a valid Jac environment instead of showing long error messages
  • Formatter Bug Fixes:
  • Fixed if/elif/else expression formatting
  • Improved comprehension formatting (list/dict/set/gen)
  • Corrected decorator and boolean operator formatting
  • Fixed function args/calls formatting (removed extra commas/spaces)
  • Fixed index slice spacing and redundant atom units

jaclang 0.8.7#

  • Fix jac run same_name_of_jac.py- there was a bug which only runs jac file if both jac and python files were having same name. It was fixed so that developers run python files which has same name as jac with jac run command. (Ex: jac run example.jac, jac run example.py)
  • Fix jac run pythonfile.py bugs: Few bugs such as init is not found, SubTag ast node issue, are fixed. So that developers can run jac run of python files without these issues.
  • Fix lambda self injection in abilities: Removed unintended self parameter in lambdas declared inside abilities/methods.
  • Fix jac2py lambda annotations: Stripped type annotations from lambda parameters during jac2py conversion to ensure valid Python output while keeping them in Jac AST for type checking.

  • TypeChecker Diagnostics: Introduced type checking capabilities to catch errors early and improve code quality! The new type checker pass provides static analysis including:

  • Type Annotation Validation: Checks explicit type annotations in variable assignments for type mismatches
  • Type Inference: Simple type inference for assignments with validation against declared types
  • Member Access Type Checking: Type checking for member access patterns (e.g., obj.field.subfield)
  • Import Symbol Type Checking: Type inference for imported symbols (Basic support)
  • Function Call Return Type Validation: Return type checking for function calls (parameter validation not yet supported)
  • Magic Method Support: Type checking for special methods like __call__, __add__, __mul__
  • Binary Operation Type Checking: Operator type validation with simple custom operator support
  • Class Instantiation: Type checking for class constructor calls and member access
  • Cyclic Symbol Detection: Detection of self-referencing variable assignments
  • Missing Import Detection: Detection of imports from non-existent modules

Type errors now appear in the Jac VS Code extension (VSCE) with error highlighting during editing.

  • VSCE Semantic Token Refresh Optimization: Introduced a debounce mechanism for semantic token refresh in the Jac Language Server, significantly improving editor responsiveness:
  • Reduces redundant deep checks during rapid file changes
  • Optimizes semantic token updates for smoother editing experience

  • Windows LSP Improvements: Fixed an issue where outdated syntax and type errors persisted on Windows. Now, only current errors are displayed

jaclang 0.8.6#

jaclang 0.8.5#

  • Removed LLM Override: function_call() by llm() has been removed as it was introduce ambiguity in the grammer with LALR(1) shift/reduce error. This feature will be reintroduced in a future release with a different syntax.
  • Enhanced Python File Support: The jac run command now supports direct execution of .py files, expanding interoperability between Python and Jac environments.
  • Jac-Streamlit Plugin: Introduced comprehensive Streamlit integration for Jac applications with two new CLI commands:
  • jac streamlit - Run Streamlit applications written in Jac directly from .jac files
  • jac dot_view - Visualize Jac graph structures in interactive Streamlit applications with both static (pygraphviz)
  • Improved Windows Compatibility: Fixed file encoding issues that previously caused UnicodeDecodeError on Windows systems, ensuring seamless cross-platform development.
  • Fixed CFG inaccuracies: Fixed issues when handling If statements with no Else body where the else edge was not captured in the CFG (as a NOOP) causing a missing branch on the CFG of the UniiR. Also fixed inaccuracies when terminating CFG branches where return statements and HasVariables had unexpected outgoing edges which are now being removed. However, the return still keeps connected to following code which are in the same scope(body) which are dead-code.

  • CFG print tool for CLI: The CFG for a given program can be printed as a dot graph by running jac tool ir cfg. filename.jac CLI command.

jaclang 0.8.4#

  • Support Spawning a Walker with List of Nodes and Edges: Introduced the ability to spawn a walker on a list of nodes and edges. This feature enables initiating traversal across multiple graph elements simultaneously, providing greater flexibility and efficiency in handling complex graph structures.
  • Bug fix on supporting while loop with else part: Now we are supporting while loop with else part.

jaclang 0.8.3#

  • JacMachine Interface Reorganization: The machine and interface have been refactored to maintain a shared global state(similar to Python's sys.modules) removing the need to explicitly pass execution context and dramatically improving performance.
  • Native Jac Imports: Native import statements can now be used to import Jac modules seamlessly into python code, eliminating the need to use _.jac_import().
  • Unicode String Literal Support: Fixed unicode character handling in string literals. Unicode characters like "", "○", emojis, and other international characters are now properly preserved during compilation instead of being corrupted into byte sequences.
  • Removed Ignore Statements: The ignore keyword and ignore statements have been removed as this functionality can be achieved more elegantly by modifying path collection expressions directly in visit statements.

jaclang 0.8.1#

  • Function Renaming: The dotgen built-in function has been renamed to printgraph. This change aims to make the function's purpose clearer, as printgraph more accurately reflects its action of outputting graph data. It can output in DOT format and also supports JSON output via the as_json=True parameter. Future enhancements may include support for other formats like Mermaid.
  • Queue Insertion Index for Visit Statements: Visit statements now support queue insertion indices (e.g., visit:0: [-->] for depth-first, visit:-1: [-->] for breadth-first) that control where new destinations are inserted in the walker's traversal queue. Any positive or negative index can be used, enabling fine-grained control over traversal patterns and supporting complex graph algorithms beyond simple depth-first or breadth-first strategies.
  • Edge Ability Execution Semantics: Enhanced edge traversal behavior with explicit edge references. By default, [-->] returns connected nodes, while [edge -->] returns edge objects. When walkers visit edges explicitly using visit [edge -->], abilities are executed on both the edge and its connected node. Additionally, spawning a walker on an edge automatically queues both the edge and its target node for processing, ensuring complete traversal of the topological structure.
  • Jac Imports Execution: Jac imports (Jac.jac_import) now run in a Python-like interpreter mode by default. Full compilation with dependency inclusion can only occur when explicitly calling compile from the JacProgram object.
  • Concurrent Execution with flow and wait: Introduced flow and wait keywords for concurrent expressions. flow initiates parallel execution of expressions, and wait synchronizes these parallel operations. This enables efficient parallel processing and asynchronous operations directly within Jac with separate (and better) semantics than python's async/await.

Version 0.8.0#

  • impl Keyword for Implementation: Introduced the impl keyword for a simpler, more explicit way to implement abilities and methods for objects, nodes, edges, and other types, replacing the previous colon-based syntax.
  • Updated Inheritance Syntax: Changed the syntax for specifying inheritance from colons to parentheses (e.g., obj Car(Vehicle)) for better alignment with common object-oriented programming languages.
  • def Keyword for Functions: The def keyword is now used for traditional Python-like functions and methods, while can is reserved for object-spatial abilities.
  • visitor Keyword: Introduced the visitor keyword to reference the walker context within nodes/edges, replacing the ambiguous use of here in such contexts. here is now used only in walker abilities to reference the current node/edge.
  • Lambda Syntax Update: The lambda syntax has been updated from with x: int can x; to lambda x: int: x * x;, aligning it more closely with Python's lambda syntax.
  • Object-Spatial Arrow Notation Update: Typed arrow notations -:MyEdge:-> and +:MyEdge:+> are now ->:MyEdge:-> and +>:MyEdge:+> respectively, to avoid conflicts with Python-style list slicing.
  • Import from Syntax Update: The syntax for importing specific modules from a package now uses curly braces (e.g., import from utils { helper, math_utils }) for improved clarity.
  • Auto-Resolved Imports: Removed the need for explicit language annotations (:py, :jac) in import statements; the compiler now automatically resolves imports.