This is the changelog for the wax / wax-lib opam packages. dune-release reads the top entry's version and notes when cutting an opam release (see RELEASING.md). The VS Code extension keeps its own changelog, editors/vscode/CHANGELOG.md; the tree-sitter grammar keeps none, and both are versioned independently of the toolchain.
type word = i64; in Wax, (@type $word i64) in WAT, names a value type that can then be used wherever a value type is expected ((@type $word) in WAT). Defined differently in the branches of an #[if] or (@if ā¦), an alias stands for a different type in each configuration, so code that only passes such values around no longer has to be written once per configuration. A value read from a declaration written with an alias has the alias's type, so a local whose type is inferred from it is declared with the alias, and editors show the alias. The unused-field warning reports an alias nothing reachable uses. Aliases convert between Wax and WAT, conditional ones included. Compiling to the binary format, or --desugar, replaces them by the types they stand for.i8, i16, i32_s, ā¦), as it already could not be named after a value type; the conversion from WAT renames such a type.(@if $name ā¦)) and with -D. A name that is not one, such as $portable-int, used to be accepted but could not be converted to Wax. A keyword is a valid name, and Wax now accepts it in a condition (#[if(loop)]).--try-table flag: rewrite the legacy exception-handling instructions (try with catch/catch_all) into the standard try_table, for engines and tools that only accept the current exception-handling proposal. It works on any input, including WAT and Wasm files using legacy exceptions; on a Wax-to-Wax conversion, each try_legacy becomes a structured try.i64 result in one branch and an i32 one in the other shows fn() -> i64 | fn() -> i32, where only the first was shown.--faithful no longer rejects a module that converts a non-null value with extern.convert_any or any.convert_extern and uses the result where a non-null reference is expected (a (ref extern) function result, for example): the conversion's result was typed nullable.try taking block parameters is no longer rejected when it has a catch_all handler: the handler was wrongly expected to receive the parameters, which the body consumes.try taking block parameters in expression position is now reported as an error, as a block, loop or if there is, instead of crashing the type checker.try without a result type now takes it from its context, as try_legacy does: a value it yields as a function's result, for example, is no longer typed i32 when the function returns f32, which made a literal in dead code after a throw a type error.-D or an access in each branch converts.struct.get or struct.set reads or writes when the value's type is a subtype that names the field differently (or names another field the same): the receiver is ascribed the instruction's type, (x : &?s1).f, so the field is looked up there.i64 result in one branch and an i32 one in the other, a global or a struct field declared likewise.(@if ā¦) (or #[if]) with different fields. Each branch now uses its own field names.br_on_cast_fail, br_on_cast or br_on_null) whose code branches to the block's label again, for example two br_on_cast_fail to the same block. Such blocks now stay as blocks instead of being turned into a match. The same conversion also no longer fails with "The variable 'ā¦' is not bound", or silently produces code that computes a different result, when a local set by such a type test is used again outside it (by a second test, or on the next iteration of a loop).try_table catch clause or of a branch that skips the loop's final update, nor with "This expression occurs before a hole" on a try_table whose caught value feeds a compound assignment such as x += ā¦.Wax.output_binary takes a ?label_names argument; passing false leaves label names out of the name section. Binaryen's wasm-opt does not know this subsection and warns about it.Wax.wat_to_binary and Wax.wax_to_binary take ?try_table, the embedding counterpart of --try-table.; of a block is now optional. Writing it still does not discard the statement's value, and the formatter keeps emitting the canonical trailing ;.(e : t). It asserts a type instead of converting to it: the operand's type must already be a subtype of t, the expression compiles to nothing, and an ascription that does not hold is a compile-time error. Use it to pin a literal's width, or to widen a reference without a runtime cast.#[freq = n], #[never_opt], #[always_opt] and #[targets(f: 0.73, ā¦)] prefixing an expression, and #[priority = n], #[optimization = n] and #[run_once] on a function._ is now rejected, with an explanation, as a match scrutinee, a dispatch index or a while condition. Those positions desugar to a nested block, where a hole draws from the block's own stack and could never pick up the intended value.custom-descriptors follows two upstream rule changes: a type and its descriptor must agree on finality, and the "L" subtyping rule is gone. Exact allocation types are now gated on the feature as well.compact-import-section treats the text form as authoritative for import layout. A Wax import "m" { ⦠} block (or a WAT (import "m" (item ā¦) ā¦) group) lowers to a compact entry, a one-item block flattens to a plain import, and imports written separately are never merged. A binary input, which has no authorial layout, still gets its runs of same-module imports coalesced. The shared-type text form cannot bind identifiers, so a group whose items carry names takes the one-type-per-item form and the binary encoder restores the shared-type encoding.#[if] / (@if) conditional, since the name applies in every configuration.--faithful: decompile without the stream-reshaping recoveries, so a WAT or Wasm module converted to Wax recompiles with the same reachable instruction structure. Wax output only. The new Round-Tripping page states the contract and the few inert divergences that remain.--desugar now synthesizes the declarative element segment that Wax's lenient reader lets a module omit, so the output passes strict spec validation, including after resolving conditionals with -D. The metadata.code.* hint annotations are real WebAssembly text and are kept.--error-format human appends the warning's -W name to the header (Warning [unused-local]:). With json, a diagnostic carrying a machine-applicable fix gained an edit object, and syntax errors now emit hint, related and edit like every other diagnostic.--source-map is rejected as a usage error when the input is a Wasm binary, which carries no source to map.--debug categories width-check and width-record, self checks on the decompiler's numeric-width recording.unused-field and unused-import now ask reachability from the module's roots rather than the mere presence of a reference, so a dead cycle of mutually recursive functions or types is reported. They span every named index space: functions, globals, memories, tables, tags, types, and passive data and element segments.unnecessary-mut (shown by default): a module-defined, non-exported global declared mutable that no assignment ever targets, so it could be a const.confusable-unicode (shown by default): a "Trojan Source" bidirectional control character in a string the module carries (an export or import name, a string literal, a data segment), which can make the source read differently than it runs.suggestion group, hidden by default and reported at a distinct Suggestion severity, each entry carrying a machine-applicable rewrite: compound-assignment (x = x + e to x += e), field-punning ({x: x} to {x}) and redundant-annotation (a type the inference already pins).dead-code also reports a #[if] / (@if) branch that no configuration can select.unused-local, unused-label, precedence and redundant casts, so editors can offer them.cast-always-fails reports only the innermost cast of a chain, where the fix belongs.x - x on floats, any/extern conversions, a leading sign on a constant operand, and SIMD vector methods now classified as effect-free. Constant expressions are linted too, not just function bodies.stele, a message generator vendored with the repository, and are golden-tested. They name the construct under repair, underline the right token, and fall back to hand-written wording for the states heuristics cannot serve.; or operand) offers that insertion as a fix.br_table targets, block parameters and results, a missing block argument, the arms of an if and of a structured try, the intrinsic name in an arity error, a non-function callee at the call site, a missing ; at the end of the previous lexeme.textDocument/codeAction quick fixes over LSP, mirroring the VS Code code-action provider.let's initializer is kept in the outer scope.copy, length, switch, a SIMD lane operation). The type checker chose the intrinsic by name and argument count, while the lowering had always chosen by the receiver's type. Field names come from the name section, so a decompiled module could carry one and produce Wax that did not compile back.(@if) body are converted, imports are hoisted, and every configuration is checked as a whole rather than through a separate specializer.(type N) blocktype naming an implicit type resolves.dispatch and match arm labels.become of a binary intrinsic method, plus several crashes and a hang in typer error recovery.br_on_cast flags byte outside 0..3, overlong tag bytes, degenerate import groups, a non-finite or over-long hint payload, a br value that violates the block's result type, and a table with a non-nullable element type written with an inline element list, whose active segment fills the table at instantiation but leaves the table itself without a default. An out-of-f32-range float literal takes type f64.do, if, loop, try) whose value is left on the stack for a later statement keeps its result type. Two annotations that were each redundant on their own ā the block's result, and the cast pinning the same type ā were both dropped, and the Wax that came out no longer compiled.ref.cast, or a stray extern.convert_any, on the way back. The decompiler pins the type of an operand it cannot see, and those pins were landing on a neighbouring value instead of on the hole they were meant for, where they turned into real instructions. Affected a ref.cast into the extern hierarchy, both extern/any conversions, a call_ref callee, a field or element access receiver, array.len and i31.get_s.#[if] it could take a value one of the branches had pushed ā producing Wax that either did not compile back or gained a stray extern.convert_any.0x40 shorthand, funcidx element segments encode (ref func) rather than funcref, an empty name-section entry names nothing, and data strings are split at word boundaries.rec groups and catch arms always broken across lines, (do &t { x; }[0] = 1), and more faithful preservation of comments and of annotations the tokenizer does not interpret.Printer replaces Format throughout.wax-lib depends on cmdliner, and now needs menhir 20260122 or later: the parser and its error messages are built with options that release introduced.Driver.to_binary reports a failed lowering (a conditional that survived specialization, an unresolved reference) as a located diagnostic instead of letting an exception escape to the embedder.Parsing and Parser_error_runtime moved to lib-utils, Naming to lib-conversion.wax agent skill under skills/wax/, for coding assistants that read the SKILL.md format (npx skills add ocsigen/wax).opam install wax is documented now that both packages are in opam-repository.wax-lib).