Repository navigation
Jacket 0.20.0
Adds jacket, one front door and one config file for the toolchain, along with editor completion and dead-code features and a faster jaic. Checking a 60k-line program is about 1.5x faster than in 0.19.1, and building it about 1.4x faster.
Highlights
jacket, one command for the toolchain.jacket build,check,run,lint,fmtandlspare drop-ins forjaic,jailint,jaifmtandjailsp: every argument, single-dash Jai flags included, reaches the tool exactly as typed. Whatjacketadds is the project: with no.jaifile named it builds the configured or inferred entry file.jacket new,init,config,cleanandversionare its own. It ships in the release archives, the installers and the Homebrew formula. Seedocs/tools/jacket.md.- One config file,
jacket.toml. Its[project],[toolchain],[lint],[format]and[experimental]tables replacejai.toml,jailint.tomlandjaifmt.toml, andjaic,jacket,jailsp,jailintandjaifmtread the same file. The old files still work; the firstjacketcommand next to them writesjacket.toml(never underCI, never deleting anything), andjacket config migrate [--dry-run]does it on demand. - Entry files are inferred the same way everywhere: the configured entry, then
build.jai/first.jai, thenmain.jai/src/main.jai, thenmodule.jai.
Added
jailspcompletes a procedure as a call when the client takes snippets:name($0)leaves the cursor inside the parentheses and opens parameter hints,name()$0for a procedure without parameters. Parentheses are left out where the procedure is a value: before an existing(, after*or#procedure_of_call, insidetype_of(...), in a::or global:=initialiser, and in type positions. Settingjai.completion.callSnippets(completion.callSnippetsin the server's options):addParentheses(default),fillArguments(a placeholder per required parameter) ornone. Also in the Neovim plugin ascompletion.call_snippets.jailspoffers postfix completions after a value:flag.if,.while,.for,.return(at the start of a statement),.notand.ref, which rewrite the whole expression (if flag { },!flag,*flag).jailspshows// nameas an inlay hint after the closing brace of procedures, structs and enums of 25 lines or more. Settingjai.inlayHints.closingBraceMinLines(0turns it off), alsoinlay_hints.closing_brace_min_linesin the Neovim plugin.jailspcompletesif,forandwhileat the start of a statement as a skeleton with the cursor in the body, andcaseinside the braces ofif x == {.jailspfades code that can never run: statements after an exit, branches and loops a condition rules out (also by integer ranges and narrowing),cases a value cannot select, and#if/ifxarms that were not taken. They arrive asunreachablehints taggedUnnecessary, with no squiggle.- The browser formatter reads
jacket.toml: the playground driver finds the nearestjacket.tomlorjaifmt.toml, andjaifmt.wasm --config-name jacket.tomltakesjacket.tomltext. - A misplaced
removesays why it cannot apply: the loop is over a fixed-size array (with the[..]and[]types that would work), a range of numbers or is awhileloop, or there is no enclosing loop. It used to say only thatremoveis valid inside a for loop over an array.
Performance
- Source files are lexed and parsed on worker threads ahead of type checking. Workers crawl the
#loadand#importgraph from the main file, and type checking takes each finished file from a shared cache. Output is identical to a serial parse.-serial_parseturns the workers off, and-parse_threads Nsets their number (default: cores - 1, at most 4). - Name lookup no longer copies
usingand import lists on every lookup, skips exported-usingprobing for modules that have none, and remembers scopes whose imports hold nothing under a name. - On macOS, LLVM's own C++ allocations also go through mimalloc.
- Finding procedures that need no stack-trace instrumentation uses one parallel scan and a worklist instead of repeated passes.
Fixes
- In a polymorphic procedure that nothing calls, a declaration initialised by a call to another polymorphic procedure (
found, index := array_find(array.*, item)) got the type hint: ?and a?hover, because the editor refused the call. The call now takes the callee's signature with the caller's own$T, so the hints read: bool,: s64,: T,: [] T; a hint is never?. jailint --allow-incompleteprints how many programs did not compile completely on the target instead of one line for each;-vlists them.- A baked
$xs: [$N] Tparameter takes$Nfrom the element count of a constant array view (xs :: #run make()), where it used to say "array kind mismatch". - Assigning to an indexed expression (
=and the compound assignments) on a type withoperator *[]calls it and writes through the returned pointer, where it used to say "cannot assign to a value of type"; reads keep usingoperator []. - A
[$N] Tparameter takes$Nfrom the element count of an untyped.[...]argument or default (f(.["a", "b"])makesN2), where it used to say it could not infer$N. A default.[...]whose length differs from the inferred count is an error at the default;[$N] $Tstill cannot get$Tfrom an untyped literal. - Vectors, quaternions and square matrices of the
Mathmodule compare with==and!=, where it used to say nooperator==matched. a op= bisa = a op bwhen nooperator op=accepts the operands (v /= 2.0with onlyoperator /= (*Vector2, Vector2)and aVector2 / float), where it used to be a no-overload error.- A
for_expansiontaking*Titerates aTvalue through its address when otherfor_expansionoverloads in scope have polymorphic first parameters that do not match, where it used to say none matched. - A
[$N] Tmodule parameter with a.[...]default has the default's count when the import gives no value, and can be forwarded to another module's parameter. - An overload that is not polymorphic is preferred to
$Twhen the argument needs only a mild conversion (a wider integer, a pointer to the struct), as in the reference compiler; an integer is still not made a float for it. - An untyped
.{...}argument no longer matches astringor other built-in aggregate parameter whose members its values cannot fill (an array literal fordata). value[xx x]on a struct withoperator []casts to the parameter of the overload picked for the struct, where it used to cast to an integer.- In
jailsp, a statement missing only its;at the end of a line keeps working: goto-definition, hover and highlighting work on it, and the error is shown on its last token instead of covering the statement's first name.
Breaking changes
| Before | Now |
|---|---|
jai.toml, jailint.toml, jaifmt.toml |
jacket.toml (the old files are still read, with a hint to migrate; a jacket.toml in the same directory wins) |
jai.toml build_files |
[project] entry |
JAIC_PARSE_THREADS=N, JAIC_SERIAL_PARSE=1 |
-parse_threads N, -serial_parse; [experimental] for projects |
JAIC_CODEGEN_UNITS=N |
-codegen_units N (-no_split for 1); [experimental] codegen_units |
JAIC_PROFILE=1, JAIC_PROFILE=ops |
-profile, -profile_ops |
JAIC_COVERAGE=FILE |
-coverage_file FILE |
JAIC_VERIFY_IR, JAIC_SPLIT_UNITS, JAIC_NO_SINK, JAIC_NO_FLOW, JAIC_DUMP_OPS, JAIC_BODY_PROBE, JAIC_BODY_ORDER, JAIC_O0_PASSES |
-dev_verify_ir, -dev_split_units, -dev_no_sink, -dev_no_flow, -dev_dump_ops, -dev_body_probe, -dev_body_fifo, -dev_o0_passes (for working on the compiler; may change) |
JAIC_CODEGEN_UNITS=4 for CI's native tests |
JACKET_TEST_CODEGEN_UNITS=4 (the test helpers pass -codegen_units 4) |
Kept: JAIC_STDLIB, JAIC_NATIVE_LIBS, JAIC_LINKER, JAIC_AR, JAIC_CACHE_DIR, JAIC_MEMORY_LIMIT, JAIC_DIAGNOSTICS, JAIC_DSYM, JAIC_COVERAGE_DIR/JAIC_COVERAGE_FILE (read by the instrumented program), JAIC_SANITIZER_CC, JAIC_WASM_LD and the installer variables. They locate things on the machine or are read where a flag cannot reach, rather than tuning one project.