control-toolbox / control-toolbox/CTParser.jl

An unknown discretization scheme is thrown as a bare String, not a CTException

Open
#322 1 comment 0 reactions 1 assignee Claimed by @jbcaillau View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.