Skip to content

Commit 0826d0c

Browse files
ocotsclaude
andcommitted
fix(strategies): shorten parametric struct option values in show output
Generalize `_display_value` to detect any option value whose type is a parametric struct (e.g. an OrdinaryDiffEq algorithm instance) and display just its type name, instead of hardcoding the `:alg` option name. Standard containers (Array, Tuple, NamedTuple, Dict) are excluded and keep showing their full contents. Also fixes a latent bug where a simple Symbol/String `:alg` value would have displayed as "Symbol" instead of its real value. Bump to 0.28.6-beta, update CHANGELOG.md and BREAKING.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 21b4ba6 commit 0826d0c

8 files changed

Lines changed: 174 additions & 5 deletions

File tree

BREAKING.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@
33

44
This document outlines all breaking changes introduced in CTBase v0.18.0-beta compared to v0.17.4. Use this guide to migrate your code and understand the impact of these changes.
55

6+
## Non-breaking note (0.28.6-beta)
7+
8+
- **`Strategies`: option values whose type is a parametric struct are now
9+
displayed by type name only, instead of their full `repr`.** In
10+
`Base.show(io, MIME"text/plain", strategy)`, `Base.show(io, strategy)`,
11+
and the equivalent `StrategyOptions` show methods, a value like an
12+
`OrdinaryDiffEq` algorithm instance (e.g. `Vern6{typeof(trivial_limiter!),
13+
...}(...)`) previously printed its entire type-parameterized `repr`,
14+
making the tree display unreadable. It now prints just the type name
15+
(e.g. `Vern6`). The rule applies to **any** option value whose type is a
16+
parametric `struct` — not just a specific option name — while standard
17+
containers (`AbstractArray`, `Tuple`, `NamedTuple`, `AbstractDict`) are
18+
excluded and keep displaying their full contents. **No breaking change**:
19+
purely a display improvement; no API, type, or constructor changes. No
20+
migration required.
21+
622
## Non-breaking note (0.28.5-beta)
723

824
- **`Data`: new `PseudoHamiltonianVectorField` / `AbstractPseudoHamiltonianVectorField`

CHANGELOG.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,50 @@ All notable changes to CTBase will be documented in this file.
66
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
77
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## [0.28.6-beta] - 2026-07-26
10+
11+
### 🐛 Bug Fixes
12+
13+
#### **Strategies** — display of parametric struct option values
14+
15+
- **`_display_value`** (`Strategies/display_formatting.jl`) now shortens
16+
**any** option value whose type is a parametric `struct` to just its
17+
type name, instead of special-casing the `:alg` option name. Previously,
18+
an option like `alg = Vern6()` (an `OrdinaryDiffEq` algorithm) displayed
19+
its entire type-parameterized `repr` (e.g.
20+
`Vern6{typeof(trivial_limiter!), typeof(trivial_limiter!),
21+
FastBroadcast.Serial, Val{true}}(...)`) in the `show` tree output,
22+
instead of just `Vern6`.
23+
- The new rule is based on the value's type (`isstructtype(T) &&
24+
!isempty(T.parameters)`), not on the option's key name — `CTBase` has no
25+
built-in knowledge of `:alg` or any downstream package's option-naming
26+
convention. Standard containers (`AbstractArray`, `Tuple`, `NamedTuple`,
27+
`AbstractDict`) are excluded and keep displaying their full contents,
28+
since they are themselves parametric structs but their contents (not
29+
their type name) are the useful display.
30+
- Fixes a latent bug in the previous key-based approach: an `:alg` option
31+
holding a simple `Symbol`/`String` value would have displayed the type
32+
name (`"Symbol"`) instead of the actual value.
33+
34+
### 🧪 Testing
35+
36+
- Added `_display_value` unit tests in `test_abstract_strategy.jl`
37+
covering: simple values (`Int`, `Float64`, `Symbol`, `String`, `Bool`,
38+
`Nothing`) unaffected, a parametric struct shortened to its type name,
39+
and standard containers (`Vector`, `Tuple`, `NamedTuple`, `Dict`)
40+
displayed in full.
41+
- Added integration tests in `test_abstract_strategy.jl` and
42+
`test_strategy_options.jl` verifying that `show` (both the verbose
43+
`MIME"text/plain"` form and the compact form) shortens a
44+
`FakeParametricAlgorithm{A,B}` option value to its type name without
45+
leaking field values or type parameters.
46+
- Full suite green: **4907/4907**
47+
48+
### ✅ Compatibility
49+
50+
- **No breaking changes**: purely a display fix; no API, type, or
51+
constructor changes. See [BREAKING.md](BREAKING.md).
52+
953
## [0.28.5-beta] - 2026-07-24
1054

1155
### ✨ New Features

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "CTBase"
22
uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd"
3-
version = "0.28.5-beta"
3+
version = "0.28.6-beta"
44
authors = ["Olivier Cots <olivier.cots@irit.fr>", "Jean-Baptiste Caillau <caillau@univ-cotedazur.fr>"]
55

66
[deps]

src/Strategies/contract/abstract_strategy.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ function Base.show(io::IO, ::MIME"text/plain", strategy::T) where {T<:AbstractSt
512512
fmt.reset,
513513
" = ",
514514
fmt.value,
515-
Options.value(opt),
515+
_display_value(Options.value(opt)),
516516
fmt.reset,
517517
" [",
518518
fmt.label,
@@ -568,7 +568,7 @@ function Base.show(io::IO, strategy::T) where {T<:AbstractStrategy}
568568
fmt.reset *
569569
"=" *
570570
fmt.value *
571-
"$(Options.value(v))" *
571+
_display_value(Options.value(v)) *
572572
fmt.reset for (k, v) in pairs(opts.options)
573573
),
574574
", ",

src/Strategies/contract/strategy_options.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ function Base.show(io::IO, ::MIME"text/plain", opts::StrategyOptions)
722722
fmt.reset,
723723
" = ",
724724
fmt.value,
725-
Options.value(opt),
725+
_display_value(Options.value(opt)),
726726
fmt.reset,
727727
" [",
728728
fmt.label,
@@ -762,7 +762,7 @@ function Base.show(io::IO, opts::StrategyOptions)
762762
fmt.reset *
763763
"=" *
764764
fmt.value *
765-
"$(Options.value(v))" *
765+
_display_value(Options.value(v)) *
766766
fmt.reset for (k, v) in pairs(_raw_options(opts))
767767
),
768768
", ",

src/Strategies/display_formatting.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,31 @@ function _print_labeled_multiline(
3939
end
4040
end
4141
end
42+
43+
"""
44+
$(TYPEDSIGNATURES)
45+
46+
Format an option value for display, shortening verbose parametric struct instances.
47+
48+
Values whose type is a parametric struct (e.g. an ODE algorithm such as
49+
`Tsit5{...}`) are displayed as just the type name (e.g. `"Tsit5"`) instead of the
50+
full `repr`, which would otherwise include type parameters and field values.
51+
Standard containers (`AbstractArray`, `Tuple`, `NamedTuple`, `AbstractDict`) are
52+
excluded from this shortening since their contents are the useful information to
53+
display.
54+
55+
# Arguments
56+
- `value`: Option value
57+
58+
# Returns
59+
- `String`: Display representation of the value
60+
"""
61+
function _display_value(value)
62+
T = typeof(value)
63+
if isstructtype(T) &&
64+
!isempty(T.parameters) &&
65+
!(value isa Union{AbstractArray,Tuple,NamedTuple,AbstractDict})
66+
return string(nameof(T))
67+
end
68+
return string(value)
69+
end

test/suite/strategies/test_abstract_strategy.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ function Strategies.description(::Type{<:FakeStrategyWithDescription})
6969
return "A strategy for testing description display.\nSee: https://example.com"
7070
end
7171

72+
# Fake parametric struct simulating a verbose algorithm type (e.g. an ODE solver
73+
# algorithm from OrdinaryDiffEq), used to test `_display_value` shortening.
74+
struct FakeParametricAlgorithm{A,B}
75+
a::A
76+
b::B
77+
end
78+
7279
# ============================================================================
7380
# Test function
7481
# ============================================================================
@@ -284,6 +291,32 @@ function test_abstract_strategy()
284291
end
285292
end
286293

294+
Test.@testset "_display_value helper" begin
295+
Test.@testset "simple values are unaffected" begin
296+
Test.@test Strategies._display_value(200) == "200"
297+
Test.@test Strategies._display_value(1.0e-8) == "1.0e-8"
298+
Test.@test Strategies._display_value(:sparse) == "sparse"
299+
Test.@test Strategies._display_value("hello") == "hello"
300+
Test.@test Strategies._display_value(true) == "true"
301+
Test.@test Strategies._display_value(nothing) == "nothing"
302+
end
303+
304+
Test.@testset "parametric struct is shortened to type name" begin
305+
value = FakeParametricAlgorithm(1, "x")
306+
Test.@test Strategies._display_value(value) == "FakeParametricAlgorithm"
307+
# The shortened form must not leak field values or type parameters
308+
Test.@test !occursin("\"x\"", Strategies._display_value(value))
309+
Test.@test !occursin("{", Strategies._display_value(value))
310+
end
311+
312+
Test.@testset "standard containers are displayed in full" begin
313+
Test.@test Strategies._display_value([1, 2, 3]) == string([1, 2, 3])
314+
Test.@test Strategies._display_value((1, 2)) == string((1, 2))
315+
Test.@test Strategies._display_value((; a=1)) == string((; a=1))
316+
Test.@test Strategies._display_value(Dict(:a => 1)) == string(Dict(:a => 1))
317+
end
318+
end
319+
287320
# ========================================================================
288321
# INTEGRATION TESTS
289322
# ========================================================================
@@ -333,6 +366,27 @@ function test_abstract_strategy()
333366
Test.@test_nowarn show(stdout, Strategies.options(strategy))
334367
end
335368
end
369+
370+
Test.@testset "Strategy display shortens parametric struct option values" begin
371+
opts = Strategies.StrategyOptions(;
372+
alg=Options.OptionValue(FakeParametricAlgorithm(1, "x"), :user)
373+
)
374+
strategy = FakeStrategy(opts)
375+
376+
io = IOBuffer()
377+
show(io, MIME("text/plain"), strategy)
378+
verbose_output = String(take!(io))
379+
Test.@test occursin("FakeParametricAlgorithm", verbose_output)
380+
Test.@test !occursin("\"x\"", verbose_output)
381+
Test.@test !occursin("{", verbose_output)
382+
383+
io = IOBuffer()
384+
show(io, strategy)
385+
compact_output = String(take!(io))
386+
Test.@test occursin("FakeParametricAlgorithm", compact_output)
387+
Test.@test !occursin("\"x\"", compact_output)
388+
Test.@test !occursin("{", compact_output)
389+
end
336390
end
337391
end
338392
end

test/suite/strategies/test_strategy_options.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import CTBase.Options
88
const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true
99
const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true
1010

11+
# Fake parametric struct simulating a verbose algorithm type (e.g. an ODE solver
12+
# algorithm from OrdinaryDiffEq), used to test that display shortens such values.
13+
struct FakeParametricAlgorithm{A,B}
14+
a::A
15+
b::B
16+
end
17+
1118
# ============================================================================
1219
# Test function
1320
# ============================================================================
@@ -258,6 +265,26 @@ function test_strategy_options()
258265
Test.@test occursin("computed", output)
259266
end
260267

268+
Test.@testset "Display shortens parametric struct option values" begin
269+
opts = Strategies.StrategyOptions(;
270+
alg=Options.OptionValue(FakeParametricAlgorithm(1, "x"), :user)
271+
)
272+
273+
io = IOBuffer()
274+
show(io, MIME"text/plain"(), opts)
275+
verbose_output = String(take!(io))
276+
Test.@test occursin("FakeParametricAlgorithm", verbose_output)
277+
Test.@test !occursin("\"x\"", verbose_output)
278+
Test.@test !occursin("{", verbose_output)
279+
280+
io = IOBuffer()
281+
show(io, opts)
282+
compact_output = String(take!(io))
283+
Test.@test occursin("FakeParametricAlgorithm", compact_output)
284+
Test.@test !occursin("\"x\"", compact_output)
285+
Test.@test !occursin("{", compact_output)
286+
end
287+
261288
Test.@testset "Integration with OptionDefinition" begin
262289
# Create OptionDefinition
263290
opt_def = Options.OptionDefinition(

0 commit comments

Comments
 (0)