control-toolbox / control-toolbox/CTParser.jl
An unknown discretization scheme is thrown as a bare String, not a CTException
- Dominant language
- Julia
- Stars
- 3
- Forks
- 0
- Avg merge
- 4h 22m
- Merged PRs (30d)
- 2
Description
## Summary
`src/onepass.jl` throws a `String` for an unknown scheme, so `typeof(e) === String`. Callers
cannot dispatch on it, and any code following the ecosystem's exception conventions has nothing
typed to catch.
## Reproduction
```julia
using OptimalControl, ExaModels, NLPModelsIpopt
ocp = @def begin
t ∈ [0, 1], time
x = (q, v) ∈ R², state
u ∈ R, control
x(0) == [-1, 0]
x(1) == [0, 0]
∂(q)(t) == v(t)
∂(v)(t) == u(t)
∫(0.5u(t)^2) → min
end
try
solve(ocp, :exa; scheme=:gauss_legendre_2, display=false)
catch e
println("typeof = ", typeof(e))
println(e)
end
```
```
typeof = String
unknown numerical scheme: gauss_legendre_2 (possible choices are :euler, :euler_implicit, :midpoint, :trapeze)
```
## Cause
`src/onepass.jl:941`, and identically at `:1035` and `:1106`:
```julia
throw(
"unknown numerical scheme: $scheme (possible choices are :euler, :euler_implicit, :midpoint, :trapeze)",
) # (vs. __throw) since raised at runtime (and __wrap-ped)
```
The adjacent comment says the choice is deliberate — `__throw` is avoided because the error is
raised at runtime inside `__wrap`-ped generated code. But `__wrap` rethrows whatever it caught,
so the type of the thrown object is preserved either way; nothing about the runtime context
requires it to be untyped.
## Why it matters
The ecosystem's stated rule is seven typed exceptions under `CTException`, chosen so callers can
catch precisely. Three consequences here:
1. `catch e ... e isa CTBase.Exceptions.IncorrectArgument` never matches.
2. `showerror(io, e)` falls back to `show`, so the message renders **with surrounding quotes**
instead of the formatted `│ Reason / Context / Hint` block every other error in the ecosystem
produces.
3. Concretely, this is the one exception demonstration in the OptimalControl documentation that
cannot use the Handbook's prescribed `@repl` + `showerror` form — the other twenty-nine
converted cleanly. See control-toolbox/OptimalControl.jl#878.
## Suggested fix
`CTBase.Exceptions.IncorrectArgument` fits exactly: a single argument has an invalid value, and
the type carries `got` / `expected` / `suggestion` fields that already match what the message
spells out by hand.
```julia
throw(IncorrectArgument(
"unknown numerical scheme";
got = string(scheme),
expected = ":euler, :euler_implicit, :midpoint or :trapeze",
))
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Contributor guide
Assessment
This issue has not been assessed yet.