Make @def hygienic: the generated code should contain the modules, not their names

Aperta
#356 1 commento 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@jbcaillau ci sta già lavorando.

Dal 9/10/2026.

  • #413 di @ocots — aperta

Valutazione

Questa issue non è ancora stata valutata.

Descrizione

Summary

The code generated by @def names the modules CTModels, ExaModels and CTBase by symbol, so it only runs in a module that has bindings with these names. Today, OptimalControl.@def does not work after a plain import OptimalControl. Put the modules themselves in the generated code instead, and remove the configurable prefixes, which no longer have a purpose.

The problem

With CTParser 0.9 and OptimalControl 2.2:

module M
import OptimalControl
ocp = OptimalControl.@def begin
    t ∈ [0, 1], time
    x ∈ R, state
    u ∈ R, control
    x(0) == 0
    ẋ(t) == u(t)
    ∫(u(t)^2) → min
end
end
UndefVarError: `CTModels` not defined in `Main.M`

using OptimalControl works, because OptimalControl re-exports the module names CTModels, ExaModels and CTBase for this reason only (@reexport import CTModels: CTModels # for generated code (prefix)). So every user of @def depends on the namespace of OptimalControl, and using OptimalControl: @def or import OptimalControl does not work. The @Lie macro of CTLie has the same issue: control-toolbox/CTLie.jl#40.

Where the names come from

In src/onepass.jl, about twenty code generators start with pref = prefix_fun() (or prefix_exa(), or the error prefix) and generate :($pref.state!(…)). The prefixes are Ref{Symbol} (PREFIX_FUN, PREFIX_EXA, E_PREFIX), with defaults :CTModels, :ExaModels and :CTBase in src/defaults.jl, and setters prefix_fun!, prefix_exa! and e_prefix!. The symbol is resolved in the module where the user calls the macro.

The prefixes were introduced when the functions were re-exported through OptimalControl, and OptimalControl was the prefix of everything. This is no longer the case, and in the ecosystem only the tests of CTParser (test/test_prefix.jl) set them to another value.

Proposal: dependencies on the three packages, and the modules in the generated code

Make CTBase, CTModels and ExaModels direct dependencies of CTParser, and generate the code with the module values:

# before
pref = prefix_fun()        # a Symbol, resolved in the caller's module
:($pref.state!(…))

# after
pref = CTModels            # the module itself
:($pref.state!(…))

The generator lines are the same. A module value interpolated before a dot is resolved when the macro is defined, not in the caller's module:

julia> m = Module(:Elsewhere); pref = Base;
julia> Core.eval(m, :($pref.sin(1.0)))
0.8414709848078965

Changes:

  1. Add CTModels and ExaModels to [deps] (and LinearAlgebra, a standard library), with their compat. CTModels does not depend on CTParser, so there should be no cycle.
  2. Replace prefix_fun(), prefix_exa() and e_prefix() by three constants, in one place (for example FUN = CTModels, EXA = ExaModels, ERR = CTBase). Remove PREFIX_FUN, PREFIX_EXA, E_PREFIX, their getters and setters, __default_prefix_*, __default_e_prefix, their docstrings and test/test_prefix.jl.
  3. Move the methods of the extension CTParserExaModels into the main module, since ExaModels is no longer optional.
  4. Document the removal of prefix_fun!, prefix_exa! and e_prefix! in the changelog: it is a change of the public API.

Effects:

  • import OptimalControl and using OptimalControl: @def work.
  • The compat ExaModels = "0.12", which the generated code already requires, is enforced.
  • OptimalControl no longer needs to re-export CTModels, ExaModels and CTBase for the generated code (see the issue below).
  • ExaModels is loaded with CTParser, including for users of the functional backend only. It is a dependency of OptimalControl already.

Alternative, without new dependencies

If CTParser should not depend on CTModels and ExaModels, keep the prefixes, but let them hold a Module instead of a Symbol (Ref{Any}), and let OptimalControl set them once, in its __init__:

function __init__()
    CTParser.prefix_fun!(CTModels)
    CTParser.prefix_exa!(ExaModels)
end

The generated code then contains the modules, and the generators do not change. Today, this fails at the first step: CTParser.prefix_fun!(CTModels) raises Cannot convert an object of type Module to an object of type Symbol, since the Ref is typed. Drawbacks: a global state in CTParser, which depends on the loading order if CTParser is used without OptimalControl, and CTParser keeps its optional dependencies and the extension.

Test

In a fresh module, with import OptimalControl only (or import CTParser and the packages loaded), check that @def builds a problem, for the functional backend and for :exa, and in a module that defines its own CTModels binding, that the result is the same.

Lingua principale
Julia
Stelle
3
Fork
0
Merge medio
1g 32m
PR unite (30g)
18

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di control-toolbox/CTParser.jl

Tutte le issue di control-toolbox/CTParser.jl

Issue simili

Altre issue su Julia

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.