You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Closing out the OptimalControl.jl documentation rewrite (PR #942, the §12 acceptance walk), one criterion reads: "every removed v2.0 spelling either throws a PreconditionError naming its replacement, or is documented as unshimmable, with the reason." Walking it made clear that the ecosystem currently deprecates in at least four different ways, with no written policy for choosing between them.
This is a discussion, not a proposal to merge anything — I want to lay out the picture and float some options.
The current picture
Mechanism
What the user gets
Where it is used today
Hard throw — PreconditionError naming the replacement
fails immediately, points at the fix
OptimalControl src/deprecated.jl (Lie, ⋅, HamiltonianLift, Flow(f), time(ocp), success(sol), the positional flow-call conventions); CTFlows Flows/building.jl guards (Flow(ocp, u, g, μ), Flow(f::Function))
BREAKING.md exists in CTBase, CTSolvers, CTFlows, CTModels, CTParser, CTLie, OptimalControl — but not CTDirect.
A dedicated src/deprecated.jl exists only in OptimalControl. Everywhere else, deprecation logic (where there is any) is inline and scattered.
The PreconditionError wording is hand-rolled per call site — OptimalControl has a _deprecated(old, new) helper; CTFlows writes its own strings.
What already works well
OptimalControl's deprecated.jl + _deprecated helper: one file, one wording, every removed spelling lands on a message that names its replacement and the version it went in.
CTFlows guarding its own removed Flow signatures directly, so the error comes from the package that owns the API.
migration.md's explicit "What could not be shimmed, and why" table — the cases Julia genuinely cannot dispatch on (keyword renames, module-qualified names, same-arity collisions) are named, each with its reason.
Things worth discussing (suggestions, not decisions)
1. A default mechanism? A candidate: hard PreconditionError naming the replacement, for anything that was public and is now gone or renamed. Fail fast, point at the fix, never a silent behaviour change. The soft-depwarn path buys a grace period but risks code that "still works" while quietly doing something different — times(sol) changing what it returns is the cautionary tale.
2. Where the fallback is recorded. When a hard throw is not dispatchable — keyword-name renames (autonomous= → is_autonomous=), module-qualified names (CTDirect.Collocation()), same-arity collisions — the only place to catch it is BREAKING.md / the migration guide. Suggestion: make that an explicit rule — if it cannot be shimmed, it goes in BREAKING.md with the reason it cannot — so "silent breakage" is never the resting state.
3. One known home per repo. Every repo gets a src/deprecated.jl (even a near-empty one), included at a consistent point, as the single place to look. Scattered inline guards work but are hard to find and audit.
4. BREAKING.md everywhere — CTDirect is the only repo without one.
5. A shared helper.CTBase.deprecated(old, new; reason, version) returning the standard PreconditionError, so the wording is identical across the ecosystem. OptimalControl's _deprecated would move there. Optional — but it is the difference between seven slightly-different messages and one.
6. Avoid silent aliases for documented names.euler_b / trapezoidal still resolve with no signal. Even a one-time depwarn would tell a user the canonical name changed.
7. The module re-export asymmetry. OptimalControl re-exports CTBase / CTModels / CTFlows / CTLie as module aliases but deliberately not CTSolvers / CTDirect / CTParser. That is a defensible "these are implementation packages" stance — but it means CTDirect.Collocation() from an old script fails with UndefVarError: CTDirect and there is nothing to attach a hint to. Worth deciding whether that asymmetry stays (and is simply documented) or the three get aliased too.
8. How long do shims live? Until the next stable? Two minors? A stated horizon would let us actually delete deprecated.jl files instead of carrying them indefinitely.
Not asking for a decision here
Just want to hear what people think the policy should be, so it can go into the Handbook and the repos can align against it. Happy to draft a Handbook/DEPRECATION.md and the per-repo issues once there is rough consensus.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Where this comes from
Closing out the OptimalControl.jl documentation rewrite (PR #942, the §12 acceptance walk), one criterion reads: "every removed v2.0 spelling either throws a
PreconditionErrornaming its replacement, or is documented as unshimmable, with the reason." Walking it made clear that the ecosystem currently deprecates in at least four different ways, with no written policy for choosing between them.This is a discussion, not a proposal to merge anything — I want to lay out the picture and float some options.
The current picture
PreconditionErrornaming the replacementsrc/deprecated.jl(Lie,⋅,HamiltonianLift,Flow(f),time(ocp),success(sol), the positional flow-call conventions); CTFlowsFlows/building.jlguards (Flow(ocp, u, g, μ),Flow(f::Function))Base.depwarn— still works, warns onceis_parameter_type→is_a_parametereuler_b→euler_implicit,trapezoidal→trapezeMethodError/UndefVarErrortimes(sol)semantic change; CTFlowsOpenLoop(() -> …),Systems.hamiltonian_gradient→get_*; CTLieCTFlows.DifferentialGeometry.ad→CTLie.ad,ad_backendkeyword type; CTParser:exaneeds ExaModels ≥ 0.12;CTSolvers.Modelers.ADNLP()/CTDirect.Collocation()Supporting infrastructure is uneven too:
BREAKING.mdexists in CTBase, CTSolvers, CTFlows, CTModels, CTParser, CTLie, OptimalControl — but not CTDirect.src/deprecated.jlexists only in OptimalControl. Everywhere else, deprecation logic (where there is any) is inline and scattered.PreconditionErrorwording is hand-rolled per call site — OptimalControl has a_deprecated(old, new)helper; CTFlows writes its own strings.What already works well
deprecated.jl+_deprecatedhelper: one file, one wording, every removed spelling lands on a message that names its replacement and the version it went in.Flowsignatures directly, so the error comes from the package that owns the API.migration.md's explicit "What could not be shimmed, and why" table — the cases Julia genuinely cannot dispatch on (keyword renames, module-qualified names, same-arity collisions) are named, each with its reason.Things worth discussing (suggestions, not decisions)
1. A default mechanism? A candidate: hard
PreconditionErrornaming the replacement, for anything that was public and is now gone or renamed. Fail fast, point at the fix, never a silent behaviour change. The soft-depwarnpath buys a grace period but risks code that "still works" while quietly doing something different —times(sol)changing what it returns is the cautionary tale.2. Where the fallback is recorded. When a hard throw is not dispatchable — keyword-name renames (
autonomous=→is_autonomous=), module-qualified names (CTDirect.Collocation()), same-arity collisions — the only place to catch it isBREAKING.md/ the migration guide. Suggestion: make that an explicit rule — if it cannot be shimmed, it goes inBREAKING.mdwith the reason it cannot — so "silent breakage" is never the resting state.3. One known home per repo. Every repo gets a
src/deprecated.jl(even a near-empty one), included at a consistent point, as the single place to look. Scattered inline guards work but are hard to find and audit.4.
BREAKING.mdeverywhere — CTDirect is the only repo without one.5. A shared helper.
CTBase.deprecated(old, new; reason, version)returning the standardPreconditionError, so the wording is identical across the ecosystem. OptimalControl's_deprecatedwould move there. Optional — but it is the difference between seven slightly-different messages and one.6. Avoid silent aliases for documented names.
euler_b/trapezoidalstill resolve with no signal. Even a one-timedepwarnwould tell a user the canonical name changed.7. The module re-export asymmetry. OptimalControl re-exports
CTBase/CTModels/CTFlows/CTLieas module aliases but deliberately notCTSolvers/CTDirect/CTParser. That is a defensible "these are implementation packages" stance — but it meansCTDirect.Collocation()from an old script fails withUndefVarError: CTDirectand there is nothing to attach a hint to. Worth deciding whether that asymmetry stays (and is simply documented) or the three get aliased too.8. How long do shims live? Until the next stable? Two minors? A stated horizon would let us actually delete
deprecated.jlfiles instead of carrying them indefinitely.Not asking for a decision here
Just want to hear what people think the policy should be, so it can go into the Handbook and the repos can align against it. Happy to draft a
Handbook/DEPRECATION.mdand the per-repo issues once there is rough consensus.All reactions