Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ All notable changes to CTBase will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.30.3-beta] - unreleased

### 🐛 Bug Fixes

**`Descriptions.complete` — truncation marker and closest-matches hint** ([#553](https://github.com/control-toolbox/CTBase.jl/issues/553)).
- The candidate list is no longer silently head-truncated: when the catalog has more
descriptions than `max_show`, a `… and N more` line is appended so the `Available`
list is not mistaken for exhaustive.
- The `"Try one of the closest matches:"` hint now actually lists the closest matching
descriptions — previously the computed matches were discarded and the hint was empty.
- The number of candidates shown in error output was raised from 10 to 20.

Both are display-only corrections; the resolution logic of `complete` is unchanged.

### ✅ Compatibility

- **No breaking changes**: only error-message display and the shown-candidate ceiling
changed; `complete`'s resolution behaviour, signatures, and return values are
unchanged. No migration required.

## [0.30.2-beta] - unreleased

### 📚 Documentation
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "CTBase"
uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd"
version = "0.30.2-beta"
version = "0.30.3-beta"
authors = ["Olivier Cots <olivier.cots@irit.fr>", "Jean-Baptiste Caillau <caillau@univ-cotedazur.fr>"]

[deps]
Expand Down
4 changes: 2 additions & 2 deletions src/Descriptions/complete.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function complete(list::Symbol...; descriptions::Tuple{Vararg{Description}})::De
if maximum(table[:, 2]) == 0
# Find similar descriptions for helpful suggestions
similar_descs = _find_similar_descriptions(list, descriptions; max_results=5)
all_candidates = _format_description_candidates(descriptions; max_show=10)
all_candidates = _format_description_candidates(descriptions; max_show=20)

# Build contextual suggestion
suggestion = if !isempty(similar_descs)
Expand All @@ -93,7 +93,7 @@ function complete(list::Symbol...; descriptions::Tuple{Vararg{Description}})::De
throw(
Exceptions.AmbiguousDescription(
list;
candidates=all_candidates,
candidates=isempty(similar_descs) ? all_candidates : similar_descs,
suggestion=suggestion,
context="description completion",
diagnostic=diagnostic,
Expand Down
25 changes: 18 additions & 7 deletions src/Descriptions/similarity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ Format description candidates from a catalog for display in error messages.
- `max_show::Int=5`: Maximum number of descriptions to include in the output

# Returns
- `Vector{String}`: A vector of formatted description strings
- `Vector{String}`: A vector of formatted description strings. If the catalog has more
than `max_show` descriptions, an extra `"… and N more"` marker line is appended so the
list is not mistaken for exhaustive.

# Example

Expand All @@ -116,17 +118,26 @@ julia> CTBase.Descriptions.format_description_candidates(descriptions; max_show=
"(:a, :b)"
"(:a, :c)"
"(:x, :y)"

julia> descriptions = ((:a, :b), (:a, :c), (:x, :y), (:p, :q), (:r, :s), (:t, :u))
julia> CTBase.Descriptions.format_description_candidates(descriptions; max_show=2)
3-element Vector{String}:
"(:a, :b)"
"(:a, :c)"
"… and 4 more"
```
"""
function _format_description_candidates(
descriptions::Tuple{Vararg{Description}}; max_show::Int=5
)::Vector{String}
if isempty(descriptions)
return String[]
end
isempty(descriptions) && return String[]

# Take up to max_show descriptions
to_show = descriptions[1:min(max_show, length(descriptions))]
n = length(descriptions)
formatted = Vector{String}([string(desc) for desc in descriptions[1:min(max_show, n)]])

if n > max_show
push!(formatted, "… and $(n - max_show) more")
end

return Vector{String}([string(desc) for desc in to_show])
return formatted
end
51 changes: 51 additions & 0 deletions test/suite/descriptions/test_complete.jl
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,57 @@ function test_complete()
end
end

Test.@testset "issue 553 - truncation marker and closest matches" begin
# ================================================================
# NON-REGRESSION: issue #553
# 1) candidate list must not be silently truncated - a
# "… and N more" marker is appended when > max_show.
# 2) the "closest matches" hint must actually list the similar
# descriptions (previously discarded, leaving the hint empty).
# ================================================================

# --- Build a catalog of 12, the last two GPU ones (issue scenario) ---
descs = ()
for i in 1:10
descs = Descriptions.add(descs, (:ipopt, Symbol(:cpu_, i)))
end
descs = Descriptions.add(descs, (:gpu, :exact))
descs = Descriptions.add(descs, (:gpu, :krylov))
Test.@test length(descs) == 12

# 1a. Marker appended by the formatter when > max_show
formatted = Descriptions._format_description_candidates(descs; max_show=10)
Test.@test length(formatted) == 11 # 10 shown + marker
Test.@test formatted[end] == "… and 2 more"

# 1b. max_show raised to 20 in complete: catalog of 22 must still
# surface the marker line in the raised exception.
descs22 = ()
for i in 1:22
descs22 = Descriptions.add(descs22, (:ipopt, Symbol(:cpu_, i)))
end
err22 = try
Descriptions.complete(:zzz; descriptions=descs22)
catch e
e
end
Test.@test err22 isa Exceptions.AmbiguousDescription
Test.@test length(err22.candidates) == 21 # 20 shown + marker
Test.@test err22.candidates[end] == "… and 2 more"

# 2. Closest matches are not discarded: candidates == the nearest
# descriptions once similar_descs is non-empty.
err = try
Descriptions.complete(:adnlp, :gpu; descriptions=descs)
catch e
e
end
Test.@test err isa Exceptions.AmbiguousDescription
Test.@test occursin("closest matches", err.suggestion)
Test.@test !isempty(err.candidates)
Test.@test Set(err.candidates) == Set(["(:gpu, :exact)", "(:gpu, :krylov)"])
end

Test.@testset "Diagnostic field verification" begin
# Empty catalog - should have diagnostic
try
Expand Down
21 changes: 14 additions & 7 deletions test/suite/descriptions/test_similarity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,47 +164,54 @@ function test_similarity()
Test.@testset "format_description_candidates - basic" begin
descriptions = ((:a, :b), (:a, :c), (:x, :y), (:p, :q), (:r, :s), (:t, :u))
formatted = Descriptions._format_description_candidates(descriptions)
Test.@test length(formatted) == 5 # default max_show=5
Test.@test length(formatted) == 6 # default max_show=5 + "… and 1 more" marker
Test.@test formatted[1] == "(:a, :b)"
Test.@test formatted[5] == "(:r, :s)"
Test.@test formatted[end] == "… and 1 more"

# Custom max_show
formatted3 = Descriptions._format_description_candidates(
descriptions; max_show=3
)
Test.@test length(formatted3) == 3
Test.@test length(formatted3) == 4 # 3 shown + "… and 3 more" marker
Test.@test formatted3[1] == "(:a, :b)"
Test.@test formatted3[3] == "(:x, :y)"
Test.@test formatted3[end] == "… and 3 more"
end

Test.@testset "format_description_candidates - boundaries" begin
# Exactly max_show descriptions
# Exactly max_show descriptions - no marker
descriptions = ((:a, :b), (:c, :d), (:e, :f), (:g, :h), (:i, :j))
formatted = Descriptions._format_description_candidates(
descriptions; max_show=5
)
Test.@test length(formatted) == 5
Test.@test !(occursin("more", formatted[end]))

# Less than max_show
# Less than max_show - no marker
descriptions2 = ((:a, :b), (:c, :d))
formatted2 = Descriptions._format_description_candidates(
descriptions2; max_show=5
)
Test.@test length(formatted2) == 2

# More than max_show
# More than max_show - marker appended
descriptions3 = ((:a, :b), (:c, :d), (:e, :f), (:g, :h), (:i, :j), (:k, :l))
formatted3 = Descriptions._format_description_candidates(
descriptions3; max_show=3
)
Test.@test length(formatted3) == 3
Test.@test length(formatted3) == 4 # 3 shown + "… and 3 more" marker
Test.@test formatted3[1] == "(:a, :b)"
Test.@test formatted3[3] == "(:e, :f)"
Test.@test formatted3[end] == "… and 3 more"

# max_show=1
formatted4 = Descriptions._format_description_candidates(
descriptions3; max_show=1
)
Test.@test length(formatted4) == 1
Test.@test length(formatted4) == 2 # 1 shown + "… and 5 more" marker
Test.@test formatted4[1] == "(:a, :b)"
Test.@test formatted4[end] == "… and 5 more"
end

Test.@testset "format_description_candidates - edge cases" begin
Expand Down
Loading