Skip to content

Let typed parses with a custom read style compile under juliac --trim - #555

Merged
quinnj-bot[bot] merged 2 commits into
masterfrom
claude/trim-custom-style-parse
Oct 30, 2026
Merged

quinnj-bot[bot] merged 2 commits into
masterfrom
claude/trim-custom-style-parse

Conversation

@quinnj-bot

@quinnj-bot quinnj-bot Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #553.

Since #543 (JSON 1.13.0), a typed JSON.parse with a custom read style fails juliac --trim verification. The trimmed program can also fail at run time: "[1]" parsed into Int64 threw MethodError(indexed_iterate, ...) instead of the MethodError(convert, ...) the JIT throws.

What was wrong:

  • The "cannot parse" message interpolated the LazyValue, which brings show(::LazyValue) and Base's array printing into the image.
  • A custom style's values are untyped, because its hooks can return anything. They were handed to callbacks (ObjectClosure's value closure, lift's ValueClosure), which is a dynamic call.
  • Objects were built through _makeobject. Its fallback into StructUtils' generic make, for input that isn't an object, sits among the recursive calls. With it there, juliac couldn't compile those calls on Julia 1.14. On 1.13 it couldn't compile a later default-style JSON.parse(s, Dict{String,Any}) in the same program: 2 unresolved invokes of applyobject, depending on which parse inference reached first. JSON 1.12.2 had neither problem.

What changed:

  • For a custom style, make(st, Any, x) and lift build objects and arrays with their concrete types (_makecontainer). Objects go through _makeobjectvalue, which is _makeobject without that fallback; the input is already known to be an object there.
  • ObjectClosure takes a custom style's Any values from make instead of a callback.
  • Default-style code paths are unchanged; only _customstyle checks that fold away at compile time were added to them.
  • The error message no longer includes the value.
  • reading.md says that the dicttype= keyword can't be used under juliac --trim. A type passed as a keyword reaches the parser as a plain DataType. --trim=safe reports this, and an unsafe build fails at run time. There's nothing to fix in JSON for this.

Test: test/json_trim_public_entrypoints.jl gains exercise_custom_style_parse:

  • a scalar target;
  • a struct target;
  • an Any field holding nested objects, arrays, numbers including a BigInt, null and bools;
  • a Dict{String,Any} target;
  • a mismatch that must throw a MethodError.

It runs before the default-style parses, so it also covers the ordering problem.

Verification (macOS arm64, JuliaC 0.3.12, --trim=safe unless noted):

  • The new trim test fails on master and passes with this PR:

    Julia master this PR
    1.14.0-alpha2 (not counted) 7/7
    1.13.3 132 verifier errors 7/7
    1.12.7 98 verifier errors 7/7
  • The issue's repro has no verifier errors with this PR. For 42, [1], {"a":1}, "x", null, true, 1.5 and nested input, the trimmed program gives the same result or exception as the JIT.

  • A custom-style parse followed by a default Dict{String,Any} parse in one program builds clean on 1.12.7, 1.13.3 and 1.14.0-alpha2. That program is also clean on JSON 1.12.2 and on a7ec7ba, the commit before Fix Any-field lift and parse-option regressions; lift targets get the parse's options #543.

  • Full test suite passes locally on Julia 1.10.12, 1.12.7 and 1.13.3. Trim compile is 7/7 on 1.12 and 1.13 and skipped on 1.10.

  • Default-style speed: the default style's code paths are the same as on master, including with null=, dicttype= and error_context=true. An earlier version that sent default null= parses through _makecontainer measured +4% to +12% on 1.13.3 for Any fields, so default styles keep master's path.

  • Independent review of the previous head: JIT results matched master in 53,493 cases (9 styles, 9 option sets, 26 targets, 22 inputs), and 19 --trim=safe programs built and matched the JIT on 1.12.7, 1.13.3 and 1.14.0-alpha2. Custom-style parses use 22-69% fewer instructions, and an 8,000-key Any object is still built in one pass. On this head, every custom-style program in that set builds clean on 1.13.3 and 1.14.0-alpha2.

  • Not changed, and as on master: default-style null= and error_context=true parses into Any fields aren't trim-safe yet (2 and 6 verifier errors on 1.13.3), and neither is error_context=true with a custom style.

Note: juliac --trim=unsafe-warn reported neither the dynamic callback call nor the dicttype= one. --trim=safe reported both, and both failed at run time.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.23810% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.21%. Comparing base (0e8463a) to head (1ffbbc7).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
src/parse.jl 95.23% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #555      +/-   ##
==========================================
+ Coverage   94.14%   94.21%   +0.06%     
==========================================
  Files           8        8              
  Lines        2324     2333       +9     
==========================================
+ Hits         2188     2198      +10     
+ Misses        136      135       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@quinnj-bot quinnj-bot Bot mentioned this pull request Oct 30, 2026
@quinnj-bot
quinnj-bot Bot force-pushed the claude/trim-custom-style-parse branch from 2e42bc7 to 70650b0 Compare October 30, 2026 18:00
Since #543, a typed `JSON.parse` with a custom `StructStyle` builds nested
objects and arrays with the style's hooks. Under `juliac --trim` that path
failed verification, and in an unsafe build it threw
`MethodError(indexed_iterate, ...)` at run time:

- the "cannot parse" error message interpolated the `LazyValue`, which
  pulled `show(::LazyValue)` into the image;
- a custom style's values, untyped because its hooks can return anything,
  were handed to callbacks (`ObjectClosure`'s value closure, `lift`'s
  `ValueClosure`), which is a dynamic call;
- objects were built through `_makeobject`, whose fallback into
  StructUtils' generic `make` sits among the recursive calls; with it,
  `juliac` couldn't compile those calls (Julia 1.14) or, on 1.13, a later
  default-style `Dict{String,Any}` parse in the same program.

For a custom style, `make(st, Any, x)` and `lift` now build objects and
arrays with their concrete types (`_makecontainer`), objects without that
fallback (`_makeobjectvalue`), and `ObjectClosure` takes `Any` values from
`make` instead of a callback. Default-style code paths are unchanged. The
trim entry-point test parses with a custom style, including `Any` fields,
nested values and a `Dict` target, before the default-style parses.

The docs say that `dicttype=` can't be used under `juliac --trim`.

Fixes #553.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@quinnj-bot
quinnj-bot Bot force-pushed the claude/trim-custom-style-parse branch from 70650b0 to ff4c43a Compare October 30, 2026 18:32
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@quinnj-bot
quinnj-bot Bot merged commit db9e50c into master Oct 30, 2026
12 checks passed
@quinnj-bot
quinnj-bot Bot deleted the claude/trim-custom-style-parse branch October 30, 2026 19:24
@quinnj-bot quinnj-bot Bot mentioned this pull request Oct 30, 2026
quinnj referenced this pull request Oct 30, 2026
Co-authored-by: Jacob Quinn <quinn.jacobd@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSON.parse with a custom style is no longer juliac --trim-verifiable since #543 (0 → 116 verifier errors)

1 participant