Repository navigation
Releases: matteopolak/jacket
Release list
Jacket 0.21.0
New
- Two new lints:
constant_condition(a condition that is always true or false) andstring_literal_write(writing into a string literal). - Inlay hints for
itandit_indexinforloops. - Code after a call that never returns is faded as unreachable.
jaifmt --versionandjaifmt --is-ignored.-timingsshows memory allocated per phase.
Improved
- Rename in the editor is more complete (overloads, polymorphic parameters, loop variables and more) and refuses names it can't find.
- Hover and go-to-definition work in more places, including code not checked yet and module aliases.
Fixed
- Release archives were missing native libraries, most visibly on Windows (#13).
- Writing into a string literal at compile time is now an error instead of silently changing the literal everywhere (#14).
- Multisampled OpenGL windows on Windows (#15).
String.tprintand other Basic procedures throughString(#16).jacket runruns the executable the build actually produced.- Several error messages point at the right place, and a few invalid programs the reference compiler rejects are now rejected too.
- An editor hang on structs that refer to
#this.
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.
Jacket 0.19.1
Restores stdlib compatibility and makes jaic faster. Several stdlib changes in 0.19.0 made observable behaviour differ from the official standard library; they are reverted, and UTF-8 decoding of malformed input now matches it too.
Performance
jaicuses mimalloc instead of the system allocator, which makes checks and builds about 7–13% faster (a 60k-line check takes 137 ms instead of 154 ms).
Fixes
split_from_right(s, byte)returns(false, "", s)again when the byte is absent.scanchecks the%that%%matches but leaves it unconsumed again.Sort.quick_sortis no longer deprecated, andSortno longer exportsintro_sortorheap_sort.quick_sortkeeps the faster introsort from 0.19.0, and its key form still computes each key once.IntroSort.intro_sortis unchanged.- Strict
character_utf8_to_utf32again reports only the bytes before a bad continuation byte as consumed, which is 0 when the first continuation is bad. character_utf32_to_utf8encodes surrogates (U+D800 to U+DFFF) as themselves again.find_index_from_left(s, substring, start_index)asserts on a negativestart_indexinstead of treating it as 0.Window_Creationbuilds for Android again instead of failing with an#assert.character_utf8_to_utf32decodes malformed input to the payload bits of its bytes: a continuation byte without its10marker contributes its low six bits, and a stray continuation byte decodes as 0 in one byte, also in strict mode.
Jacket 0.19.0
Faster default builds and compile-time string code, a much faster jailsp on large workspaces, stdlib fixes found by fuzzing and review, and a new excessive_nesting lint. This release includes the changes planned for 0.18.1, which was not published separately. The breaking changes are small stdlib behaviour fixes, listed below.
Highlights
jailspkeeps up with typing. In this repository a keystroke is re-analysed in about 60 ms (it was about 700 ms when editing a stdlib file), and the first hover or completion answers in under 10 ms instead of about half a second. The server keeps its#loadgraph and per-module indexes across edits instead of rescanning the stdlib, and workspace symbols no longer resolve every file path. On very large single programs the remaining cost is the full type check.- Faster default builds. Library files skip the
#if OS/CPUbranches the target rules out while they are lexed and parsed, which removes most of an empty program's front-end time. Default builds are about 10% faster, and small leaf procedures no longer get a stack-trace node, so debug executables run up to 13% faster. - Faster compile-time string code.
String_Builderappends, integer printing and float formatting take far fewer steps, so string-heavy#runcode runs about twice as fast. - Stdlib and
#loaded files analyse correctly. A file inside a standard library tree is checked against that library, so itsinternal/...imports are allowed. A file that another module#loads is checked as part of that module. - New lint:
excessive_nesting.jailintreports blocks nested deeper thanmax_nesting(also--max-nesting). - Coverage builds.
jaic -coveragebuilds a program that records which code ran;tools/coverage/report.pyturns that into line and function coverage. See coverage. - Fuzzing builds.
-sanitize fuzzer(withaddress) instruments a program for libFuzzer and links its engine. See sanitizers.
Breaking changes
split_from_right(s, byte)returns(false, s, "")when the byte is absent, as thestringoverload andsplit_from_leftalready did. It returned(false, "", s).scanconsumes the%that%%matches, asscan2does.scan("%%", "%")now succeeds; it used to fail on the unconsumed byte.Sort.quick_sortis deprecated. The comparator form was a heap sort and the key form an insertion sort. Useintro_sort(a real introsort, also inSort; the key form computes each key once and keeps equal keys in order) orheap_sort.quick_sortforwards tointro_sortfor one release.- Strict
character_utf8_to_utf32reports at least the lead byte as consumed when a continuation byte is invalid (it could report 0). The consumed count for such input is now 1 or more. Window_Creationfails with a clear#asserton Android instead of leaving its procedures undefined.
Fixes
- A decode loop over invalid UTF-8 no longer stalls on a bad first continuation byte.
character_utf32_to_utf8encodes surrogates (U+D800 to U+DFFF) as U+FFFD.find_index_from_left(s, substring, start_index)treats a negativestart_indexas 0 instead of reading before the string.table_find_multipleprobes from the key's slot instead of scanning every entry, and aREFILL_REMOVED = falsetable no longer doubles under remove/add churn.- Substring search skips to the first byte of the substring, about ten times faster on large text.
- A declaration whose initializer has an error still declares its name, so one mistake no longer causes a cascade of "unknown identifier" errors.
- Completions rank named arguments first, then locals, the file's own names, the rest, internal names and auto-imports.
- In auto-import, a workspace's own stdlib is offered only through its public modules.
jailintandjaicrender diagnostics in linear time, so a file with thousands of findings no longer takes seconds to report.jailintalso caches neighbouring files between runs.- The interpreter runs compile-time code faster: dead local setup is removed, jumps are straightened, and copies, fills and tests use dedicated operations.
- On Windows, a compile-time call no longer loses the program's last error while the interpreter sets up a frame, and a failed check is reported at its own line.
- A compile-time call into a foreign library that is missing names the library.
- Path completion works inside a closed
#import "..."and with,fileand,dir; a completion after.with no expected type is about ten times faster. Machine_X64can be imported on every target, with feature checks that report no features and no-op hints off x64.- A missing
#import,fileprints only its path in the diagnostic. #runwith no result as a constant's value now says that it has no result.Inputkeeps its per-frame arrays on the default allocator when a program sets the context allocator to temp.Simpresolves its GL procedures once per context instead of on every draw.
For tool authors
jaicexposes more of what the editor tools need, andjailspandjailintnow use it instead of their own copies:jaic::project(settings, directive scan, load closure, root resolution, loader graph),jaic::paths(normalisation, canonical caches, stdlib discovery),jaic::source::LineIndex,jaic::lexer::lex_with_trivia(comments),#asmblock lookup, andide_facts::cursor_context.- The message callback receives a
ReportSeverity(ReportSink).
Jacket 0.18.0
The project has a new name: Jacket ("Jai kit"). This release also lets editors open code you don't trust safely, and makes jailint faster and easier to extend.
Note
The standard library has been rewritten, this release may be a bit unstable.
Highlights
- New name, same tools. The repository is now matteopolak/jacket.
jaic,jailsp,jaifmtandjailintkeep their names, and so do the crates. - Workspace trust. In an untrusted folder,
jailspchecks your code without running any of its compile-time code (#run,#insert, build metaprograms). VS Code follows its own workspace trust setting; in Neovim, passtrusted = falsetosetup(). See workspace trust. jailintis several times faster on large trees. It lints each file once and compiles programs in parallel (-j).--allow-incompletelets you lint a tree where some modules only build on another OS.- Better switch completion. The "add missing cases" action now works on every kind of
if x == { case ...; }, and its error covers the switch's header instead of the whole block. - Native console colours on Windows.
Print_Colorsets console colours through the Windows API, so they also work in consoles that don't understand ANSI escapes.
Breaking changes
| What | Was | Now |
|---|---|---|
| VS Code extension | matteopolak.jai-toolchain (and matteopolak.jai before that) |
matteopolak.jacket. It offers to uninstall the old one, and your jai.* settings carry over. |
| Neovim plugin | matteopolak/jai.nvim |
matteopolak/jacket.nvim. The jai Lua module and the :Jai command are unchanged. |
| Homebrew | matteopolak/tap/jai |
matteopolak/tap/jacket. brew upgrade moves you over. |
| Release archives | jai-<platform>.tar.gz |
jacket-<platform>.tar.gz (.zip on Windows), unpacking to jacket-<platform>/. |
How jailint recognises procedures |
by name (trim, min, free, ...) |
by a note on the declaration: @UseResult, @Minimum, @Maximum, @RemovesElements, @Releases. The standard library already has them; add them to your own procedures if you want the rules to apply. See declaration notes. |
ImGui and Curl printf-style procedures (Text, TreeNode, curl_msnprintf, ...) |
a Jai string format, forwarded through a libffi bridge that releases never shipped |
the C functions themselves: the format is a C string and the arguments are C varargs. Pass text arguments as *u8 (cast(*u8) "ok", name.data), and TextColored takes *ImVec4. The _CFormat names are gone. |
Earlier releases have been removed. Install 0.18.0 or later.
Fixes
- A
*voidconverts to a procedure type without a cast, as it should. #no_resetis accepted alongside other declaration modifiers.- A variable is never treated as a constant, even when its initializer is one.
- The interpreter runs
memcpy,memsetandmemcmpwithout extra system calls, so memory-heavy programs under-interprun faster, especially on Linux. - On Linux, a shared library marked
link_alwaysis no longer dropped by--as-needed. - On X11, an empty clipboard reads as empty text instead of failing, and fullscreen works without a window manager.