Inference And Trimming
OptParse is built with trimming in mind, so inference quality is not an optional polish step.
What To Test
Current tests generally use:
- ordinary behavior tests
@test_optfor inference-sensitive paths- trimming integration tests under
test/trim/
When adding a parser family, add:
- direct primitive or family tests
- at least one
@test_opton the constructor itself - at least one
@test_optthroughoptparse(...)
Common Inference Traps
- Loose
parse/completesignatures - Returning
Contextwithout preserving the state type parameter - Hidden type changes inside helper functions
- Broadly typed higher-order helpers
- Accidental materialization into abstract containers
JET-Specific Lesson
A runtime path may be perfectly fine while JET still reports a dispatch problem if impossible parser-family instantiations are not ruled out by method signatures.
When that happens, check:
- whether the parser family method is too broadly typed
- whether constructor invariants are being expressed in dispatch
- whether a helper erased the state parameter from
Context{S}
Do not paper over this with assertions first. Tighten the family invariants first.
Why Tight State Dispatch Matters
Even with a concrete parser tree, inference will consider many possible parser-family instantiations unless your method signatures rule impossible ones out early.
This is why a family-specific method like:
function parse(p::ArgOption{T, OptionState{T}}, ctx::Context{OptionState{T}})::InnerParseResult{OptionState{T}} where {T}is much better than:
function parse(p::ArgOption, ctx::Context)The tight version:
- documents the actual invariant of the family
- trims impossible instantiations sooner
- keeps JET focused on reachable execution paths
- usually improves trimming behavior as well
Wrapper Child-State Invariants
For wrapper parsers, also constrain the child parser type parameter.
The common trap is writing a wrapper method that constrains the wrapper state but leaves the child parser type unconstrained:
function parse(p::ModHelp{T,S,_p,P}, ctx::Context{S}) where {T,S,_p,P}Runtime construction may guarantee that P is a parser whose state is S, but the method signature does not say that. JET can then explore impossible instantiations of P, such as a child parser whose state does not match Context{S}.
Prefer:
function parse(
p::ModHelp{T,S,_p,P},
ctx::Context{S},
)::InnerParseResult{S} where {T,S,_p,P <: AbstractParser{<:Any,S}}For wrappers with nested child state, bind P to the inner state:
function complete(
p::ModWithDefault{T,WithDefaultState{S},_p,P},
state::WithDefaultState{S},
)::ParseResult{T} where {T,S,_p,P <: AbstractParser{<:Any,S}}This invariant was exposed by the help information modifier wrapping default. The parser values were valid, but the unconstrained method left inference free to consider impossible child-state combinations.
Current Design Notes
Two internal rules are worth keeping in mind while working on inference-sensitive code:
- parser families should only operate on their own state shape
Contextshould be updated through its helper API so the state parameter stays explicit- wrapper methods should constrain child parser type parameters to the child state they delegate to
If inference starts to widen, look there first before adding local assertions.