diff --git a/BREAKINGS.md b/BREAKINGS.md index 99c8e493..1b2cca48 100644 --- a/BREAKINGS.md +++ b/BREAKINGS.md @@ -2,6 +2,30 @@ 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. +## Breaking changes (0.25.0-beta) + +- **`NotProvided` / `NotProvidedType` removed from `CTBase.Options`.** They now live and are + exported **only** in `CTBase.Core`. `CTBase.Options.NotProvided` and + `CTBase.Options.NotProvidedType` no longer exist, and `using CTBase.Options` no longer + brings these names into scope. + + **Migration:** replace `Options.NotProvided` → `Core.NotProvided` and + `Options.NotProvidedType` → `Core.NotProvidedType` (fully qualified: `CTBase.Core.NotProvided`). + + ```julia + # before + using CTBase.Options + OptionDefinition(name=:x, type=Int, default=NotProvided) + + # after + import CTBase.Core + using CTBase.Options + OptionDefinition(name=:x, type=Int, default=Core.NotProvided) + ``` + +- `NotStored` / `NotStoredType` are unchanged and remain extraction-internal to + `CTBase.Options` (the defining file was renamed `not_provided.jl` → `not_stored.jl`). + ## Non-breaking note (0.24.0-beta) - **Differentiation module**: Added comprehensive AD backend infrastructure for computing gradients diff --git a/CHANGELOGS.md b/CHANGELOGS.md index c34587b5..9313d700 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -5,6 +5,23 @@ 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.25.0-beta] - 2026-06-26 + +### ⚠️ Breaking Changes + +- **NotProvided moved to Core (no re-export)**: `NotProvidedType` and `NotProvided` are now + defined and exported **only** from `CTBase.Core`. They are **no longer available** from + `CTBase.Options` — `CTBase.Options.NotProvided` / `CTBase.Options.NotProvidedType` no longer + exist. Replace any `Options.NotProvided` usage with `Core.NotProvided` (or + `CTBase.Core.NotProvided`). +- `CTBase.Core.NotProvided` is the canonical ecosystem-wide "not provided" sentinel. + +### 🔄 Refactoring + +- `CTBase.Options` now only defines the extraction-internal `NotStored` / `NotStoredType` + sentinels (file renamed `not_provided.jl` → `not_stored.jl`). Internal Options/Strategies + code references `Core.NotProvidedType` directly. + ## [0.24.0-beta] - 2026-06-25 ### ✨ New Features diff --git a/Project.toml b/Project.toml index db0f3896..1ac08ed9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "CTBase" uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd" -version = "0.24.0-beta" +version = "0.25.0-beta" authors = ["Olivier Cots ", "Jean-Baptiste Caillau "] [deps] diff --git a/src/Core/Core.jl b/src/Core/Core.jl index 213949d0..87eba4a8 100644 --- a/src/Core/Core.jl +++ b/src/Core/Core.jl @@ -13,6 +13,7 @@ import DocStringExtensions: TYPEDEF, TYPEDSIGNATURES include("types.jl") include("tags.jl") include("caches.jl") +include("not_provided.jl") include("default.jl") # Private utilities @@ -27,6 +28,7 @@ include("display.jl") # Export public API export ctNumber, matrix2vec, to_out_of_place, make_coerce, @ensure export AbstractTag, AbstractCache +export NotProvided, NotProvidedType export Style, Palette export DEFAULT_PALETTE, MONOCHROME_PALETTE, HIGH_CONTRAST_PALETTE export current_palette, set_palette!, set_color!, reset_palette!, show_palette diff --git a/src/Core/not_provided.jl b/src/Core/not_provided.jl new file mode 100644 index 00000000..8200a1fb --- /dev/null +++ b/src/Core/not_provided.jl @@ -0,0 +1,39 @@ +""" +$(TYPEDEF) + +Singleton type marking the absence of a provided value. + +Ecosystem-wide sentinel for "no default / argument not given". The canonical +value is [`NotProvided`](@ref). + +See also: [`NotProvided`](@ref). +""" +struct NotProvidedType end + +""" + NotProvided + +Singleton instance of [`NotProvidedType`](@ref). + +The canonical "not provided" sentinel used across the control-toolbox ecosystem +(option defaults, optional variable parameters, optional AD backends, …). + +# Example +```julia-repl +julia> using CTBase.Core + +julia> x = NotProvided +NotProvided + +julia> x isa NotProvidedType +true + +julia> x === NotProvided +true +``` + +See also: [`NotProvidedType`](@ref). +""" +const NotProvided = NotProvidedType() + +Base.show(io::IO, ::NotProvidedType) = print(io, "NotProvided") diff --git a/src/Options/Options.jl b/src/Options/Options.jl index 37f37fad..fc55d455 100644 --- a/src/Options/Options.jl +++ b/src/Options/Options.jl @@ -18,14 +18,13 @@ import CTBase.Core import CTBase.Exceptions # Submodules -include(joinpath(@__DIR__, "not_provided.jl")) +include(joinpath(@__DIR__, "not_stored.jl")) include(joinpath(@__DIR__, "option_value.jl")) include(joinpath(@__DIR__, "option_definition.jl")) include(joinpath(@__DIR__, "extraction.jl")) # Public API -export NotProvided, NotProvidedType export OptionValue, OptionDefinition, extract_option, extract_options, extract_raw_options export all_names, aliases export is_user, is_default, is_computed # is_computed works for both OptionValue and OptionDefinition diff --git a/src/Options/extraction.jl b/src/Options/extraction.jl index a68a297b..66dd42c6 100644 --- a/src/Options/extraction.jl +++ b/src/Options/extraction.jl @@ -77,7 +77,7 @@ function extract_option(kwargs::NamedTuple, def::OptionDefinition) end # Not found - check if default is NotProvided - if def.default isa NotProvidedType + if def.default isa Core.NotProvidedType # No default and not provided by user - return NotStored to signal "don't store" return NotStored, kwargs end @@ -223,7 +223,7 @@ builders to use their own defaults. Options with explicit `nothing` values are i opts = (backend = OptionValue(:optimized, :user), show_time = OptionValue(false, :default), minimize = OptionValue(nothing, :default), - optional = OptionValue(NotProvided, :default)) + optional = OptionValue(CTBase.Core.NotProvided, :default)) extract_raw_options(opts) # (backend = :optimized, show_time = false, minimize = nothing) @@ -236,7 +236,7 @@ function extract_raw_options(options::NamedTuple) for (k, v) in pairs(options) val = v isa OptionValue ? v.value : v # Filter out NotProvided values, but keep nothing values - if !(val isa NotProvidedType) + if !(val isa Core.NotProvidedType) raw_opts_dict[k] = val end end diff --git a/src/Options/not_provided.jl b/src/Options/not_provided.jl deleted file mode 100644 index e8ef882b..00000000 --- a/src/Options/not_provided.jl +++ /dev/null @@ -1,98 +0,0 @@ -# NotProvided sentinel type -# -# Singleton type representing the absence of a default value for an option. - -""" -$(TYPEDEF) - -Singleton type representing the absence of a default value for an option. - -This type is used to distinguish between: -- `default = NotProvided`: No default value, option must be provided by user or not stored -- `default = nothing`: The default value is explicitly `nothing` - -# Example -```julia -# Option with no default - won't be stored if not provided -opt1 = OptionDefinition( - name = :minimize, - type = Union{Bool, Nothing}, - default = NotProvided, - description = "Whether to minimize" -) - -# Option with explicit nothing default - will be stored as nothing -opt2 = OptionDefinition( - name = :backend, - type = Union{Nothing, KernelAbstractions.Backend}, - default = nothing, - description = "Execution backend" -) -``` - -See also: `OptionDefinition`, `extract_options` -""" -struct NotProvidedType end - -""" - NotProvided - -Singleton instance of `NotProvidedType`. - -Use this as the default value in `OptionDefinition` to indicate -that an option has no default value and should not be stored if not provided -by the user. - -# Example -```julia-repl -julia> using CTBase.Options - -julia> def = OptionDefinition( - name = :optional_param, - type = Any, - default = NotProvided, - description = "Optional parameter" - ) - -julia> # If user doesn't provide it, it won't be stored -julia> opts, _ = extract_options((other=1,), [def]) -julia> haskey(opts, :optional_param) -false -``` -""" -const NotProvided = NotProvidedType() - -# Pretty printing -Base.show(io::IO, ::NotProvidedType) = print(io, "NotProvided") - -""" -$(TYPEDEF) - -Internal sentinel type used by the option extraction system to signal that an option -should not be stored in the instance. - -This is returned by `extract_option` when an option has `NotProvided` as its -default and was not provided by the user. - -# Note -This type is internal to the Options module and should not be used directly by users. -Use `NotProvided` instead. - -See also: `NotProvided`, `extract_option` -""" -struct NotStoredType end - -""" - NotStored - -Internal singleton instance of `NotStoredType`. - -Used internally by the option extraction system to signal that an option should not -be stored. This is distinct from `nothing` which is a valid option value. - -See also: `NotProvided`, `extract_option` -""" -const NotStored = NotStoredType() - -# Pretty printing -Base.show(io::IO, ::NotStoredType) = print(io, "NotStored") diff --git a/src/Options/not_stored.jl b/src/Options/not_stored.jl new file mode 100644 index 00000000..d63fdf9e --- /dev/null +++ b/src/Options/not_stored.jl @@ -0,0 +1,30 @@ +""" +$(TYPEDEF) + +Internal sentinel type used by the option extraction system to signal that an option +should not be stored in the instance. + +This is returned by `extract_option` when an option has [`CTBase.Core.NotProvided`](@ref) +as its default and was not provided by the user. + +# Note +This type is internal to the Options module and should not be used directly by users. +Use [`CTBase.Core.NotProvided`](@ref) instead. + +See also: [`CTBase.Core.NotProvided`](@ref), [`extract_option`](@ref). +""" +struct NotStoredType end + +""" + NotStored + +Internal singleton instance of [`NotStoredType`](@ref). + +Used internally by the option extraction system to signal that an option should not +be stored. This is distinct from `nothing` which is a valid option value. + +See also: [`CTBase.Core.NotProvided`](@ref), [`extract_option`](@ref). +""" +const NotStored = NotStoredType() + +Base.show(io::IO, ::NotStoredType) = print(io, "NotStored") diff --git a/src/Options/option_definition.jl b/src/Options/option_definition.jl index a6796c20..698ec2ef 100644 --- a/src/Options/option_definition.jl +++ b/src/Options/option_definition.jl @@ -85,7 +85,7 @@ struct OptionDefinition{T} computed::Bool=false, ) where {T} # Validate with custom validator if provided (skip for NotProvided) - if validator !== nothing && !(default isa NotProvidedType) + if validator !== nothing && !(default isa Core.NotProvidedType) try validator(default) catch e @@ -152,7 +152,7 @@ julia> # No default - creates NotProvidedType julia> def3 = OptionDefinition( name = :input_file, type = String, - default = NotProvided, + default = CTBase.Core.NotProvided, description = "Input file path" ) OptionDefinition{NotProvidedType}(...) @@ -270,7 +270,7 @@ julia> using CTBase.Options julia> def = _construct_option_definition( :input_file, String, - NotProvided, + CTBase.Core.NotProvided, "Input file path", (:input,) ) @@ -285,13 +285,13 @@ See also: `OptionDefinition`, `NotProvided`, `is_required` function _construct_option_definition( name::Symbol, type::Type, - default::NotProvidedType, + default::Core.NotProvidedType, description::String, aliases::Tuple{Vararg{Symbol}}, validator::Union{Function,Nothing}, computed::Bool, ) - return OptionDefinition{NotProvidedType}(; + return OptionDefinition{Core.NotProvidedType}(; name=name, type=type, default=default, @@ -543,7 +543,7 @@ Returns `true` when the default value is `NotProvided`. ```julia-repl julia> using CTBase.Options -julia> def = OptionDefinition(name=:input, type=String, default=NotProvided, +julia> def = OptionDefinition(name=:input, type=String, default=CTBase.Core.NotProvided, description="Input file") OptionDefinition{NotProvidedType}(...) @@ -553,7 +553,7 @@ true See also: `has_default`, `default` """ -is_required(def::OptionDefinition) = def.default isa NotProvidedType +is_required(def::OptionDefinition) = def.default isa Core.NotProvidedType """ $(TYPEDSIGNATURES) @@ -579,7 +579,7 @@ true See also: `is_required`, `default` """ -has_default(def::OptionDefinition) = !(def.default isa NotProvidedType) +has_default(def::OptionDefinition) = !(def.default isa Core.NotProvidedType) """ $(TYPEDSIGNATURES) diff --git a/test/suite/options/test_coverage_options.jl b/test/suite/options/test_coverage_options.jl index 4441fc59..004bc11d 100644 --- a/test/suite/options/test_coverage_options.jl +++ b/test/suite/options/test_coverage_options.jl @@ -3,6 +3,7 @@ module TestCoverageOptions using Test: Test import CTBase.Exceptions import CTBase.Options +import CTBase.Core import CTBase.Strategies const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true @@ -43,7 +44,7 @@ function test_coverage_options() Test.@testset "NotProvided display" begin buf = IOBuffer() - show(buf, Options.NotProvided) + show(buf, Core.NotProvided) Test.@test String(take!(buf)) == "NotProvided" end diff --git a/test/suite/options/test_not_provided.jl b/test/suite/options/test_not_provided.jl index 6e25411d..9ed6967e 100644 --- a/test/suite/options/test_not_provided.jl +++ b/test/suite/options/test_not_provided.jl @@ -2,6 +2,7 @@ module TestOptionsNotProvided using Test: Test import CTBase.Options +import CTBase.Core const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true @@ -13,9 +14,9 @@ Test the NotProvided type and its behavior in the option system. function test_not_provided() Test.@testset "NotProvided Type Tests" verbose = VERBOSE showtiming = SHOWTIMING begin Test.@testset "NotProvided Basic Properties" begin - Test.@test Options.NotProvided isa Options.NotProvidedType - Test.@test typeof(Options.NotProvided) == Options.NotProvidedType - Test.@test string(Options.NotProvided) == "NotProvided" + Test.@test Core.NotProvided isa Core.NotProvidedType + Test.@test typeof(Core.NotProvided) == Core.NotProvidedType + Test.@test string(Core.NotProvided) == "NotProvided" end Test.@testset "OptionDefinition with NotProvided" begin @@ -23,12 +24,12 @@ function test_not_provided() def_not_provided = Options.OptionDefinition( name=:optional_param, type=Union{Int,Nothing}, - default=Options.NotProvided, + default=Core.NotProvided, description="Optional parameter", ) - Test.@test Options.default(def_not_provided) === Options.NotProvided - Test.@test Options.default(def_not_provided) isa Options.NotProvidedType + Test.@test Options.default(def_not_provided) === Core.NotProvided + Test.@test Options.default(def_not_provided) isa Core.NotProvidedType # Option with nothing default (different!) def_nothing = Options.OptionDefinition( @@ -39,14 +40,14 @@ function test_not_provided() ) Test.@test Options.default(def_nothing) === nothing - Test.@test !(Options.default(def_nothing) isa Options.NotProvidedType) + Test.@test !(Options.default(def_nothing) isa Core.NotProvidedType) end Test.@testset "extract_option with NotProvided" begin def = Options.OptionDefinition( name=:optional, type=Union{Int,Nothing}, - default=Options.NotProvided, + default=Core.NotProvided, description="Optional", ) @@ -79,7 +80,7 @@ function test_not_provided() Options.OptionDefinition( name=:optional, type=Union{Int,Nothing}, - default=Options.NotProvided, + default=Core.NotProvided, description="Optional", ), Options.OptionDefinition( @@ -104,7 +105,7 @@ function test_not_provided() # Verify NO NotProvidedType in extracted values for (k, v) in pairs(extracted) - Test.@test !(Options.value(v) isa Options.NotProvidedType) + Test.@test !(Options.value(v) isa Core.NotProvidedType) end end @@ -120,7 +121,7 @@ function test_not_provided() Options.OptionDefinition( name=:minimize, type=Union{Bool,Nothing}, - default=Options.NotProvided, + default=Core.NotProvided, description="Minimize with NotProvided", ), ] @@ -168,7 +169,7 @@ function test_not_provided() # Verify NO NotProvidedType in raw values for (k, v) in pairs(stored_options) - Test.@test !(Options.value(v) isa Options.NotProvidedType) + Test.@test !(Options.value(v) isa Core.NotProvidedType) end end @@ -181,7 +182,7 @@ function test_not_provided() minimize=Options.OptionDefinition( name=:minimize, type=Union{Bool,Nothing}, - default=Options.NotProvided, + default=Core.NotProvided, description="Minimize flag", ), backend=Options.OptionDefinition( @@ -202,7 +203,7 @@ function test_not_provided() # Verify NO NotProvidedType in extracted for (k, v) in pairs(extracted) - Test.@test !(v.value isa Options.NotProvidedType) + Test.@test !(v.value isa Core.NotProvidedType) end # Extract raw options (what gets passed to builder) @@ -215,7 +216,7 @@ function test_not_provided() # Verify NO NotProvidedType in raw for (k, v) in pairs(raw) - Test.@test !(v isa Options.NotProvidedType) + Test.@test !(v isa Core.NotProvidedType) end end end diff --git a/test/suite/options/test_option_definition.jl b/test/suite/options/test_option_definition.jl index 7ec1c92f..c862c981 100644 --- a/test/suite/options/test_option_definition.jl +++ b/test/suite/options/test_option_definition.jl @@ -3,6 +3,7 @@ module TestOptionsOptionDefinition using Test: Test import CTBase.Exceptions import CTBase.Options +import CTBase.Core const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true @@ -147,7 +148,7 @@ function test_option_definition() def_required = Options.OptionDefinition( name=:input, type=String, - default=Options.NotProvided, + default=Core.NotProvided, description="Input file", ) def_optional = Options.OptionDefinition( @@ -211,17 +212,17 @@ function test_option_definition() def = Options._construct_option_definition( :input_file, String, - Options.NotProvided, + Core.NotProvided, "Input file path", (:input,), nothing, false, ) - Test.@test def isa Options.OptionDefinition{Options.NotProvidedType} + Test.@test def isa Options.OptionDefinition{Core.NotProvidedType} Test.@test Options.name(def) == :input_file Test.@test Options.type(def) == String - Test.@test Options.default(def) === Options.NotProvided + Test.@test Options.default(def) === Core.NotProvided Test.@test Options.description(def) == "Input file path" Test.@test Options.aliases(def) == (:input,) Test.@test Options.validator(def) === nothing @@ -331,11 +332,11 @@ function test_option_definition() def_not_provided = Options.OptionDefinition( name=:input_file, type=String, - default=Options.NotProvided, + default=Core.NotProvided, description="Input file", ) Test.@test def_not_provided isa - Options.OptionDefinition{Options.NotProvidedType} + Options.OptionDefinition{Core.NotProvidedType} Test.@test Options.type(def_not_provided) == String # type is preserved # Test that public constructor with concrete value creates correct type @@ -423,7 +424,7 @@ function test_option_definition() required_def = Options.OptionDefinition( name=:input, type=String, - default=Options.NotProvided, + default=Core.NotProvided, description="Input file", ) Test.@test Options.has_default(required_def) === false @@ -608,7 +609,7 @@ Test.@testset "Getters and introspection" begin Test.@test Options.has_validator(def) === true required_def = Options.OptionDefinition( - name=:input, type=String, default=Options.NotProvided, description="Input file" + name=:input, type=String, default=Core.NotProvided, description="Input file" ) Test.@test Options.has_default(required_def) === false Test.@test Options.is_required(required_def) === true diff --git a/test/suite/options/test_options.jl b/test/suite/options/test_options.jl index ecf071ba..451c91f8 100644 --- a/test/suite/options/test_options.jl +++ b/test/suite/options/test_options.jl @@ -4,6 +4,7 @@ using Test: Test import CTBase.Exceptions using CTBase: CTBase import CTBase.Options +import CTBase.Core using CTBase.Options # For testing exported symbols const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true @@ -38,7 +39,7 @@ function test_options() # Test exported types Test.@testset "Exported Types" begin - for T in (NotProvidedType, OptionValue, OptionDefinition) + for T in (OptionValue, OptionDefinition) Test.@testset "$(nameof(T))" begin Test.@test isdefined(Options, nameof(T)) Test.@test isdefined(CurrentModule, nameof(T)) @@ -47,14 +48,12 @@ function test_options() end end - # Test exported constants - Test.@testset "Exported Constants" begin - for c in (:NotProvided,) - Test.@testset "$c" begin - Test.@test isdefined(Options, c) - Test.@test isdefined(CurrentModule, c) - end - end + # NotProvided / NotProvidedType live in CTBase.Core, not in Options + Test.@testset "NotProvided moved to Core" begin + Test.@test isdefined(Core, :NotProvided) + Test.@test isdefined(Core, :NotProvidedType) + Test.@test !isdefined(Options, :NotProvided) + Test.@test !isdefined(Options, :NotProvidedType) end # Test exported functions @@ -95,7 +94,7 @@ function test_options() Test.@testset "NotProvided and NotStored" begin Test.@testset "NotProvided display" begin buf = IOBuffer() - show(buf, Options.NotProvided) + show(buf, Core.NotProvided) Test.@test String(take!(buf)) == "NotProvided" end @@ -263,7 +262,7 @@ function test_options() opt_def_no_default = Options.OptionDefinition( name=:required_option, type=Int, - default=Options.NotProvided, + default=Core.NotProvided, description="Required option", ) @@ -316,7 +315,7 @@ function test_options() opt_def_no_default = Options.OptionDefinition( name=:required_option, type=Int, - default=Options.NotProvided, + default=Core.NotProvided, description="Required option", ) options = NamedTuple() @@ -339,7 +338,7 @@ function test_options() Options.OptionDefinition( name=:option3, type=Float64, - default=Options.NotProvided, + default=Core.NotProvided, description="Third option (required)", ), ] diff --git a/test/suite/options/test_options_value.jl b/test/suite/options/test_options_value.jl index 12f05d68..48f299fa 100644 --- a/test/suite/options/test_options_value.jl +++ b/test/suite/options/test_options_value.jl @@ -4,6 +4,7 @@ using Test: Test import CTBase.Exceptions using CTBase: CTBase import CTBase.Options +import CTBase.Core const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true @@ -18,7 +19,6 @@ function test_options_value() Test.@testset "Exports verification" begin Test.@test isdefined(CTBase, :Options) Test.@test isdefined(Options, :OptionValue) - Test.@test isdefined(Options, :NotProvided) Test.@test isdefined(Options, :value) Test.@test isdefined(Options, :source) Test.@test isdefined(Options, :is_user) diff --git a/test/suite/strategies/test_utilities.jl b/test/suite/strategies/test_utilities.jl index b0b5493e..bf6a2fb6 100644 --- a/test/suite/strategies/test_utilities.jl +++ b/test/suite/strategies/test_utilities.jl @@ -3,6 +3,7 @@ module TestStrategiesUtilities using Test: Test import CTBase.Strategies import CTBase.Options +import CTBase.Core const VERBOSE = isdefined(Main, :TestData) ? Main.TestData.VERBOSE : true const SHOWTIMING = isdefined(Main, :TestData) ? Main.TestData.SHOWTIMING : true @@ -372,7 +373,7 @@ function test_utilities() # Create StrategyOptions with NotProvided value opts = Strategies.StrategyOptions( max_iter=Options.OptionValue(500, :user), - optional=Options.OptionValue(Options.NotProvided, :default), + optional=Options.OptionValue(Core.NotProvided, :default), ) # Convert to Dict