Changelog¶
2026.06.02¶
Add a Bash JSON round-trip check to CI using the shared round-trip fixture, driven by the pinned
bashinterpreter. Because Bash associative arrays cannot nest, the check walks the known JSON shape and runsevalon each nested-collection string to rebuild a fresh array before re-emitting JSON.Add a C JSON round-trip check to CI using the shared round-trip fixture; because the literalizer’s
CValunion carries no run-time discriminator, the helper builds a parallelcJSONtree whose shape is generated from the parsed input (onecJSON_Create*call per node, reading theCValslot that matches the original JSON type) and prints it withcJSON_PrintUnformatted.Add a COBOL JSON round-trip check to CI using the shared round-trip fixture, compiled and run with GnuCOBOL and re-emitting JSON via the standard
JSON GENERATEstatement.JSON GENERATEskipsFILLERitems (so the literalizer’sFILLER-group arrays cannot be reconstructed), has no JSON boolean and no floating-point support, and mangles keys to COBOL data names, so the helper renames the recoverable top-level scalars back to their original keys with aNAME OFclause and excludes the array/object/boolean/float fields plus the integer-overflowingbigintegerand the width-truncatedstring_empty/string_unicodevalues.Add a Fortran JSON round-trip check to CI using the shared round-trip fixture, and switch the Fortran language module to emit
real(real64)literals (with the matchingfrealconstructor signature in thefval_mpreamble) so that double-precision values like1.7976931348623157e+308are no longer silently truncated to single precision.Add a MATLAB JSON round-trip check to CI using the shared round-trip fixture, serializing the literalized
myDatavalue with the built-injsonencode; the 26-digitbigintegerfield is excluded because MATLAB’s only numeric type is the IEEE 754double, which re-emits it as1e26.Add a Mojo JSON round-trip check to CI using the shared round-trip fixture, run with
mojo run. The shared input’s mixed-type top-level object is literalized with theVARIANTheterogeneous strategy and re-emitted by copying each value out of itsVariantslot into a CPythondictand callingjson.dumpson it through Mojo’sstd.pythonbridge to CPython (Mojo has no JSON encoder of its own). The widebigintegerfield (which overflows Mojo’s 64-bitInt) and the array/object fields (which theVARIANTstrategy cannot place alongside scalars in one dict) are excluded.Add a Nix JSON round-trip check to CI using the shared round-trip fixture, evaluating the literalized expression with
nix-instantiate --eval --strict --jsonso that Nix’s built-in JSON printer re-emits the value.Add a Norg JSON round-trip check to CI using the shared round-trip fixture. The literalizer stores the value inside a
@code jsonranged verbatim tag, so the check parses the generated document with thetree-sitter-norggrammar, pulls the embedded code block back out, and re-parses it with the standard libraryjsonmodule.Add a PowerShell JSON round-trip check to CI using the shared round-trip fixture, serializing the literalized
$myDatavalue with the built-inConvertTo-Json -Depth 100 -Compress; the 26-digitbigintegerfield is excluded because PowerShell parses it as a[double]and re-emits it with a fractional part.Add a Scheme JSON round-trip check to CI using the shared round-trip fixture, driven by Guile 3 and the
guile-jsonlibrary.Add a SystemVerilog JSON round-trip check to CI using the shared round-trip fixture, built and run with
verilator --binary. Because the literalizer’s_VValis a flat tagged record (it has no recursive component and no boolean tag), nested containers are serialized to opaque strings and booleans share the integer slot, so the helper generates the re-emitting walk from the parsed input’s shape (one expression per top-level key, reading the_VValslot that matches the original JSON type) and excludes the array/object fields, which cannot be walked back into at run time.Rework the Forth language to emit a structured visitor stream. The literalized
: my_data ... ;definition now calls small constructor words (+obj/-obj/+arr/-arr/+key/+int/+float/+str/+bool/+null) that preserve the document structure, instead of the previous flat sequence of stack pushes that dropped all array and object boundaries. The constructor words are a protocol the caller binds: literalizer ships a default binding (src/literalizer/languages/forth_prelude.fs) that writes JSON through the Forth Foundation Libraryjosmodule, so a literalized definition prints the document as JSON out of the box, while a caller can redefine any word to build a Forth-side structure or emit another format.Add
Odin(json_type=Odin.json_types.JSON_VALUE), the Odin sibling of the existing Zig / C++ / Haskell / OCaml / PureScript JSON-value modes, so output renders the literalized document as a singlejson.parse_stringcall against an embedded JSON text rather than the defaultmap[string]any/[dynamic]anyshape. The rendered binding therefore has the static typejson.Value(thecore:encoding/jsonsum type), so the value flows directly throughjson.marshalinstead of needing a hand-rolledany-walker, andheterogeneous_strategy=RECORDis rejected because the generatedstructdeclarations cannot coexist with the single parsed value.Expose the nested
JsonTypesandBoolFormatsenum classes (plus their snake_casejson_types/bool_formatsaliases) on everyLanguagesubclass, using an empty enum for languages that do not support these options. Consumers can now enumerate these options uniformly across languages without reflection helpers.Add a
json_type=Scheme.json_types.GUILE_JSONoption to the Scheme language that renders objects as Scheme association lists, arrays as vectors, and null as'nullso the literalized binding can be handed directly to guile-json’sscm->jsonwithout an intermediate shape walker.Change the default
Schemedict and ordered-map rendering from a flat alternating(list "k" v "k" v ...)to an association list of cons pairs ((list (cons "k" v) ...)). Each entry is now apair?rather than a bare scalar, so a non-empty mapping is locally distinguishable from a heterogeneous sequence, and the form is whatassoc/alist->hash-tableexpect. The empty case stays(list)for both an empty dict and an empty sequence.Add
C(json_type=C.json_types.CJSON), which renders the literalized document as a portablecJSON_Create*(...)node tree (onecJSON *statement per node, composed withcJSON_AddItemToArray/cJSON_AddItemToObject) under#include <cjson/cJSON.h>instead of the default taggedCValunion. Integers are widened todouble(cJSONhas no integer constructor) andheterogeneous_strategy=RECORDis rejected because the generatedstructdeclarations cannot coexist with thecJSONvalue type.Rewrite the C JSON round-trip CI check to literalize the shared fixture through
C(json_type=C.json_types.CJSON)and print it withcJSON_PrintUnformatted, dropping the Python-side walker that previously built a parallelcJSONtree from the parsed input. The check still reportsC round-trip OKwith the same two excluded fields.Size COBOL
PIC X(n)alphanumeric clauses by the UTF-8 byte length of the string rather than its character count, so that string literals containing characters that take more than one UTF-8 byte are no longer given an undersized picture and silently truncated by GnuCOBOL at runtime.Give colliding COBOL data names a numeric suffix so that two distinct JSON object keys that map to the same COBOL data name (because the character rewriting or the 30-character name limit collapses them together) no longer produce two sibling items with the same name in one group, which GnuCOBOL rejects as ambiguous when the name is referenced.
Add
Cobol(json_type=CJSON), which renders a document as acJSONnode tree built through COBOL’s CCALLinterface (cJSON_Create*composed withcJSON_AddItemTo*) rather than a WORKING-STORAGE record, so arbitrary string keys, JSON booleans, real numbers, heterogeneous arrays, nested objects, and the empty string are all represented faithfully. The COBOL JSON round-trip now uses this mode withcJSON_PrintUnformatted, so it reproduces every field of the shared input exceptbigintegerandfloat_large_exponent.Lead the README and documentation home page with a minimal
literalizecall so that the required arguments are obvious at a glance, and move the format-option configuration into a follow-up example instead of mixing default-valued arguments into the first example.Collapsed the repetitive per-language “JSON value types” documentation section into a single lookup table, keeping prose only for the unusual OCaml and D cases.
Document when to choose a
heterogeneous_strategyversus ajson_typefor mixed-type data: the heterogeneous strategies page now carries a “Which should I use?” note contrasting the statically typed wrappers with the single runtime JSON value type, and the two documentation sections cross-link each other.Add a “Common errors and how to resolve them” documentation page mapping each user-facing exception to its cause and the recommended fix.
Add TOML and JSON5 input examples to the documentation, showing that TOML comments are preserved and that JSON5’s relaxed syntax (comments, single-quoted strings, trailing commas, and hexadecimal numbers) is accepted.
Restructured the API reference into labeled sections (core functions, result type, variable forms, identifier cases, function calls, formatting configuration, language definition and exceptions) and stopped surfacing undocumented internals on the page.
Remove the incomplete custom-language sketch from the “Custom language implementations” documentation; it imported private, underscore-prefixed modules. The section now points readers to the built-in language modules as the worked example.
2026.05.28¶
Add Ruby, TypeScript, Haskell, Perl, PHP, Go, Zig, C#, Scala, Swift, Kotlin, Rust, D, TOML, Elm, OCaml, YAML, Julia, R, and Groovy JSON round-trip checks to CI using the shared round-trip fixture.
Add
Rust(json_type=Rust.json_types.SERDE_JSON_VALUE)to render values throughserde_json::json!instead of Rust’s narrow collection types.Add
Perl(bool_format=...)so booleans can round-trip through JSON and YAML libraries. The defaultPerl.BoolFormats.INTEGERkeeps the historic bare1/0output;JSON_PP_REFrenders\1/\0scalar references (the conventional form used byJSON::PP,JSON::XS,Cpanel::JSON::XS, andMojo::JSON);JSON_PP_SINGLETONrenders theJSON::PP::true/JSON::PP::falseblessed singletons with ause JSON::PP;preamble.Add
Cpp(json_type=Cpp.json_types.NLOHMANN_JSON)to render values throughnlohmann::json::parseinstead of C++’s narrowstd::vector/std::map/std::unordered_mapcollection types.Add
Java(json_type=Java.json_types.JACKSON_JSON_NODE)to render values through Jackson’sObjectMapper.readTreeso the binding has the static typecom.fasterxml.jackson.databind.JsonNodeinstead of Java’s narrowList/Maptypes.Add
CSharp(json_type=CSharp.json_types.SYSTEM_TEXT_JSON_NODE)to render values throughSystem.Text.Json.Nodes.JsonNodeinstead of C#’s narrow collection types.Add
Kotlin(json_type=Kotlin.json_types.KOTLINX_JSON_ELEMENT)to render values throughJson.parseToJsonElementso the binding has the static typekotlinx.serialization.json.JsonElementinstead of Kotlin’s narrowList/Maptypes.Add
Scala(json_type=Scala.json_types.CIRCE)to render values through CirceJson.obj/Json.arr/Json.fromXxxinstead of Scala’s narrow collection types.Add
Haskell(json_type=Haskell.json_types.AESON_VALUE)to render values through theaesonQQquasi-quote bracket fromData.Aeson.QQso the binding has the static typeData.Aeson.Valueinstead of Haskell’s narrow customValalgebraic type.Add
Zig(json_type=Zig.json_types.STD_JSON_VALUE)to render values throughstd.json.parseFromSliceso the binding has the static typestd.json.Valueinstead of Zig’s narrowZValtagged union.Add
Nim(json_type=Nim.json_types.JSON_NODE)to render values through the standard-libraryjson.JsonNodeand the%*macro instead of Nim’s narrow collection types.Add
Crystal(json_type=Crystal.json_types.JSON_ANY)to render values throughJSON.parse(%(...))into Crystal’s standard-libraryJSON::Anyinstead of nativeArrayandHashcollections.Add
D(json_type=None)to render D data through narrow native collections (raw scalars,T[]arrays,V[K]associative arrays) instead of the defaultstd.json.JSONValuewrapper, giving D users the same two-mode choice the otherjson_type-supporting languages have. D’s polarity is reversed:json_type=D.json_types.STD_JSON_VALUE(the default) matches the other languages’ opt-in JSON value rendering, whilejson_type=Nonematches their typed-collection defaults.Add an opt-in
Perl.string_formats.DOUBLE_UTF8variant that emits non-ASCII characters literally in double-quoted strings and contributesuse utf8;to the file preamble whenever the literalized value contains a non-ASCII string.Add an Ada JSON round-trip check to CI using the shared round-trip fixture.
Add a Clojure JSON round-trip check to CI using the shared round-trip fixture.
Add a Common Lisp JSON round-trip check to CI using the shared round-trip fixture.
Add a Crystal JSON round-trip check to CI using the shared round-trip fixture.
Add a Dart JSON round-trip check to CI using the shared round-trip fixture.
Add a Dhall JSON round-trip check to CI using a scalar-only subset of the shared round-trip fixture.
Add an Elixir JSON round-trip check to CI using the shared round-trip fixture.
Add an Erlang JSON round-trip check to CI using the shared round-trip fixture.
Add a Forth JSON round-trip check to CI using the shared round-trip fixture.
Add an F# JSON round-trip check to CI using the shared round-trip fixture.
Add a Gleam JSON round-trip check to CI using the shared round-trip fixture.
Add an HCL JSON round-trip check to CI using the shared round-trip fixture.
Add a Lua JSON round-trip check to CI using the shared round-trip fixture.
Add a Nim JSON round-trip check to CI using the shared round-trip fixture.
Add an Objective-C JSON round-trip check to CI using the shared round-trip fixture.
Add an Odin JSON round-trip check to CI using the shared round-trip fixture.
Add a Racket JSON round-trip check to CI using the shared round-trip fixture.
Add a Raku JSON round-trip check to CI using the shared round-trip fixture.
Add a Roc JSON round-trip check to CI using the shared round-trip fixture.
Add a Standard ML JSON round-trip check to CI using the shared round-trip fixture.
Add a Tcl JSON round-trip check to CI using the shared round-trip fixture.
Add a V JSON round-trip check to CI using the shared round-trip fixture.
Add a Visual Basic JSON round-trip check to CI using the shared round-trip fixture.
Add a Wren JSON round-trip check to CI using the shared round-trip fixture.
Replace the hand-rolled JSON encoder in the Java round-trip CI check with Jackson’s
ObjectMapper.The Kotlin JSON round-trip CI check now serializes via Jackson’s
ObjectMapperinstead of a hand-rolledtoJsonElementwalker over heterogeneousAny?plus primitive-array types.Raise
UnrepresentableEmptyDictErrorat literalize time when an empty mapping appears anywhere in the input for the Lua, PHP, and R language specifications. These languages have a single runtime collection type that cannot distinguish an empty mapping from an empty sequence, so emitting an empty-table literal would silently lose the mapping/sequence distinction on round-trip through any JSON encoder. The new exception is exposed fromliteralizer.exceptions.Raise
UnrepresentableIntegerErrorat literalize time for integers outside the target language’s representable range on the Go, TypeScript, Rust, D, and C++ language specifications, replacing silent emission of literals the target compiler would reject or silently truncate. Go and D now emit aUL/uint64(...)literal for positives up toulong.max/math.MaxUint64and raise above that; C++ retains its existingULLfallback and now raises aboveULLONG_MAX; Rust raises above thei128range; TypeScript raises aboveNumber.MAX_SAFE_INTEGER(2**53 - 1).Always emit a dotted mantissa in float scientific-notation literals (
1.0e+16rather than1e+16) so the output parses as a float in Ada, Cobol, Elixir, Erlang, Gleam, Nix, and YAML. Gleam additionally strips the+from positive exponents to satisfy its parser, which letsGleam’s JSON round-trip handle the full IEEE 754doublerange.Internal: rewrite the Kotlin and Zig JSON round-trip scripts on top of the
KOTLINX_JSON_ELEMENTandSTD_JSON_VALUEjson_typestrategies, dropping the bespokeAny?-to-JsonElementandZVal-to-std.json.Valuewalkers.std.json.Value.number_stringpreserves the 26-digitbigintegerfield end-to-end on Zig, so that exclusion is dropped there;kotlinx.serialization’sparseToJsonElementstill collapses it to aDouble, so the Kotlin exclusion is retained.Add
Erlang(json_type=Erlang.json_types.OTP_JSON)to render strings, ISO dates, datetimes, times, and base64-encoded bytes as UTF-8 binary literals (<<"..."/utf8>>), null as the bare atomnull, and sets as JSON arrays, so the rendered value feeds straight into Erlang’s built-injson:encode/1(available sinceOTP_27) without a normalization pass.Add
OCaml(json_type=OCaml.json_types.YOJSON_SAFE_T)to render values directly asYojson.Safe.tpolymorphic-variant literals (Bool,Int,Float,String,Null,List,Assoc,Intlit) so the binding has the static typeYojson.Safe.tinstead of OCaml’s generatedval_talgebraic type; arbitrary-precision integers route through theIntlitescape hatch.Document the F#
Valdiscriminated union’s JSON serialization limitations: theFMaptuple-list shape (chosen to preserve insertion order) andFSharp.SystemTextJson’sUntaggedencoding both preventSystem.Text.Json.JsonSerializer.Serializefrom producing valid JSON without a custom converter. TheFSharplanguage module now describes both pitfalls and points users at thewriteValhelper in.github/scripts/run_fsharp_roundtrip.pyas a starting template.Add
Elm(json_type=Elm.json_types.JSON_ENCODE_VALUE)to render values as idiomaticelm/jsonJson.Encode.*calls producing aJson.Encode.Valuedirectly, replacing the per-fixtureValADT.Add
FSharp(json_type=FSharp.json_types.SYSTEM_TEXT_JSON_NODE)to render values throughSystem.Text.Json.Nodes.JsonNodeinstead of F#’s generated taggedValdiscriminated union.Add
Gleam(json_type=Gleam.json_types.GLEAM_JSON_JSON)to render values directly asgleam_jsonbuilder calls (json.object,json.preprocessed_array,json.int,json.string, …) so the binding has the static typegleam/json.Jsoninstead of Gleam’s generatedGValtagged ADT.Add
PureScript(json_type=PureScript.json_types.ARGONAUT_JSON)to render values through afromRight jsonNull (jsonParser "...")expression so the binding has the static typeData.Argonaut.Core.Jsoninstead of PureScript’s narrow customValalgebraic type.Raise
UnrepresentableEmptyDictErrorat literalize time when an empty mapping appears anywhere in the input for the Ada language specification. The Ada literalizer’s unifiedA_Valaggregate cannot distinguish an emptyAMap'[]from an emptyAList'[]at run time, so emitting an empty-mapping literal would silently lose the mapping/sequence distinction on round-trip. Ada now joins Lua, PHP, and R in raising rather than emitting an ambiguous literal.
2026.05.21.1¶
Added
format_constructor_targetto theLanguageprotocol. It returns the language-specific target string for a zero-argument constructor call, suitable for passing toliteralize_call()astarget_function. The default isClassName; Java, JavaScript, TypeScript, C#, Scala, Go, Ruby, and Rust override it for their common constructor forms (new ClassName,NewClassName,ClassName.new, andClassName::new).
2026.05.21¶
No significant changes.
2026.05.20.1¶
Speed up backslash string formatting for strings that do not need escaping.
2026.05.20¶
Reduce recursive renderer overhead for large JSON inputs.
2026.05.18.1¶
Rustnow exposes a non-emptyModifiersenum with a singleMUTmember. PassingNewVariable(name=..., modifiers={Rust.Modifiers.MUT})(or the same vialiteralize_call()) renders a mutable binding (let mut name = ...;) instead of the immutable default (let name = ...;), so a value constructed and bound through the variable form can then be mutated through that binding. The modifier applies to the defaultLETdeclaration style; other declaration styles ignore it.Added two metadata properties to the
Languageprotocol:supports_multi_param_call_wrapper_stub(whether the language can declare and positionally invoke a wrapper stub that receives the call’s result alongside other positional arguments) andsupports_dict_literal_as_free_expression(whether a map literal can appear as a free-standing expression rather than only on the right-hand side of a typed assignment). Both are test-harness metadata, like the existinghas_free_function_callsandsupports_dotted_call_stubproperties;literalize_call()does not inspect them.
2026.05.18¶
literalize_call()can now bind a zero-argument call to avariable_form. Previously avariable_formwas rejected outright withper_element=Trueand the whole-value path (per_element=False) always passed one argument, so the zero-argument constructor case (p2 = Playlist(),let p2 = Playlist::new();) had no representation. Avariable_formis now accepted whenever the input produces exactly one call:per_element=Falseas before, orper_element=Trueover a single-element source (a[[]]source yields the zero-argument constructor). Zero calls or more than one call are rejected withUnsupportedCallShapeError. As part of this, a call-result binding no longer carries a type annotation derived from the call’s argument data (which a strict type-checker rejected, since the annotation describes the argument, not the call’s return type):Pythonnow binds the call result with a plainname = valueand the annotation-only preamble it would have required is no longer emitted.literalize_call()now honorscollection_layout=CollectionLayout.COMPACTfor the$zippedliteral exposed onCallContext. Previously a mapping paired throughzip_sourcealways rendered multi-line, even though the call-argument mapping rendered by the same call was single-line under the same setting, which split a one-linecall_transformacross several physical lines. The$zippedliteral is now rendered through the same whole-value path as call arguments, so a compact mapping stays on one line.Added support for Haxe as a new output language.
Haxerenders every collection literal with an explicit(... : Array<Dynamic>)or(... : Map<String, Dynamic>)cast, so heterogeneous and empty collections type-check without per-variable annotations. Maps use Haxe’s["key" => value]literal syntax and strings use the double-quoted form (no interpolation). Calls use positional syntax with local-function and anonymous-structure-closure stubs emitted insidestatic function main(). A newlint-haxejob in.github/workflows/lint.ymlcompiles and runs every.hxfixture in a singlehaxe --interpinvocation.
2026.05.17.1¶
CSharparray sequence-format no longer emitsusing System;orusing System.Collections.Generic;. A C# array literal (new T[] {...}) is a built-in language feature and requires nousingdirectives, so those lines were dead noise on the array path (for example aDictionarywhose values are arrays picked up a spurioususing System;).using System.Collections.Generic;is still emitted for the collection types that need it (List<T>,Dictionary<TKey, TValue>, and the set types), andusing System;forSystemscalar types such asDateOnly. The array empty form is now the typed literalnew T[] {}rather thanArray.Empty<T>(), keeping the array path free of anySystemreference. See #2524.literalize_call()now accepts abound_refsargument, the call-side counterpart ofliteralize()’s ownbound_refs. Withwrap_in_file=Trueit renders a complete, self-contained file: each ref is declared (cased viaref_case) ahead of the calls, a no-op stub for the target function is injected, and one reconciled preamble (header and body) is placed in front, so callers no longer hand-roll a duplicate-removal pass. Entries double asref_valuesand are emitted in iteration order. Recomputing the body preamble across the union of types and reconciling the data-dependent header block into a single copy covering every type replaces the previous multi-line preamble-filter heuristic. The same single-copy reconciliation now also backsliteralize()’s ownbound_refsdeclaration composition, removing the last preamble-filter heuristic there with no change to generated output. See #1946.The
supports_call_variable_bindinglanguage-class flag has been removed. Every language now binds aliteralize_call()result directly with no literal-only wrapping, so the flag wasTruefor all languages and itsUnsupportedCallShapeErrorrejection path was unreachable. No generated output changes. See #2521.
2026.05.17¶
Dnow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable. A D literal binding wraps the right-hand side to encode the parsed value’s runtime type (aJSONValue(...)projection, or a positionalRecord0(...)struct-constructor literal under theRECORDstrategy), which is wrong for a call whose return type is opaque to the renderer; the call result is now bound directly asauto my_data = make_widget(...);. No caller-supplied return-type hint is required: D infers the binding type from the initializer. Because the binding is anautodeclaration while theExistingVariableform is a bare assignment to an already-declared name, only theNewVariableform is golden-covered. Itssupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding and call-without-binding output is unchanged. Follow-up to #1961. See #2511.SystemVerilognow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable. A SystemVerilog literal binding wraps the right-hand side in a named_VValstruct literal derived from the parsed value’s runtime type, which is wrong for a call whose return type is opaque to the renderer; the call result is now bound directly asstatic _VVal my_data = make_widget(...);. SystemVerilog declarations are typed and have no inference, so the binding needs an explicit declared type; no caller-supplied return-type hint is required because every generated value-returning call stub returns the universal tagged_VValstruct, so the binding’s declared type is always_VVal(the same type a SystemVerilog scalar literal binding declares). The call stub is emitted at module scope rather than insideinitial begin, where afunctiondeclaration is illegal. Because the binding is a typed declaration while theZignow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable. A Zig literal binding wraps the right-hand side in a designated-initializer struct literal that encodes the value’s runtime type (a taggedZValunion projection, or a generatedRecord0struct literal under theRECORDstrategy) and declares an explicitZValvalue type, which is wrong for a call whose return type is opaque to the renderer; the call result is now bound with a plain inferred declaration,const my_data = make_widget(...);. No caller-supplied return-type hint is required: the Zigconst/vartype inference supplies the binding’s type. Because the binding is a keyword declaration while theExistingVariableform is a bare assignment to an already-declared name, only theNewVariableform is golden-covered. Itssupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding and call-without-binding output is unchanged. Follow-up to #1961. See #2510.Adanow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable. An Ada literal binding wraps the right-hand side in theA_Valconstructor chosen from the parsed literal’s runtime type, which is wrong for a call whose return type is opaque to the renderer; the call result is now bound directly asmy_data : A_Val := Make_Widget (...);. No caller-supplied return-type hint is required: every generated Ada call stub returnsA_Val, so the binding’s declared type is alwaysA_Val(the same type an Ada literal binding declares). Because the binding is a typed declaration while theExistingVariableform is a bare assignment to an already-declared name, only theNewVariableform is golden-covered. Itssupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding and call-without-binding output is unchanged. Follow-up to #1961. See #2509.Fortrannow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable. A Fortran literal binding wraps the right-hand side in thefval_tconstructor matching the parsed literal’s type (the integer, real, or string constructor), which is wrong for a call whose return type is opaque to the renderer; the call result is now bound directly astype(fval_t) :: my_datafollowed bymy_data = make_widget(...). No caller-supplied return-type hint is required: every generated Fortran call stub returnstype(fval_t), so the binding’s declared type is alwaystype(fval_t)(the same type a Fortran literal binding declares). The call stub is emitted in the program’scontainssection, after the binding, so a value-returning call no longer needs an inline procedure body. Because theNewVariablebinding is a typed declaration while theExistingVariableform is a bare assignment to an already-declared name, only theNewVariableform is golden-covered. Itssupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding and call-without-binding output is unchanged. Follow-up to #1961. See #2507. #2508.Cnow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable. A C literal binding wraps the right-hand side in a designated-initializer compound literal that encodes the value’s runtime type (a tagged-union projection, or astruct Record0 my_data = (struct Record0){...};aggregate under theRECORDstrategy), which is wrong for a call whose return type is opaque to the renderer; the call result is now bound directly asCVal my_data = make_widget(...);. No caller-supplied return-type hint is required: every generated call stub returns the universal taggedCValunion, so the binding’s declared type is alwaysCVal(the same type a C literal binding declares). Because the binding is a typed declaration while theExistingVariableform is a bare assignment to an already-declared name, only theNewVariableform is golden-covered. Itssupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding and call-without-binding output is unchanged. Follow-up to #1961. See #2506.Elixirnow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable. The call stub is emitted at module scope and the binding lives inside the generateddef x doentry function, somy_data = make_widget(42)no longer needs adefnested inside anotherdef. Because Elixir rebinds names with=, theExistingVariableoutput is identical to theNewVariableoutput. Itssupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding and call-without-binding output is unchanged. Follow-up to #1961. See #2226.Pythonnow emitsfrom __future__ import annotationsonly when the rendered code actually contains an annotation: aRECORD-strategy@dataclasses.dataclassblock or an inline variable type hint (an empty-collection helper hint, or any declaration undervariable_type_hints=ALWAYS). Call-only output and annotation-free literals no longer carry the unused future import. See #2495.Forthnow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable, binding the call result with the same colon definition Forth already uses for literal bindings:: my_data 42 make_widget ;. This is a deferred word that re-executesmake_widgeton every invocation, and (because Forth has no reassignment in this model) theExistingVariableoutput is identical to theNewVariableoutput; Forth’sVALUE/TOidiom was rejected because it holds only a single cell and cannot represent the string/dict/sequence values the colon form already covers. Itssupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding output is unchanged. Follow-up to #1961. See #2456.Nimnow acceptsvariable_formonliteralize_call(), binding the call result directly:var my_data = make_widget(42). The literal-binding declaration template prefixes the right-hand side with the%*json macro (or@for sequences) chosen from the parsed literal’s type, which JSON-constructs a literal rather than invoking a call; the new call-specific declaration and assignment templates drop that wrapping regardless of the source data type. Itssupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding and call-without-binding output is unchanged. Only theNewVariableform emits a golden because theExistingVariablebare assignment (my_data = make_widget(42)) to an undeclared name is not self-contained. Follow-up to #1961. See #2455.Ziggains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,Java,PythonandCpp). The default (ERROR) strategy keeps the homogeneousZValtagged-union model; underRECORDeach record-shaped dict (non-empty, string-keyed) becomes a generatedconst RecordN = struct { ... };declared in the preamble plus a matchingRecord0{ .field = value, ... }literal whose fields are raw Zig values, so a record-shaped dict that mixes scalars with a container is representable. Field names are the dict keys verbatim and field types are inferred structurally from the value (i64/u64,f64,bool,[]const u8,?i64,[]const Tslices,struct { ... }tuples for heterogeneous lists, nestedRecordN). Without theZValunion the whole value is raw, so a non-record collection is a&.{ ... }slice /.{ ... }tuple and the binding drops its: ZValannotation; the class-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter and itssupports_record_struct_name_prefixlanguage-class flag is nowTrue. See #2477.Fixed the
ZigRECORDfield type of an integer-list record field whose first element is small but a later element exceeds the signed 64-bit range: it is now[]const u64(typed from the widest element) instead of the[]const i64that inferring from the first element alone produced. See #2488.Odingains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,Java,PythonandCpp). Each record-shaped dict (non-empty, string-keyed) becomes a generated package-scopestructdeclared in the preamble plus a matchingRecord0{ field = value, ... }literal, so a record-shaped dict that mixes scalars with a container is representable even thoughmap[string]Vrequires homogeneous values. Field names are the dict keys verbatim, and the class-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter; itssupports_record_struct_name_prefixlanguage-class flag is nowTrue. A list or ordered-map record field keeps Odin’s standard[dynamic]any{...}/map[string]any{...}rendering and is typed as the matchinganycontainer (no element type is inferred), and an integer field beyond the signed 64-bit range is typedu64to match its literal. The default (ERROR)map[string]anyoutput is unchanged. See #2481.StubReturnis now part of the public API. It was already the parameter type of the publicLanguageprotocol’sformat_call_stubandformat_call_preamble_stubmethods, so it is now re-exported from the package root for consumers implementing that protocol. See #1947.Dgains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,Java,PythonandCpp). The default (ERROR) strategy keeps the homogeneousstd.json.JSONValuemodel; underRECORDeach record-shaped dict (non-empty, string-keyed) becomes a generatedstruct RecordN { ... }declared in the preamble plus a matching positionalRecord0(value, ...)constructor literal whose fields are raw D values, so a record-shaped dict that mixes scalars with a container is representable. Field names are the dict keys verbatim and field types are inferred structurally from the value (long/ulong,double,bool,string,typeof(null),T[]arrays, nestedRecordN). Without theJSONValuewrapper the whole value is raw and the binding drops itsJSONValue; the class-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter and itssupports_record_struct_name_prefixlanguage-class flag is nowTrue. A heterogeneous scalar list, a set, an ordered map or a non-record dict has no raw D representation and raisesUnrepresentableInputError(the cross-language decision for these is tracked in #2317). See #2478.Crystalgains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,Java,CppandPython). Each record-shaped dict (non-empty, string-keyed) becomes a generatedrecordstruct declared in the per-fixture module body plus a matching positionalRecord0.new(value, ...)literal, so a record-shaped dict that mixes scalars with a container is representable even thoughHashrequires a homogeneous value type. Field names are the dict keys verbatim, an integer field is sized to match its rendered literal (Int32/Int64/ the_i128-suffixedInt128), and the struct-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter; itssupports_record_struct_name_prefixlanguage-class flag is nowTrue. The default (ERROR) strategy still raises for such a dict. See #2420.Cppgains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,JavaandPython). Each record-shaped dict (non-empty, string-keyed) becomes a generated aggregatestructdeclared in the preamble plus a matchingRecord0{.field = value, ...}C++20 designated-initializer literal, so a record-shaped dict that mixes scalars with a container is representable even thoughstd::maprequires homogeneous values. Field names are the dict keys verbatim, scalar members carry a{}in-class initializer (so the aggregate satisfies clang-tidy’s member-init check), and the class-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter; itssupports_record_struct_name_prefixlanguage-class flag is nowTrue. A list whose every element is a record-shaped dict is opened withstd::vector{so class-template argument deduction infersstd::vector<RecordN>. The default (ERROR)std::variantoutput is unchanged. See #2420.CSharpgains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,Java,PythonandCpp). Each record-shaped dict (non-empty, string-keyed) becomes a generated positionalrecorddeclared in the preamble plus a matchingnew Record0(value, ...)literal, so a record-shaped dict that mixes scalars with a container is representable even thoughDictionaryrequires a homogeneous value type. Component names are the PascalCase form of the dict keys; auto names areRecord0,Record1, …sequence_formatis forced toARRAYunder this strategy so a list-valued component has a typed array form, and a list whose every element is a record-shaped dict is opened with an implicitly-typed arraynew[] { ... }so C# infersRecordN[]. The default (ERROR)Dictionaryoutput is unchanged. See #2475.Swiftgains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,Java,PythonandCpp). Each record-shaped dict (non-empty, string-keyed) becomes a generatedstructdeclared in the preamble plus a matchingRecord0(field: value, ...)initializer literal, so a record-shaped dict that mixes scalars with a container is representable as a typed value even thoughDictionaryrequires a homogeneous value type. Field names are the dict keys verbatim, a field whose value is a list of record-shaped dicts of one shape is typed[RecordN], and the struct-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter; itssupports_record_struct_name_prefixlanguage-class flag is nowTrue. The default (ERROR)Anyoutput is unchanged. See #2474.Cgains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,Java,Python,CppandSwift). Each record-shaped dict (non-empty, string-keyed) becomes a generated aggregatestructdeclared in the preamble plus a matching(struct Record0){.field = value, ...}C99 designated-initializer compound literal, so a record-shaped dict that mixes scalars with a container is rendered with cleanly typed members rather than the taggedCValunion. Field names are the dict keys verbatim and each generatedstructis auto-namedRecord0,Record1, … A scalar member maps to its exact C type, a nested record dict to its generatedstructtype, and a list whose every element is a record-shaped dict to a fixed-sizestructarray member. Every other container (a scalar or heterogeneous list, or an empty list) is typed a pointer toCValand rendered as aCValarray literal, reusing C’s existing tagged union for arbitrary heterogeneity. Because that fixed-sizestructarray is sized from the shape’s first-seen instance, two same-shape records whose shared all-record-list field has differing lengths are rejected withUnrepresentableInputErrorrather than emitting C that fails to compile or misrepresents the input (cf. the set / non-record-dict field boundary tracked in #2317). The default (ERROR)CValoutput is unchanged. See #2476.Vgains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,Java,PythonandCpp). Each record-shaped dict (non-empty, string-keyed) becomes a generated file-scopestructdeclared in the preamble plus a matchingRecord0{ field: value, ... }literal, so a record-shaped dict that mixes scalars with a container is representable even though amaprequires a homogeneous value type. Field names are the dict keys verbatim; a field is typed from its value (an integer by its own magnitude so a wide value keeps itsi64(...)/u64(...)cast, a nested record by its generated name, a list of record-shaped dicts as[]RecordN,Noneasvoidptr, an empty list as[]IVal), and the struct-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter; itssupports_record_struct_name_prefixlanguage-class flag is nowTrue. AnEPOCHdatetime is now routed through the integer formatter so a post-2038 value keeps thei64(...)cast V requires. The default (ERROR) andINTERFACEoutputs are unchanged. See #2480.Nimgains theRECORDheterogeneous_strategy(already onRust,Go,Kotlin,Scala,Java,Python,Cpp,Swift,DandV). Each record-shaped dict (non-empty, string-keyed) becomes a generated module-scopetype Record0 = objectdeclaration plus a matchingRecord0(field: value, ...)literal, so a record-shaped dict that mixes scalars with a container is representable even though a NimTablerequires a homogeneous value type. Field names are the dict keys incamelCaseand a list field is element-typed (seq[int],seq[RecordN]); the class-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter, so itssupports_record_struct_name_prefixlanguage-class flag is nowTrue. Collections render with their native Nim constructors (@[...],{...}.toTable) as underOBJECT_VARIANT, but no scalar is wrapped and nojson/%*is emitted. A null field is the Nimpointertype; a set, ordered-map or non-record-dict field, and aNIM-table-literal date/datetime field, are rejected as out of scope for the base port (consistent with the other non-Rust ports; see #2317). The default (ERROR) JSON output is unchanged. See #2479.ObjectiveCnow acceptsvariable_formonliteralize_call(), emittingid my_data = make_widget(@42);directly. The literal-binding declaration boxes a primitive right-hand side via@(...)because anidis a pointer type, but a call expression already yields an object pointer, so the boxing is dropped for the call-result binding. Itssupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding output is unchanged. See #2223.Erlangnow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable, binding the call result withMy_data = make_widget(...). Awrap_in_file=TrueErlang scaffold hoists the generated call stub to module scope between-exportandx()(previously the literal-binding scaffold nested it inside thex/0clause, producing invalid Erlang) while keeping the binding and the trailingMy_data.return insidex(). See #2454.TclandBashnow acceptvariable_formonliteralize_call()for bothNewVariableandExistingVariable. Their literal-binding templates treat the right-hand side as a value word, so a call result is now bound through command substitution instead: Tcl emitsset my_data [make_widget 42]and Bash emitsdeclare my_data="$(make_widget 42)"(a baremy_data="$(...)"for anExistingVariable). Theirsupports_call_variable_bindinglanguage-class flag is nowTrue; existing literal-binding output is unchanged. Follow-up to #1961. See #2222.
2026.05.16.1¶
The mapping arm of the public
ValueInputtype (accepted byref_valuesandbound_refs) is now a covariant-key read-onlyValueItemsMapprotocol instead of an invariantMapping, so nesteddictliterals with any scalar key type (str,int, mixed, …) are accepted by type checkers without an explicit annotation. This is a type-only relaxation that accepts strictly more inputs; runtime behavior is unchanged.Scalagains the samerecord_shape_namesconstructor parameter, aMapping[frozenset[str], str]from a record shape’s key-set to a customcase classname. A mapped shape is declared and rendered with the given name instead of the auto-generatedRecordN; therecord_struct_name_prefixcounter advances only for the shapes with no custom name. Names are validated as PascalCase Scala identifiers that do not collide with the auto{prefix}{N}pattern or each other, raisingInvalidRecordNameError. Itssupports_record_shape_nameslanguage-class flag is nowTrue. See #2332.Pythongains an opt-inRECORDheterogeneous_strategy(already onRust,Go,Kotlin,ScalaandJava). Each record-shaped dict (non-empty, string-keyed) becomes a generated frozen@dataclasses.dataclassdeclared in the preamble (with animport dataclasses) plus a matchingRecordN(field=value, ...)literal; field names are the dict keys verbatim and the class-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter. Python’sdictis already heterogeneous, so every record-shaped dict is representable as a plaindict; this is purely an idiomatic-output choice, the strategy is opt-in, and the default (ERROR) plain-dictoutput is unchanged. See #2419.Javagains the samerecord_shape_namesconstructor parameter (already onRust,GoandKotlin), aMapping[frozenset[str], str]from a record shape’s key-set to a customrecordname. A mapped shape is declared and rendered with the given name instead of the auto-generatedRecordN; therecord_struct_name_prefixcounter advances only for the shapes with no custom name. Names are validated as PascalCase Java identifiers that do not collide with the auto{prefix}{N}pattern, the wrapper class name (module_name), or each other, raisingInvalidRecordNameError. Itssupports_record_shape_nameslanguage-class flag is nowTrue. See #2333.
2026.05.16¶
Elmnow acceptsvariable_formonliteralize_call(), emitting the inference-style bindingmy_data = make_widget (EInt 42)without aname : Valannotation (the call’s return type is not known to the renderer). Awrap_in_file=TrueElm scaffold places the binding inside themainletblock so the call is still exercised when the module is run. See #2245.Cppnow supports theTUPLEheterogeneous_strategy: a fixed-length heterogeneous scalar array that is a dict value or the document root is rendered asstd::make_tuple(...)typedstd::tuple<T0, ...>(with#include <tuple>emitted by the data-dependent preamble) instead ofstd::vector<std::variant<...>>. C++’sTUPLEstrategy does not composeRECORD, so the preamble fires off the tuple ids alone, even when the data has no record-shaped dicts. The default (ERROR)std::variantoutput is unchanged. See #2329.Scalanow supports theTUPLEheterogeneous_strategy, which composesRECORD: a fixed-length heterogeneous scalar array that is a record field, another dict value, or the document root is rendered as a native tuple(e0, e1, ...)typed(T0, T1, ...)(a tuple-valuedcase classfield is declared with the tuple type) instead of raising or widening toList[Any]. Scala 3 (the only version this language targets) imposes no tuple-length limit – lengths past 22 are transparently backed byTupleXXL– so every fixed-length heterogeneous scalar array is representable. The default (ERROR) output is unchanged. See #2330.TypeScriptnow supports theTUPLEheterogeneous_strategy: a fixed-length heterogeneous scalar array that is a dict value or the document root is rendered as an[e0, e1, ...] as consttuple literal, which TypeScript infers as areadonly [T0, T1, ...]tuple type, instead of a widened(T0 | T1)[]array. TypeScript has noRECORDstrategy andas constneeds no imports, so there is no data-dependent preamble. The default (ERROR) union-array output is unchanged. See #2328.Kotlinnow supports theTUPLEheterogeneous_strategy, composingRECORD: a fixed-length heterogeneous scalar array that is a dict value or the document root is rendered as a two-elementPair(...)or three-elementTriple(...)typedPair<...>/Triple<...>, and a record field whose value is such an array becomes a tuple-typed field. Kotlin has no general N-tuple, so an array of any other length raisesTupleArityNotRepresentableErrorrather than degrading to a homogeneous list. The default (ERROR) output is unchanged. See #2331.literalize_call()gains acomment_sourceargument: a sequence of trailing source-code comments, one per generated call, paired positionally. Each non-empty entry is emitted as a line comment after the statement terminator using the target language’s comment syntax (#,//,--, …), falling back to that language’s block-comment form (/* ... */) where there is no line comment. This places the comment where only the core can put it – acall_transformonly sees the pre-terminator call expression, so a transform that appended a line comment would have the terminator commented out. An empty entry emits no comment. A length mismatch raisesCommentSourceLengthMismatchErrorand a multi-line entry raisesCommentSourceMultilineError. Languages that assemble the call sequence into a single clause/list/expression (so a separator, terminator or closer would follow the comment on the same line and be swallowed) reject a non-emptycomment_sourcewithUnsupportedCallShapeError; the supported set is the languages whosesupports_standalone_comments_in_wrapped_callsisTrue. See #2369.FSharpnow acceptsvariable_formonliteralize_call()for bothNewVariableandExistingVariable, emitting the inference-style bindinglet my_data = make_widget(42)without thename: Valtype annotation or tagged-enum constructor wrapper that literal bindings use (the call’s return type is not known to the renderer). Existing literal-binding output for F# is unchanged. See #2249.PureScriptnow acceptsvariable_formonliteralize_call(), emitting the inference-style bindingmy_data = make_widget (PInt 42)without aname :: Typeannotation (the call’s return type is not known to the renderer).wrap_in_file=TruePureScript scaffolds addimport Preludeso the call stub’sUnitresult type resolves; literal-binding output is unchanged. See #2247.Rustunder theRECORDheterogeneous_strategynow raisesUnrepresentableInputErrorfor a set-valued record field, and for a record field whose value is a dict that is not record-eligible (empty, non-string-keyed, or an ordered map), instead of emitting a struct whose declared field type disagrees with the renderedHashSet/BTreeSet/HashMapliteral and fails to compile.Rocnow acceptsvariable_formonliteralize_call(), emitting the inference-style bindingmy_data = make_widget (RInt 42i128)without amy_data : Valannotation (the call’s return type is not known to the renderer, and Roc infers it). TheValtag-union alias is omitted from such scaffolds because nothing annotates with: Val; existing literal-binding output is unchanged. See #2250.Smlnow acceptsvariable_formonliteralize_call(), emitting the inference-style bindingval my_data = make_widget(42)without the: val_tannotation ordatatypeconstructor wrapping used for literal bindings (the call’s return type is not known to the renderer, so SML infers it). See #2248.OCamlnow acceptsvariable_formonliteralize_call(), for bothNewVariableandExistingVariable, emitting the inference-style bindinglet my_data = make_widget(42)without the: val_tannotation or tag constructor used for literal bindings (the call’s return type is not known to the renderer, so OCaml infers it). The call-binding bypass now also covers the assignment template via the newformat_call_variable_assignmenthook, so OCaml’s non-barelet x : val_t = ...assignment no longer leaks a tag constructor onto a call result. Existing literal-binding output for every language is unchanged. See #2246.JavaandScalano longer emit output that fails to compile for a post-2038datetimeunder theRECORDheterogeneous_strategywithdatetime_format=EPOCH. The epoch seconds now carry the language’s wide-integer suffix and the record component widens accordingly (long/Long) once the value leaves signed 32-bit range, so the declared component type always matches the rendered literal. In-range epochs are unaffected. See #2338.Gono longer emits output that fails to compile for a record field holding a positive integer beyond the signed 64-bit range under theRECORDheterogeneous_strategy. Such a value renders through theuint64(...)overflow fallback, and the generated struct field is now typeduint64to match it instead ofint/int64. Record integer fields formatted with a non-defaultinteger_format,numeric_separatorornumeric_literal_suffixkeep their value-derived field type. In-range integers are unaffected. See #2306.Kotlin,JavaandScalano longer emit output that fails to compile for a record field holding an integer beyond the signed 64-bit range under theRECORDheterogeneous_strategy. The generateddata class/record/case classfield is now typedBigInteger/BigInteger/BigIntto match the arbitrary-precision overflow-fallback literal instead ofLong/long. In-range integers are unaffected. See #2376.Internal: the
RECORDheterogeneous_strategyno longer threads the already-formatted field literal into the field-type hook.Kotlinnow derives each generateddata classfield’s declared type structurally from the raw value through its own collection openers and scalar mapping (matching the Go/Java/Scala ports), and theformattedstring is dropped from the renderer contract. No generated output changes. See #2305.
2026.05.15.2¶
Rustgains theTUPLEheterogeneous_strategy. A fixed-length heterogeneous scalar array that is a dict value, a record field value, or the document root (every element scalar, spanning at least two scalar buckets) is rendered as a native tuple(e0, e1, ...)typed(T0, T1, ...)instead of raising. It composes withRECORD: a record field whose value is such an array becomes a tuple-typed struct field. Heterogeneous arrays nested inside another list, or containing a non-scalar element, stay out of scope and still raise. This lands the shared, language-agnostic machinery with Rust as the reference implementation; one language port follows per PR. See #2327.literalize_call()’szip_valuesparameter is replaced by azip_source/zip_input_formatpair, mirroring the primarysource/input_format.zip_sourceis parsed internally with the same parser assource(so YAML!!omap, datetime/bytes coercion, JSON5, TOML, … behave identically by construction); its parsed top-level elements pair positionally with the generated calls (element-by-element whenper_elementisTrue, otherwise the whole parsed value pairs with the single call) and are surfaced onzippedexactly as before. Callers no longer need to parse a companion file themselves or reach into private parsing internals. Supplyingzip_sourcewithoutzip_input_formatraises the newZipSourceWithoutInputFormatError; the existingZipValuesWithoutCallTransformErrorandZipValuesLengthMismatchErrorcontracts are unchanged, and aper_element=Truezip_sourcethat does not parse to a list raisesPerElementNotListError. See #2340.Gogains therecord_shape_namesconstructor parameter (already onRust), aMapping[frozenset[str], str]from a record shape’s key-set to a custom struct name. A mapped shape is declared and rendered with the given name instead of the auto-generatedRecordN; therecord_struct_name_prefixcounter advances only for the shapes with no custom name. Names are validated as PascalCase Go identifiers that do not collide with the auto{prefix}{N}pattern or each other, raisingInvalidRecordNameError. A newsupports_record_shape_nameslanguage-class flag mirrors the constructor. See #2324.Kotlingains the samerecord_shape_namesconstructor parameter, aMapping[frozenset[str], str]from a record shape’s key-set to a customdata classname. A mapped shape is declared and rendered with the given name instead of the auto-generatedRecordN; therecord_struct_name_prefixcounter advances only for the shapes with no custom name. Names are validated as PascalCase Kotlin identifiers that do not collide with the auto{prefix}{N}pattern or each other, raisingInvalidRecordNameError. Itssupports_record_shape_nameslanguage-class flag is nowTrue. See #2324.Kotlinnow declares adata classfield whose value is a custom-named nested record with that nested record’srecord_shape_namesname (e.g.Task). Previously such a field fell through toDoublebecause the custom name does not match the auto-generated{prefix}{N}head, so the generated Kotlin did not compile. See #2348.
2026.05.15.1¶
Javagains theRECORDheterogeneous_strategy(already onRustandGo). Each record-shaped dict (non-empty, string-keyed) becomes a generated top-levelrecord RecordN(type field, ...) {}declaration plus a matching positionalnew RecordN(value, ...)literal, so a dict whose values mix scalars and containers is representable instead of raising. Component names keep the original keys and the record-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter. Generatedrecorddeclarations require Java 16, so aRECORDspec pinslanguage_versiontoJDK_16. See #2300.literalize_call()’scall_transformnow receives aCallContextinstead of the bare call string. The context exposescall(the rendered call expression, formerly the sole argument), the zero-basedindex, the inputrow, andzipped. A newzip_valuesparameter pairs a second, equal-length sequence positionally with the generated calls; each entry is rendered as a language-native literal and surfaced onzipped, so a transform can print an expected value beside each call’s actual return value.zip_valuesrequires acall_transformand must match the call count, raising the newZipValuesWithoutCallTransformError/ZipValuesLengthMismatchError.call_transformis now supported only for call styles whose form is an expression that can be wrapped (positional, keyword, object); the sentinel-probe wrapper synthesis for prefix/postfix/command styles has been removed, and those styles now rejectcall_transformwithUnsupportedCallShapeError.literalize_call()no longer raisesDottedCallStubNotSupportedErrororFreeFunctionCallNotSupportedError(a context-awarecall_transformis opaque, so the core cannot inspect the wrapper); those exceptions are removed. Thesupports_dotted_call_stub/has_free_function_callslanguage attributes are retained as descriptive metadata for callers that generate wrapper stubs. See #2293.Kotlingains theRECORDheterogeneous_strategy(already onRustandGo). Each record-shaped dict (non-empty, string-keyed) becomes a generateddata class RecordN(val ...)declared in the preamble plus a matchingRecordN(field = value, ...)literal, so a dict whose values mix scalars and containers is representable instead of raising. Field names keep the original dict keys and the data-class-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter. See #2298.LanguageClsnow exposes asupports_record_struct_name_prefixflag alongside the existingsupports_*family. Runtime-dispatched callers that look up a language by name can use it to decide whether to pass therecord_struct_name_prefixconstructor keyword argument without inspecting dataclass fields or the__init__signature. It isTrueonGo,Kotlin, andRust, andFalseon every other language.Javanow offersVersionFormats.JDK_16alongsideVersionFormats.JDK_11(still the default), selected vialanguage_version. Generated code is currently identical for both targets; the member exists so a future JavaRECORDheterogeneous_strategy(whoserecorddeclarations require Java 16) can gate on it. The golden harness emits a parallel@jdk_16fixture set. See #2313.
2026.05.15¶
literalize()now accepts an opt-inbound_refsmapping. Unlikeref_values(which only informs a ref’s type and leaves it as a free external identifier), each name inbound_refsadditionally has a binding emitted for it before its first use, so a singleliteralize(..., bound_refs=..., wrap_in_file=True)call produces a complete, valid file with per-language declaration sequencing (Nix nestedlet, the Fortran rule that specification statements precede executable statements, and so on). Binding emission only happens withwrap_in_file=Trueand aNewVariableorExistingVariablevariable_form; otherwisebound_refsdegrades to type information only, exactly likeref_values. The default (nobound_refs) is unchanged: a$refstays a free external identifier. The ref golden-file harness now drives every case through oneliteralizecall, retiring its regex-based stub-stitching helpers. See #2294.Gogains theRECORDheterogeneous_strategy(already onRust). Each record-shaped dict (non-empty, string-keyed) becomes a generatedtype RecordN struct { ... }declared in the preamble plus a matchingRecordN{Field: value, ...}literal, so a dict whose values mix scalars and containers is representable instead of raising. Field names are exported (PascalCase) and the struct-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter. See #2297.Scalagains theRECORDheterogeneous_strategy(already onRustandGo). Each record-shaped dict (non-empty, string-keyed) becomes a generatedcase class RecordN(field: Type, ...)declared in the enclosingobjectplus a matchingRecordN(field = value, ...)literal, so a dict whose values mix scalars and containers is representable instead of raising. Field names are the dict keys verbatim and thecase class-name prefix is configurable via the newrecord_struct_name_prefixconstructor parameter. See #2299.Rustaccepts arecord_shape_namesconstructor parameter — a mapping from each record’s key-set (frozenset[str]) to a user-chosenstructname — so theRECORDheterogeneous strategy can emitstruct Task { ... }instead of the auto-generatedRecord0,Record1, … names. Shape names that are not PascalCase Rust identifiers, that collide withheterogeneous_value_enum_name, that duplicate another mapped name, or that match a Rust reserved keyword raise the newInvalidRecordNameError. The existingrecord_struct_name_prefixis validated the same way. See #2236.Fortrannow offersVersionFormats.V2003alongsideVersionFormats.V2008(the default). The 2003 target defines theint64kind viaselected_int_kind(18)instead of importing it from the intrinsiciso_fortran_envmodule (whoseint64constant is a Fortran 2008 addition), so generated code is otherwise identical and the_int64literal suffix is unchanged. The golden harness emits a parallel@v2003/@v2008fixture set and CI lints each with the matchinggfortran -std=f2003/-std=f2008flag. See #1931.The integration golden-file harness now accepts
input.tomlnext to the existinginput.yamlfor cases whose input contains a value YAML 1.2 cannot natively express (currentlydatetime.time). Newtime_list,time_dict, andtimes_heterogeneous_with_datesgolden cases give every supported language end-to-end coverage ofdatetime.timerendering, which surfaced and fixed several preexisting bugs in time emission: tagged-union languages (Elm,OCaml,Roc,Gleam,Haskell,Zig,Sml,Ada,Fortran,ObjectiveC) now wrap a fallback ISO 8601 time inside the value-type constructor the same way they wrap other scalars; collection type inference (C++, C#, Java, Kotlin, Scala, Dart, Go, Visual Basic, and others) now knows aboutdatetime.timeand narrows homogeneous collections to the correct element type; andFSharpfully qualifiesSystem.TimeOnlyso the rendered output no longer emits anopen Systemline beforemodule. See #2230.Haskellnow acceptsvariable_formonliteralize_call(), emitting the inference-style bindingmy_data = make_widget (42)without aname :: Typeannotation (the call’s return type is not known to the renderer).wrap_in_file=TrueHaskell scaffolds emit a{-# OPTIONS_GHC -Wno-missing-signatures #-}pragma so the inferred binding compiles under-Wall -Werror. See #2244.datetime.timeis now a first-classScalarvalue. Languages with a native time-only type emit native literals (Pythondatetime.time(...), TOML unquotedHH:MM:SS, .NETnew TimeOnly(...)/TimeOnly(...)/New TimeOnly(...)forCSharp,FSharp,VisualBasic, andLocalTime.of(...)forJava,Kotlin,Scala,Groovy); other languages fall back to the existing ISO 8601 quoted-string form. TOMLtimeinputs now round-trip throughTomlas native time literals instead of being re-emitted as quoted ISO 8601 strings. Typed collection openers in languages such asCSharp,Java,Kotlin, andScalanow narrow uniform time-only sequences to the language’s time type (e.g.TimeOnly[]/Array<LocalTime>) instead of falling back to a genericObject/Anyopener.Golden files for languages whose compiler version is pinned (Elixir, Erlang, Gleam, Kotlin, Odin, Zig) now carry the version in the filename:
{stem}@{version}{extension}(e.g.Odin@dev-2026-04.odin). Every fixture is explicitly tied to the compiler version it was generated against. Eachlint.ymljob sets a job-scoped environment variable once and feeds it to both the install action andpython -m tests.integration.list_fixtures; the test code auto-discovers the same version from sibling filenames so no separate registry file is needed.Fortran’slanguage_versiondefault is nowFortran.VersionFormats.V2008(wasV2003) so it matches the Fortran 2008 features the generator actually emits (e.g.int64fromiso_fortran_env).V2003has been removed fromFortran.VersionFormats.lint-fastCI job now syntax-checks and runs the Python fixtures undertests/integration/cases, matching the per-language gate already in place for Bash, Ruby, JavaScript, and other fixture languages. See #1921.lint-odinCI job now usesodin run .again to catch runtime errors thatodin build .cannot detect (e.g. nil-proc calls). Thelaytan/setup-odinaction is pinned todev-2026-04, the last Odin release whereodin run .did not segfault on these fixtures;release: latest(dev-2026-05) crashes at runtime, and the compiler itself segfaults on some fixtures underodin build .. See #1745.
2026.05.14.1¶
2026.05.14¶
YAML inputs with non-string dict keys (integers, dates, booleans) now flow through to the target language’s value-formatting path instead of being silently converted to string form. Languages that can represent the key natively (Python, Ruby, Clojure, Lua, Bash, and others) produce the corresponding literal; languages whose dict syntax requires string keys or a homogeneous typed map raise
UnrepresentableInputError. The affected opt-out targets are the JSON family (alreadyJson5,Jsonnet,Toml), the string-keyed attribute-set languages (Nix,Dhall,Cobol), the statically-typed-map languages whose typed-map syntax has not yet been generalized (Go,Kotlin,CSharp,Haskell,Scala,Dart,VisualBasic,FSharp,Zig,Odin,Nim,D,TypeScript,JavaScript), the languages that reject specific non-string key types at the language level (PhprejectsDateTimekeys,Vrejectsboolkeys), and the languages whose value ADTs do not currently model non-string keys (OCaml,Sml,R).literalize_call()now accepts avariable_formargument (NewVariableorExistingVariable) that wraps the rendered call in an idiomatic per-language variable binding (e.g.let my_data = make_widget(42);,const my_data = make_widget({ count: 42 });,my_data = make_widget(count=42)). Mutability and inference style are controlled by the per-languagedeclaration_styleandModifiersenums on the suppliedLanguageinstance.BothVariableFormsis rejected – emitting both a declaration and an assignment would invoke the target function twice – as isper_element=True(no per-element name vector) and any language whose call form is a statement rather than an expression (call_returns_expression=False). All three are surfaced asUnsupportedCallShapeError. The same exception is raised for languages whose declaration template wraps or transforms the right-hand side in a way that is only valid for literal values – Tcl (needs[...]command substitution), Bash (needs$(...)command substitution), Objective-C (@(...)boxing of primitives), tagged-enum heterogeneous-strategy languages (Roc, Haskell, Elm, SML, OCaml, PureScript, F#), C / SystemVerilog / Fortran / Ada / Zig / D (struct-initializer or constructor wrapping derived from the value’s literal type), Elixir (call stubs need module scope, not the variable’s function body), Erlang, Forth, and Nim. Closes #1961.The internal
ValueandValueInputaliases now permit anyScalaras a dict key, in preparation for surface formats that admit non-string dict keys. A newUnrepresentableInputErrorand asupports_non_string_dict_keysclass attribute onLanguage(defaulting toTrue; overridden toFalseonJson5,Jsonnet, andToml) wire a centralized guard at the dict-formatting boundary. The surface parsers still produce only string-keyed dicts, so behavior is unchanged.literalize()now accepts aref_valuesmapping from ref identifier to the value declared elsewhere for that ref. Languages whose$refrendering depends on the referenced type (V’s.clone()for arrays and maps, Mojo’s^for non-trivial values, C++’sstd::movefornon-trivially-copyablevalues) consult it to choose the right form; when omitted these languages keep their type-agnostic default.Vnow emits a bare identifier for scalar refs (int,bool,f64) becauseint.clone()is rejected by the V compiler;Mojodrops^for register-trivial scalars where it is a hard error under--Werror;Cppdropsstd::movefortrivially-copyablescalars where clang-tidy’shicpp-move-const-argrejects it. Preamble inference also walks the resolvedref_valuesso sum-type body declarations (Haskell’sdata Val, OCaml’sval_t, Roc’sVal, …) include the variants needed by the referenced types.SystemVerilog’sliteralize()rejects scalar$refmarkers viaCallArgNotSupportedError: SV keys the variable declaration off the marker dict’s shape (_VKV name[]) and cannot produce a coherent declaration for a scalar referent.The
format_call_ref_identifier,format_call_arg_ref_identifier, andformat_call_arg_ref_identifier_consumablehooks now receive a second positional argument: theValuebehind the ref (orNonewhen the caller did not supplyref_values). This is a breaking change for customLanguageimplementations that override these hooks; most overrides ignore the new argument.Vnow defaultsheterogeneous_strategytoV.heterogeneous_strategies.ERRORand reportsdict_supports_heterogeneous_values=Falseandsupports_heterogeneity=Falseon its sequence and set formats. V is statically typed and rejects unwrapped heterogeneous collections, so rendering them now raises rather than emitting code the V compiler will not accept. Callers that want to materialize heterogeneous data must opt in toV.heterogeneous_strategies.INTERFACE, which wraps values withIVal(...)and emits theinterface IVal {}declaration as before.
2026.05.13.1¶
LanguageClsnow exposessupports_empty_dict_key,supports_call_style,supports_default_dict_key_type,supports_default_dict_value_type,supports_default_sequence_element_type,supports_default_set_element_type, andsupports_default_ordered_map_value_typeflags alongside the existingsupports_module_name. Runtime-dispatched callers that look up a language by name can use these to decide whether to pass the matching constructor keyword argument without inspecting dataclass fields or the__init__signature.
2026.05.13¶
HaskellCURRIEDCallStylenow emits a thunk binding (process :: IO Val/process = ...) for zero-parameter calls instead of a malformed signature with an empty argument type (process :: -> IO Val).literalize()now raisesWrapInFileWithoutVariableNotSupportedErrorwhenwrap_in_file=Trueis combined withvariable_form=Nonefor a target language that cannot represent a bare value at file-statement scope. EachLanguagedeclares its ability to render this shape via a newsupports_no_variable_wrap_in_fileflag. Strict-typed compiled languages (Rust, C, C++, Haskell, OCaml, Swift, Ada, D, Dart, C#, Elm, Mojo, Nim, Objective-C, Odin, SML, V, Zig, Go, Java, Kotlin, F#, Scala, Erlang, Gleam, Roc, PureScript, Tcl, Bash, VB, SystemVerilog, Occam) opt out, along with Cobol, Fortran, Php, Lua, Toml, and Dhall — each of which produced a file whose linter rejected the resulting bare-value rendering even though the issue text initially listed them as opt-in. The renderer no longer silently emits invalid output for any of these.Haskell,FSharp,OCaml, andSmlnow expose aCURRIEDCallStylealongside the existingPOSITIONALmember. Selecting it emits curried application calls (process arg1 arg2) with curried stubs in place of the tuple form.Haskelldefaults toCURRIEDsince curried application is the idiomatic call form in Haskell; F#, OCaml, and SML keepPOSITIONALas the default.Elmliteralize_call()now emits curried-application calls (process (EInt 1) (EInt 2)) with curried type stubs (process : a -> b -> ()) in place of the prior tuple-form (process (EInt 1, EInt 2)). Elm tuple literals cap at three elements, so the tuple form had no representation for calls with four or more parameters; the curried form composes naturally with|>and matches the convention used byelm-formatand the standard library.The
Language.max_call_parametersattribute has been removed. No remaining language sets a maximum parameter count, so the upstreamUnsupportedCallShapeErrorcheck inliteralize_call()and the correspondinglanguage_cls.max_call_parametersintrospection no longer have a load-bearing caller.The
supports_commented_dict_call_argsflag has been removed fromLanguage. Every (language, shape) pair previously dropped by the test-discovery filter on this flag is either already short-circuited by an earlier exception path or now renders cleanly, leaving the flag with no load-bearing callers.literalize_call()now raisesUnsupportedCallShapeErrorwhen the innermost segment oftarget_functioncollides with one of the target language’s reserved identifiers. The renderer previously produced output that would not parse in the target language.literalize_call()now raisesUnsupportedCallShapeErrorwhen acall_transformwrapper is supplied for a language whose calls are statements rather than expressions (i.e.call_returns_expressionisFalse). The wrapper cannot consume the call as a value in that case, and the renderer previously emitted invalid output.literalize_call()no longer rejects identitycall_transformvalues on Ada, Fortran, and SystemVerilog. Bare procedure-call statements (Process(x);,call process(x),process(x);) are valid in all three languages, so the prior rejection encoded a constraint that does not exist. TheLanguage.allows_bare_call_statementflag introduced alongside that check has been removed.The PureScript, Roc, and Elm wrapped-call indent helpers no longer carry defensive branches for blank or whitespace-leading lines. These helpers only ever receive single-line call expressions from
literalize_call(which usesCollectionLayout.COMPACTfor wrapped calls and rejects standalone comments in that path), so the empty-line and continuation arms were unreachable via the golden integration cases. Four unit tests intests/test_languages.pythat drove the Elm helpers directly with constructed multi-line input have been removed in favor of the existing golden-file contract.Mojoliteralize_call()now supports refs nested inside dict literals and commented dict-literal call arguments. The typed-stub work landed in #1972 made both shapes compile cleanly undermojo run --Werror, so the correspondingsupports_call_refs_in_dict_literalsandsupports_commented_dict_call_argsflags flip toTruefor Mojo and two newcall_*golden cases are exercised.MojoandC++literalize_call()no longer wrap a consumable$refin the language’s consume form when the underlying value’s type would make the wrapping a hard error or aclang-tidylint failure. In Mojo,^is dropped for register-trivial scalars (Int,Bool,Float64) because Mojo 0.26.1.0+ rejects “transfer from a value of trivial register type” under--Werror. In C++,std::moveis dropped for register-trivial types (bool, integer,double,std::chrono::year_month_day,std::chrono::system_clock::time_point) whichperformance-move-const-arg/hicpp-move-const-argreject. Non-trivial refs (e.g.List[...],Dict[...], strings, bytes) keep their consume form.Languageexposes a newconsumable_ref_value_inhibits_consuming_formpredicate that languages override to opt into per-value routing; the default (never_inhibits_consuming_form) preserves the existing behavior.Renamed
VariableTypeHints.AUTOtoVariableTypeHints.NEVERfor every language. The behavior is unchanged; the new name describes the option (no annotation, defer to the language’s inference) rather than implying intent, and pairs symmetrically withALWAYS.Every language’s
VariableTypeHintsenum now exposes a third value,SAFE, alongsideNEVERandALWAYS.SAFEannotates only when the language’s own inference would widen the variable to a permissive type (e.g.unknown[]for an empty TypeScript array,Object[]for an empty Java array), making downstream consumption safer thanNEVERwithout the noise ofALWAYS. The predicate is per-language:TypeScriptandJavaannotate empty list / set / dict literals; for every other languageSAFEcurrently produces the same output asNEVERwhile leaving room for a future per-language predicate.Nimliteralize_call()now emits the object-varianttypedeclaration when theOBJECT_VARIANTheterogeneous strategy is active, so the rendered call references a defined wrapping type.Mojotyped call stubs now coverbool,float,bytes,date, anddatetimeargument values (mapped toBool,Float64, andStringrespectively), and apply to dotted-method stubs as well as free-function stubs. The generic[*Ts: AnyType](*args: *Ts)form is still emitted when scalar types disagree across calls or any argument is non-scalar.Mojoformat_call_preamble_stub()now raisesHeterogeneousScalarCollectionErrorunder the defaultERRORheterogeneous_strategywhen concrete Mojo argument types diverge across calls at one parameter slot (including divergent shapes such as scalar in one call and list in another).VARIANTcallers continue to fall back to the generic[*Ts: AnyType](*args: *Ts)form for cross-call divergence pending follow-up wrap machinery.literalize_call()now raises a typedUnsupportedCallShapeErrorwhenwrap_in_file=Trueand the YAML source carries standalone comments but the target language setssupports_standalone_comments_in_wrapped_calls = False(currentlyElm,Erlang,Haskell,Jsonnet,PureScript, andRoc).LiteralizeResultnow exposescontains_standalone_commentsso callers that wrap manually viawrap_calls_with_declarations()can apply the same guard.literalize()now raises a typedVariableNameNotSupportedErrorwhenvariable_formis supplied but the target language setssupports_variable_names = False(currentlyJson5,Jsonnet, andYaml). The capability flag is now enforced rather than declarative.supports_variable_namesis nowTrueonClojure,CommonLisp,Julia,Racket,Ruby, andScheme, reflecting that these languages do support named variable wrapping vialiteralize’svariable_formargument (and have golden files exercising that behavior).Separated syntactic
ref_casevalidity from stylistic preference.Languagenow exposessupported_ref_cases– a frozenset of cases that produce a syntactically legal identifier – alongside the existingidentifier_cases, which now documents stylistic preference only.literalizeandliteralize_callvalidateref_caseagainstsupported_ref_cases, so cases that are syntactically legal but non-idiomatic (e.g.IdentifierCase.CAMELin Python) are now accepted. Two shared constants,NON_KEBAB_REF_CASESandALL_REF_CASES, cover the common settings.literalize_call()now raises a typedDottedCallTargetNotSupportedErrorwhentarget_functioncontains a dot but the target language setssupports_dotted_calls = False(currently onlyHcl). The capability flag is now enforced rather than declarative.literalize_call()now raises a typedDottedCallStubNotSupportedErrorwhencall_transformproduces a dotted wrapper name (e.g.tracer.emit) but the target language setssupports_dotted_call_stub = False. The capability flag is now enforced rather than declarative.literalize_call()now raises a typedFreeFunctionCallNotSupportedErrorwhencall_transformproduces a bare wrapper name with no dot (e.g.emit) but the target language setshas_free_function_calls = False(currently onlyWren). The capability flag is now enforced rather than declarative.Removed the redundant
supports_default_set_element_type,supports_default_sequence_element_type,supports_default_dict_value_type,supports_default_dict_key_type, andsupports_default_ordered_map_value_typeclass attributes fromLanguageClsand all language implementations. Direct constructor calls already surface unsupporteddefault_*_typekeyword arguments through type checking, making these probe flags unnecessary.literalize_call()accepts a newref_valuesmapping from{"$ref": "name"}identifiers to the source values declared elsewhere. Supplied ref values now participate in data-driven preamble inference, so generated body declarations such as Haskell’sdata Val = ...include types reachable only through refs while the rendered call still emits the bare identifier.
2026.05.01.1¶
2026.05.01¶
2026.04.30.3¶
2026.04.30.2¶
2026.04.30.1¶
literalize_call()accepts a newconsumable_refsparameter listing the ref identifiers the call may move from. In C++, only refs in this set – and only when they appear in exactly one call argument across the rendered calls – are wrapped instd::move(...); all other refs emit as the bare identifier so the variable remains valid for any subsequent use (whether in a later per-element call within the sameliteralize_callblock, or elsewhere in the surrounding source). Mojo’s^transfer operator is treated the same way. This is a breaking change: previously C++ unconditionally wrapped every call-argument ref instd::move(...), which produced use-after-move when the same variable was referenced by more than one per-element call. Passconsumable_refs={"my_var"}to restore the previous behavior formy_var.literalize()andliteralize_call()now accept aref_keyparameter (str, default"$ref"). The marker key used to identify variable-reference mappings in the input data is now user-configurable: a single-key dict whose key equals ref_key and whose value is a string is treated as a ref marker. Passref_caseonly when the identifier name should be converted before rendering.Every built-in language class now exposes a
VersionFormatsenum and a configurablelanguage_versionconstructor parameter that selects which version of the target language the generated code is written for. For example,Ada().language_versiondefaults toAda.version_formats.ADA_2022, whose.valueis"Ada 2022". Each language currently defines a single version; additional versions may be added in future releases. Bothversion_formatsandlanguage_versionare part of theLanguageprotocol, so custom language implementations must also define them.
2026.04.30¶
literalize_call()now expands{"$ref": "name"}markers that appear nested inside list elements or dict values of an argument, in addition to the existing support for top-level argument refs.ref_caseconversion and preamble stripping apply recursively to nested refs just as they do to top-level ones.
2026.04.29¶
literalize()now accepts aref_caseparameter (IdentifierCase). When set, any{"$ref": "name"}mapping in the input data – at the top level or nested inside dicts and lists – is rendered as a bare identifier instead of a literal dict. The identifier is case-converted to match the language’s conventions (e.g.my_varbecomesmyVarfor JavaScript). Languages apply their own sigil prefix viaformat_call_ref_identifier(e.g.$my_varfor PHP). Withoutref_case,$refmappings are treated as ordinary literal dicts, preserving backwards compatibility. Passing a case not inlanguage.identifier_casesraisesUnsupportedIdentifierCaseError.Added support for Roc as a new output language.
Rocemits aValtag-union type alias (RNull,RBool,RInt,RFloat,RStr,RList,RDict,RSet) inside the module body, exposing the generated value viamodule [my_data]. Calls use the space-separated command syntax with each argument wrapped in parentheses (process (RInt 1) (RInt 2)), with module-level stubs of the formname : a, b -> {}. A newlint-rocjob in.github/workflows/lint.ymlrunsroc checkagainst every.rocfixture using the upstream nightly tarball.C, C++, Objective-C, and D fixtures now emit a
mainentry point directly (int main(void)/int main()/void main()) instead of acheck_()/_check()function that required a separate per-language driver script. Haskell non-call fixtures now appendmain = seq my_data (return ())to the module, and SML fixtures are emitted as top-level declarations ending withval _ = my_datainstead of inside astructure Check = struct … endwrapper. All six driver scripts (c_main.c,cpp_main.cpp,objc_main.m,d_main.d,sml_force.sml,sml_main.mlb,sml_call_main.mlb) have been removed. CI now compiles and runs each fixture directly without a linking step against a driver object.run_haskell.pyhas been removed; the CI now copies each Haskell fixture to a temporary workspace (renaming to match its unique module name), generates aMain.hsdriver that imports every fixture qualified and calls itsmain, and compiles and runs them all in a singleghcinvocation.Ada output now uses Ada 2022 container aggregates (
AList'[...],AMap'[...],ASet'[...]) and emits awith A_Stub; use A_Stub;context clause so each fixture compiles and runs against a checked-in stub package. The lint workflow gained a “Run Ada files” step that builds and executes every fixture, replacing the previous syntax-only check. The combined declaration + assignment wrapper now keeps both forms in a single procedure scope so the assignment can reachmy_data.Jsonnetnow emits$refdeclarations as top-levellocalbindings before the call expressions, so call-mode output withref_declarationsis supported. Previously the integration harness skippedJsonnetfor ref-declaration cases because the array-wrapped output had no place for variable bindings. TheDeclarationStyles.ASSIGNtemplate changed from{value}tolocal {name} = {value};, andJsonnetnow overrideswrap_calls_with_declarationsto emit those bindings beforewrap_in_filewraps the calls in[ … ].C single-name call stubs (e.g.
emit,process) are now emitted asstaticdefinitions with a stub body instead of bare forward declarations, so generated fixtures can be linked and run. The lint workflow now compiles each C fixture against a smallc_main.cdriver and executes the resulting binary, surfacing runtime errors that the previous-fsyntax-onlycheck missed.Crystal.wrap_in_filenow wraps content in amodule Check ... endblock withextend self, matching what Erlang, Scala, and Haskell already do.Crystalgains amodule_nameconstructor argument (default"Check") to control the wrapper name. Callers that relied onliteralize(language=Crystal(), wrap_in_file=True)returning bare content will now receive amoduleblock.Java sets and dicts no longer emit a trailing comma when
trailing_comma=TrailingCommas.YESis requested.Set.of(...)andMap.ofEntries(...)are method calls and the previous output was rejected byjavac.SetFormatConfigandDictFormatConfiggain asupports_trailing_commafield (defaults toTrue) mirroringSequenceFormatConfig; formats built around method-call syntax can opt out.Added
CallStyleEnumas the base class for per-languageCallStylesenums. Itsconfigaccessor returns the enum member’s value typed as theCallStyleunion, removing thecast("CallStyle", self.call_style.value)boilerplate previously duplicated in every multi-style language module.module_namehas moved from a parameter onliteralizeandliteralize_callto a constructor argument on the ten languages whosewrap_in_fileintroduces a named scope:C,Cpp,D,Erlang,Fortran,FSharp,Java,ObjectiveC,OccamandSystemVerilog. Pass it when constructing the language (e.g.Java(module_name="Foo")); it defaults to"Module". Languages whose wrappers do not introduce a named scope no longer acceptmodule_nameat all, so passing it where it has no effect is now aTypeErrorinstead of being silently ignored.Language.wrap_in_fileandLanguage.wrap_combined_in_filelose themodule_nameparameter; the named-scope languages readself.module_nameinstead. Languages must now be instantiated before being passed toliteralize(language=Python()rather thanlanguage=Python).OCaml integer values outside the signed 64-bit range now raise
UnrepresentableIntegerErrorinstead of emitting anint_of_stringfallback that overflowed OCaml’s 63-bit nativeintat runtime and silently misrepresented the data.literalize_callnow supports Visual Basic. The default style is positional (foo(1, 2));VisualBasic.CallStyles.NAMEDenables VB’s named-argument syntax (foo(x:=1, y:=2)). Generated stubs are emitted as module-levelClassandFunctionblocks and the call body is placed insideSub _calls()because VB does not allow bare expression statements at module scope.Added
literalize_callsupport forZig.Zig.CallStylesnow exposes aPOSITIONALmember backed byPositionalCallStyle, andformat_call_preamble_stubemits file-scope Zig stub declarations because Zig disallows nested function definitions insidemain. Dotted targets likeapp.client.fetchare realized as a nestedstructchain rooted at a module-level constant, and call arguments are wrapped in theZValunion so anonymous union literals coerce to a concrete type at the call site.literalize_callnow emits R stub declarations (name <- function(...) NULL) for the called function and any call-transform wrappers, so generated R call output runs cleanly underRscriptwithoutcould not find functionerrors.Removed
R.TrailingCommas.YES: R’slist()rejects empty arguments, solist(1, 2,)parses but raises at runtime. OnlyR.TrailingCommas.NOremains.Fixed Dhall typed-empty literals for doubly-nested lists. Input like
[[[1, 2]], [], [[3, 4]]]previously rendered the empty sibling as[] : List List Integer, which is invalid Dhall (parses as(List List) Integer). The innerListtype is now parenthesized, producing[] : List (List Integer).infer_element_typeno longer gives up when a nested list is empty alongside non-empty homogeneous siblings: empty inner lists are now skipped during inference, so input like[[1, 2], [], [3, 4]]resolves toListType(inner=int)instead of falling back toNone(mixed types). When the rendered list literal contains an empty inner list beside non-empty list siblings, the empty inner now inherits the typed sequence opener of its siblings, so generated literals type-check cleanly under strongly typed languages (new int[]{}instead ofnew Object[]{}for Java,std::vector<int>{}for C++,[]int{}for Go,vec![]for Rust,New Integer() {}for Visual Basic, etc.).Documented the preamble-duplication sharp edge that arises when a caller composes
literalize()(declaring a{"$ref": "name"}variable) withliteralize_call()(referencing it) into a single source file: each call independently computes its ownpreambleandbody_preamble, so the combined output contains duplicates that strict compilers reject and a linter flags. Theliteralize_callreference now points at the new “Composing declarations and calls” section indocs/source/function-call-use-case.rst, which shows a worked Haskell example with the combinedbody_preambleblocks already deduplicated.CommonLispnow wraps{"$ref": "name"}identifiers in earmuffs (*name*) at the call site so they resolve to the matchingdefparameterdeclaration.CommonLispis no longer skipped by theliteralize_callreference-argument integration tests, which now lint cleanly under SBCL.Erlangnow capitalizes{"$ref": "name"}call arguments so they reference the declared variable instead of parsing as a lowercase atom, matching the existingMy_var = ...capitalization on the declaration site.Erlang.format_variable_declarationnow emits the trailing,separator itself so multiple declarations can precede a call;Erlang.wrap_in_file()is adjusted accordingly and the rendered output is unchanged for the single-declaration case.Perlliteralize_calloutput now emits Perl’s scalar$sigil before each{"$ref": "name"}identifier via aformat_call_ref_identifieroverride, so a ref tomy_varrenders as$my_varat the call site and lines up with themy $my_var = ...declaration site. Generated files now passperl -cunderuse strictandPerlis no longer excluded from the integration suite’s ref-declaration golden cases.Added
literalize_callsupport forMatlab.Matlab.CallStylesnow has aPOSITIONALmember backed by aPositionalCallStyle, andformat_call_stubemitsname = @(varargin) [];assignments so every target (including dotted paths likeapp.client.fetch) is a bound function handle before it is invoked. MATLAB’s auto-vivifying struct-field assignment means a single line defines the entire chain regardless of depth, so the stub is one statement per call target.lint-purescriptin.github/workflows/lint.ymlnow runs each PureScript fixture end-to-end. A newRun PureScript filesstep compiles each fixture withpurs compileand loads the resultingCheckmodule in Node so its top-levelmy_databinding is evaluated, catching foreign-implementation failures and other load-time crashes that the existingcheck_purescript_syntax.pycompile-only check would miss. The shared Prelude stub used by both steps lives in a newpurescript_common.pymodule.Added a
benchmarksjob to.github/workflows/ci.ymlthat runs thetests/benchmarks/suite under CodSpeed viapytest-codspeed. The job posts a per-benchmark performance delta on every pull request, making it easier to spot regressions in the YAML fast path, JSON formatting, and heterogeneous-widening logic.C,Cpp, andObjectiveCwrap_in_file/wrap_combined_in_fileoutput now emits(void)<variable_name>;after the declaration (and between the declaration and the re-assignment in the combined form) so the initial value is read before it is overwritten. clang-tidy’sclang-analyzer-deadcode.DeadStorescheck, previously suppressed in.clang-tidybecause the combined form and unused C++ scalars triggered dead-store warnings, is now enforced.
2026.04.24.1¶
Gleamnow emits apub type GValdeclaration containing only the constructors actually needed for the data, rather than always emitting all eight variants. Scalar-only inputs (e.g.GInt(42)) now produce a one-constructor type, bringing Gleam in line with Elm and Haskell.lint-luain.github/workflows/lint.ymlnow runs each Lua fixture end-to-end vialua, catching runtime errors (calls to undefined functions, missing module imports, failed assertions) that the existingluac -pparse-only step let through, mirroringlint-bash/lint-javascript/lint-perletc.literalize_call(..., wrap_in_file=True)now injects a no-op stub for thetarget_functioninto the wrapped file, so the generated source compiles against strict checkers on its own. Callers that supply acall_transformare still responsible for providing a definition for the wrapper function the transform introduces.Added
literalize_callsupport forBash. A newCommandCallStyletagged-union member renders calls astarget arg1 arg2with space-separated arguments and no surrounding parentheses; with acall_transformlikelambda c: f"emit({c})"the inner call is wrapped in$(...)command substitution (emit "$(target arg1 arg2)"). Bash’sformat_call_stubemitsname() { :; }function stubs that accept any arguments so generated files parse withbash -nand run underbash. A newCallArgNotSupportedErroris raised at literalize time when a list, dict, or set is passed as a Bash call argument — Bash has no inline compound-literal syntax in command invocations, so silently emittingcmd (1 2 3)(which parses as a nested(...)child-process group) would leave users with a broken script; callers must declare the collection as a variable and pass a$refmarker instead.literalize_callgains aref_casekeyword argument that converts{"$ref": "name"}identifiers to the target language’s idiomatic case at render time viapyhumps. PassIdentifierCase.SNAKE,CAMEL,PASCAL,UPPER_SNAKE, orKEBABto drive one YAML source through multiple languages without re-authoring the ref names (e.g. the sameuser_objref renders asuser_objfor Python,userObjfor JavaScript,UserObjfor Go). Each language exposes the subset it understands via itsidentifier_casestuple; passing an unsupported case raisesUnsupportedIdentifierCaseError. Whenref_case=None(the default) ref names are emitted verbatim, preserving existing behavior.Mojonow supports an opt-inHeterogeneousStrategies.VARIANTthat wraps mixed scalars in an auto-generatedcomptime Value = Variant[...]over only the Mojo types actually present in the data, with afrom std.utils.variant import Variantpreamble line. Each wrapped scalar renders asValue(...)(with an explicitString(...)orFloat64(...)cast when needed to select the intended Variant alternative, andNoneType()for nulls), so heterogeneous dicts and lists become homogeneous in the Variant type. The alias name is configurable viaMojo.heterogeneous_value_variant_name(default"Value"). The defaultERRORstrategy still raises on heterogeneous input.Cgenerated output now routes positive integers aboveLLONG_MAX(e.g.2**63) through a newunsigned long longunion field instead of narrowing them into the signedlong longfield. TheCValunion gains aumember alongsidei, and a newuint_fieldconstructor argument lets users rename it. clang-tidy’sbugprone-narrowing-conversionsandcppcoreguidelines-narrowing-conversionschecks, previously suppressed in.clang-tidybecause the union-initializer literal silently truncated those values, are now enforced for bothCandCppoutput.Cppgenerated output now wraps theINFINITYandNAN<cmath>macros instatic_cast<double>(with the negation applied outside the cast for-INFINITY) so that brace-init ofstd::vector<double>does not trip clang-tidy’s narrowing-conversions check on the implicitfloat-to-doubleconversion.Erlangnow supportsliteralize_call. Calls use positional argument syntax (f(A, B)); dotted targets likeapp.client.fetchare emitted as quoted-atom function names ('app.client.fetch'(...)) since Erlang atoms do not allow unquoted dots. Call stubs are emitted as module-level function definitions placed between-exportandx(), andx()separates call statements with,terminated by..Added
literalize_callsupport for Gleam:Gleam.format_call_preamble_stubemits module-levelpub fndeclarations with fresh type variables per parameter and apanicbody, andGleam.format_call_targetflattens dotted targets (e.g.app.client.fetch) to underscored identifiers (app_client_fetch) because Gleam identifiers cannot contain..Gleam.CallStyles.POSITIONALrenders calls asfunc(arg1, arg2).ObjectiveCcall stubs now emitk-prefixed, title-cased root names for thestatic const structglobals that back dotted call targets, so a user-writtenthrottler.check(...)is rendered askThrottler.check(...)(andapp.client.fetchtokApp.client.fetch). clang-tidy’sgoogle-objc-global-variable-declarationcheck, previously suppressed in.clang-tidybecause the bare root names did not conform, is now enforced.
2026.04.24¶
literalize_callwithper_element=Truenow widens Rust’sTAGGED_ENUMscalar wrapping across sibling calls at matching argument slots. Previously the wrap analysis ran per call, so a locally-homogeneous sibling dict would emit unwrapped scalars even when another call at the same slot was heterogeneous — a secondm.process(HashMap::from([("a", "x")]))would not match the&HashMap<&'static str, Value>parameter implied by the first heterogeneous call. Mirrors the dict-opener widening already applied for typed dict languages on the per-element call path.lint-erlangin.github/workflows/lint.ymlnow passes-Werrortoerlc, so warnings such asevaluation of operator '-'/1 will fail with a 'badarith' exceptionfail the job instead of being silently logged.Erlanggenerated output for negative infinity is now the quoted atom'-inf'instead of the bare-inf, which the compiler treated as unary negation of the atominfand flagged as a guaranteed runtimebadarith.
2026.04.23¶
Sibling sequences that appear as values of the same dict now widen to a common element type at each matching position, so a caller iterating the dict values tuple-style sees a consistent element type at each positional slot instead of one branch narrowed to a concrete type and another collapsed to the fallback. The widening uses the language’s fallback sequence opener when the inferred types diverge and is skipped for variant-typed languages (e.g. C++) whose fallback opener is element-specific rather than universally accepting.
lint-haskellin.github/workflows/lint.ymlnow passes-Wall -Werrorto both the syntax check and the end-to-end build, so warnings such as-Wunused-matches,-Woverlapping-patterns, and-Wtype-defaultsfail the job instead of being silently logged.Haskellgenerated output was updated to compile clean under-Wall: theNum/Fractionalinstances use_for unused parameters, the catch-allnegate _clause is now omitted whenValhas only numeric constructors, tuple-sequence bindings carry a(Val, Val, ...)annotation, call stubs get explicit type signatures, transform-wrapper stubs use a polymorphic argument type,mainbinds each call result with_ <-, and theData.Timeimport set dropssecondsToDiffTimewhen every datetime has microseconds.Javadeclarations and reassignments whose value ends in a//line comment now place the terminating;on the code line rather than on the comment line, wherejavacpreviously parsed it as part of the comment and rejected the program with';' expected.Mojo.skip_null_dict_valuesis nowTrueso dicts containing null values render as the emptyDict[String, String]()literal (previously they emitted{"k": None, ...}, which the Mojo compiler rejects becauseNoneTypeis not a usable dict value type). Mixed-type inputs continue to raiseHeterogeneousScalarCollectionErroras before; this only affects dicts whose values are entirelynull.literalize_callnow accepts{"$ref": "name"}markers at argument positions, emittingnameas a bare identifier instead of formatting the value as a literal. Refs and literals can be mixed in the same call, and the marker is detected across all four input formats (JSON, JSON5, YAML, TOML). Ref dicts are excluded from data-shape validation and data-driven preamble inference so they do not drag in imports for the{str: str}shape of the marker itself.lint-objectivecin.github/workflows/lint.ymlnow passes-Werrorto bothclang -fsyntax-onlyand the end-to-endclangcompile step so warnings such as-Wimplicitly-unsigned-literalfail the job instead of being silently logged.ObjectiveC.format_integernow appends aULLsuffix to values aboveLLONG_MAX(matching the C fallback) and raisesUnrepresentableIntegerErrorfor values belowLLONG_MIN, so emitted fixtures compile cleanly under the stricter workflow.Added
Nim.HeterogeneousStrategieswith anOBJECT_VARIANToption that auto-generates a Nim object variant in the preamble whenever a dict, list, or sibling-list pair contains scalar values of more than one Nim type. Each heterogeneous value is wrapped at the call site as{VariantName}(kind: vkX, xVal: value); only the branches actually present in the data are emitted. The strategy switches the dict syntax from%* {key: value}to{key: value}.toTable(importingtablesinstead ofjson) so the object variants can be stored directly, and nested sequences render as@[...]at every level. The variant-type name defaults toValueand is configurable via the newheterogeneous_value_variant_nameconstructor argument.OBJECT_VARIANTis incompatible withDeclarationStyles.CONSTbecause.toTableand@[]are runtime constructors; the constructor raisesIncompatibleFormatsErrorfor that combination. The default remainsHeterogeneousStrategies.ERROR(unchanged behavior).The
heterogeneous_strategyvariant case list now includes theordered_mapfixture, covering RustTAGGED_ENUMand DhallUNION_TYPErendering on!!omapinputs.lint-swiftin.github/workflows/lint.ymlnow runs itsswiftc -typecheckstep in parallel viaxargs -P, replacing the previous serialwhileloop so the job no longer cold-starts the compiler one fixture at a time.lint-swiftin.github/workflows/lint.ymlnow runs each Swift fixture end-to-end viaswiftin script mode, catching runtime errors thatswiftc -typecheckalone could miss (for example, integer literals that overflowInt). So that every emitted fixture compiles,Swift.format_integernow raisesUnrepresentableIntegerErrorfor values outside the signed 64-bit range, matching the behavior of other languages without native arbitrary-precision integer support.lint-groovyin.github/workflows/lint.ymlnow runs each Groovy fixture end-to-end, catching runtime errors (calls to undefined functions, missing module imports, failed assertions) that the existinggroovyccompile-only step let through.Groovy.format_call_stubnow emits a singleMap _argsmethod parameter whencall_styleisKEYWORD— previously thecall_keyword_argsfixture trippedMissingMethodExceptionbecause Groovy passes named arguments as a singleLinkedHashMapthat a positional parameter list rejects.POSITIONALstubs keep the concrete parameter list unchanged.lint-objectivecnow executes each fixture end-to-end instead of only syntax-checking it, mirroringlint-bash/lint-javascript/lint-perletc. To make this possible, Objective-C declarations and reassignments now box primitive scalars the same way collection entries do (id x = 42;→id x = @(42);), single-name call stubs emit astaticdefinition so fixtures link, andObjectiveC.supports_scalar_inline_commentsis nowFalse— previously the trailing//comment swallowed the statement terminator. A pre-existing casing bug in the workflow’slang_patterns(objective_c*.minstead ofObjectiveC*.m) that silently skipped every fixture is also fixed.lint-elmin.github/workflows/lint.ymlnow runs each Elm fixture end-to-end. A newRun Elm filesstep compiles each fixture alongside a smallMain.elmwrapper whosePlatform.workerinit forcesCheck.my_data, emits JavaScript viaelm make, and executes it with Node so runtime crashes such asDebug.todosurface in CI. Thescalar_int_very_negative_largefixture is skipped because the Elm 0.19.1 code generator emits--<digits>(two unary minuses) for integers at the int64 boundary, which JavaScript rejects as a prefix-decrement syntax error.lint-smlin.github/workflows/lint.ymlnow runs each Standard ML fixture end-to-end. BecauseMLtonnever evaluates astructure’s body unless a top-level expression forces it, the new step compiles each fixture via an ML Basis file that concatenates the fixture with a smallval _ = Check.my_datasnippet and runs the resulting binary, catching runtime errors such as references to undefined names, missing module imports, or failed assertions.Removed the K&R-style empty-prototype suppression directives from C and Objective-C call stubs.
C.format_call_preamble_stubandObjectiveC.format_call_preamble_stubnow emit concrete prototypes (CValparameters for C,idparameters for Objective-C) sized to the call’s parameter list, and an internalformat_call_arghook wraps each call argument so the call site matches the prototype. Generated C and Objective-C call code now compiles cleanly under-Wstrict-prototypes -Wdeprecated-non-prototype -Werrorwithout suppression.C++ container types now pick the narrowest integer type that holds the actual values in each collection:
intwhen every value fits in 32 bits, otherwiselong long. This mirrors the existing per-value suffix logic in Rust and fixes a case wherestd::variant<int, …>could not hold literals aboveINT_MAX.Cpp.NumericLiteralSuffixes.AUTOstill emitslong+Lsuffix for every integer.Added
Dhall.HeterogeneousStrategieswith aUNION_TYPEoption that auto-generates a Dhall union type in the preamble whenever a dict, list, or sibling-list pair contains scalar values of more than one Dhall type. Each heterogeneous value is wrapped at the call site as{UnionName}.{Variant} payload; only the variants actually present in the data are emitted. The union name defaults toValueand is configurable via the newheterogeneous_value_union_nameconstructor argument. The default remainsHeterogeneousStrategies.ERROR(unchanged behavior).Added
literalize_callsupport for Clojure:Clojure.format_call_stubemitsdefnstubs with[& _args]so generated definitions accept any mix of positional and keyword arguments, andClojure.CallStyles.PREFIX_KEYWORDrenders calls as(func :name value).Added
literalize_callsupport for Objective-C:ObjectiveC.format_call_preamble_stubemits C-style forward declarations and nestedstructchains with function-pointer leaves for dotted targets, andObjectiveC.CallStyles.POSITIONALrenders calls asfunc(arg1, arg2).Added
literalize_callsupport for Perl:Perl.format_call_stubemits an emptysub {}declaration for each dot-separated part of the target name, so call expressions (including dotted targets, where.is Perl’s string concatenation operator) compile cleanly underperl -c.
2026.04.21.5¶
Added
Rust.DeclarationStyles.LAZY_STATIC, which wraps the initializer instd::sync::LazyLockso module-level declarations can hold runtime-initialized collections such asHashMap,BTreeMap, andVec. UnlikeCONSTandSTATIC,LAZY_STATICcomposes with every dict, set, and sequence format.use std::sync::LazyLock;is added to the preamble automatically.Added
literalize_callsupport for Common Lisp:CommonLisp.format_call_stubemitsdefunstubs with&rest argsso generated definitions accept any mix of positional and keyword arguments, andCommonLisp.CallStyles.PREFIX_KEYWORDrenders calls as(func :name value).Added
literalize_callsupport for Racket:Racket.format_call_stubnow generatesmake-keyword-procedurestub definitions, and a newPrefixCallStylecall-style variant handles S-expression call assembly(func arg1 arg2)for Lisp-family languages.
2026.04.21.4¶
Fixed
pre_indent_levelinteraction withNewVariableandExistingVariable: a multi-line value no longer inserts the pre-indent between=and the value (and no longer doubly indents continuation lines). Every line of the wrapped declaration or assignment is now uniformly offset bypre_indent_level.Breaking: Replaced the three public collection-opener helpers
fixed_set_open,fixed_sequence_open, andfixed_dict_openwith a singlefixed_open. They had identical implementations and differed only in the type hint of the unused parameter. Replace each call withfixed_open(open_str=...).literalize_callnow raisesParameterCountMismatchErrorwith a descriptiveExpected N parameters but got M valuesmessage whenparameter_namesdoes not match a row’s value count, replacing the opaqueValueErrorfromzip(strict=True).
2026.04.21.3¶
Added
Rust.HeterogeneousStrategieswith aTAGGED_ENUMoption that auto-generates a small taggedenumin the preamble whenever a dict, list, or sibling-list pair contains scalar values of more than one Rust type. Each heterogeneous value is wrapped at the call site as{EnumName}::{Variant}(value); only the variants actually present in the data are emitted, with integer variants using Rust’s narrowest-width names (I32,I64,I128). The enum name defaults toValueand is configurable via the newheterogeneous_value_enum_nameconstructor argument. The default remainsHeterogeneousStrategies.ERROR(unchanged behavior).The
lint-juliaCI job now executes Julia golden files instead of only parsing them, catchingUndefVarErrorand other runtime errors.literalize_callnow distinguishes two reasons a language has no call support. The singleUnsupportedCallStyleErrorhas been replaced byCallsNotSupportedByLanguageError(raised for data/markup formats like YAML, TOML, JSON5, Norg that have no function call syntax) andCallsNotSupportedByToolError(raised for programming languages whose call rendering literalizer has not yet implemented). The newCallSupportenum on a language’scall_style_configattribute captures which case applies.Lint workflow now runs pre-commit hooks against the full supported Python matrix (3.12, 3.13, 3.14) instead of 3.13 only, to catch version-specific lint issues.
2026.04.21.2¶
Added
literalize_callsupport for PHP:Php.format_call_stubnow generates function, class, and nested-object stubs for a call expression.
2026.04.21.1¶
2026.04.21¶
Added a per-language
Modifiersenum exposed on each language class (alongsideDateFormats,SequenceFormats, etc.).Java.ModifiershasPUBLIC/PRIVATE/PROTECTED/STATIC/FINAL;CSharp.ModifiersaddsCONSTandREADONLY;Cpp.ModifiershasSTATIC/CONST. Languages without modifier vocabulary expose an emptyModifiersenum.NewVariableandBothVariableFormsnow accept amodifierskeyword argument. Values that are not members of the target language’sModifiersenum are silently ignored, matching how other language format enums behave.Removed automatic coercion of heterogeneous data to strings. The
error_on_coercionparameter has been removed fromliteralize;literalizenow always raises a subclass ofHeterogeneousCollectionErrorwhen the data cannot be represented in the target language’s collection formats.Replaced
HeterogeneousCoercionErrorwith precise exceptions:HeterogeneousCollectionError(base class),HeterogeneousScalarCollectionError,HeterogeneousSiblingListsError,MixedDictValuesError,MixedListValuesError,MixedDictShapesError, andHeterogeneousSetError.Renamed
SetFormatConfig.coerce_mixed_to_strtoSetFormatConfig.supports_heterogeneity(with inverted semantics).
2026.04.18¶
2026.04.15¶
2026.04.14¶
2026.04.13¶
2026.04.06¶
Added new output languages: Dhall, Elm, Gleam, JSON5, Jsonnet, Odin, PureScript, Raku, Scheme, SystemVerilog.
Unified
literalize_json,literalize_yaml, andliteralize_tomlinto a singleliteralizefunction with anInputFormatparameter.Added TOML input support (
InputFormat.TOML) with comment preservation.Added JSON5 input format (
InputFormat.JSON5).Added
body_preamble,bare_code,declaration_code, andpre_declaration_commentsattributes toLiteralizeResult.Added
requires_uniform_record_shapesanddeclared_typetoSequenceFormatConfig.Added
supports_scalar_before_commentsandsupports_scalar_inline_commentslanguage properties.
2026.03.30¶
Added
default_ordered_map_value_typeparameter to Go for configuring the element type in ordered map literals.Added
supports_default_ordered_map_value_typeandsupports_default_ordered_map_key_typeclass attributes toLanguageCls.
2026.03.29¶
2026.03.26.2¶
2026.03.26.1¶
Renamed
VariableTypeHints.NONEtoAUTOandVariableTypeHints.INLINEtoALWAYS.
2026.03.26¶
2026.03.25¶
2026.03.23¶
2026.03.22.1¶
2026.03.22¶
2026.03.21¶
Removed
LanguageSpecdataclass. Use theLanguageprotocol directly to define custom languages.
2026.03.20.3¶
2026.03.20.2¶
2026.03.20.1¶
2026.03.20¶
2026.03.19.1¶
2026.03.19¶
2026.03.18¶
Added
format_sequence_entryto theLanguageprotocol, mirroring the existingformat_set_entryfield. All built-in languages use the newpassthrough_sequence_entryformatter.