Skip to content

Autobox Python tuple -> Julia Tuple#13

Merged
jack-champagne merged 2 commits into
masterfrom
tuple-autobox
Jul 2, 2026
Merged

Autobox Python tuple -> Julia Tuple#13
jack-champagne merged 2 commits into
masterfrom
tuple-autobox

Conversation

@jack-champagne

Copy link
Copy Markdown
Member

Closes #12.

box_python_value handled bool/int/float/complex/str/None but not tuple, so a
Python tuple passed as an arg or kwarg value raised
TypeError: Cannot auto-convert tuple. This blocked tuple-valued Julia kwargs —
notably pypiccolo's SmoothPulseProblem(..., Δt_bounds=(0.1, 0.5)) and the
minimum_time demo.

Fix: recursively box each element (via auto_convert_arg, so nested tuples /
JuliaVal elements work) and build a Julia Tuple with new C jl2py_box_tuple
(Core.tuple(elts...), elements rooted across the constructing call). Output side
(Julia Tuple -> Python) already worked via the JuliaTuple wrapper.

Tests: tests/test_tuple_boxing.py — tuple identity, element access,
(0.1,0.5) -> Tuple{Float64,Float64}, tuple-as-kwarg-value, empty + nested.
Full suite: 85 passed, 3 skipped (Julia 1.12.6).

End-to-end validation against a real Piccolissimo bundle: with this change,
pypiccolo SmoothPulseProblem(qtraj, N, Q=100.0, R=1e-2, Δt_bounds=(0.1, 0.5))
now builds and evaluates (previously TypeError) — the exact pattern the
minimum_time demo needs.

🤖 Generated with Claude Code

- box_python_value: match type(val) is tuple exactly, not isinstance.
  A collections.namedtuple is a tuple subclass; isinstance matched it
  and silently boxed it as a plain positional Julia Tuple, dropping
  its field names. Now it falls through to the existing TypeError.
- jl2py_box_tuple: check jl_exception_occurred() after jl_call and
  clear it, matching every other jl_call site in this codebase.
  Core.tuple is normally total, but an unhandled exception here would
  otherwise leak into whatever unrelated jl2py_call* runs next.
- jl2py_box_tuple: cache the Core.tuple function pointer in a static
  instead of re-resolving it on every call, matching getproperty_fn's
  existing pattern in this file.

Added a regression test for the namedtuple case; existing suite
unaffected (86 passed, 3 skipped, up from 85/3 baseline).
@jack-champagne

Copy link
Copy Markdown
Member Author

Pushed a follow-up commit addressing review feedback on the tuple-boxing code itself:

  • box_python_value: match type(val) is tuple exactly instead of isinstance, so a collections.namedtuple (a tuple subclass) keeps raising TypeError instead of silently losing its field names as a flattened positional Tuple.
  • jl2py_box_tuple: check/clear jl_exception_occurred() after jl_call, matching every other jl_call site in this codebase (previously the only one that didn't).
  • jl2py_box_tuple: cache the Core.tuple function pointer in a static instead of re-resolving it on every call, matching getproperty_fn's existing pattern in the same file.

Added a namedtuple regression test; full suite is 86 passed / 3 skipped (up from 85/3).

Separately opened #14 for a GC-rooting issue in JuliaVal.__call__ and a few container call sites that predates this PR (multiple freshly-boxed values sitting unrooted in a Python list before a single consuming call) — out of scope for this PR since it's not tuple-specific, but worth a look independently.

@jack-champagne jack-champagne merged commit 7805c7e into master Jul 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Autobox Python tuple -> Julia Tuple (arg/kwarg values)

1 participant