From 1f1f11086da9e2e41a0e7bb301e60163ff89945c Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Wed, 29 Jul 2026 15:44:55 -0400 Subject: [PATCH 1/2] Use public APIs for FunctionMap keyword validation Co-Authored-By: Chris Rackauckas --- lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl | 11 +++++------ .../test/discrete_algorithm_test.jl | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl b/lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl index 4d4ace0e95b..00dea4ddd85 100644 --- a/lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl +++ b/lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl @@ -17,16 +17,15 @@ FunctionMap{scale_by_time}() where {scale_by_time} = FunctionMap{scale_by_time, trivial_limiter! ) function FunctionMap(; scale_by_time = false, step_limiter = trivial_limiter!, kwargs...) - kwargs_nt = values(kwargs) old_kw = Symbol("step_limiter!") - if haskey(kwargs_nt, old_kw) + if haskey(kwargs, old_kw) if step_limiter === trivial_limiter! - step_limiter = get(kwargs_nt, old_kw, trivial_limiter!) + step_limiter = get(kwargs, old_kw, trivial_limiter!) end end - extra_kwargs = Base.structdiff(kwargs_nt, NamedTuple{(old_kw,)}) - if !isempty(extra_kwargs) - throw(ArgumentError("Unsupported keyword argument(s): $(keys(extra_kwargs))")) + unsupported_kwargs = filter(!=(old_kw), keys(kwargs)) + if !isempty(unsupported_kwargs) + throw(ArgumentError("Unsupported keyword argument(s): $(unsupported_kwargs)")) end return FunctionMap{scale_by_time, typeof(step_limiter)}(step_limiter) diff --git a/lib/OrdinaryDiffEqFunctionMap/test/discrete_algorithm_test.jl b/lib/OrdinaryDiffEqFunctionMap/test/discrete_algorithm_test.jl index ce63f45703d..99c154f3f5a 100644 --- a/lib/OrdinaryDiffEqFunctionMap/test/discrete_algorithm_test.jl +++ b/lib/OrdinaryDiffEqFunctionMap/test/discrete_algorithm_test.jl @@ -63,3 +63,17 @@ end @test sol3.u == [[0, 0], [1, 1], [2, 3], [3, 6], [4, 10], [5, 15]] end + +@testset "FunctionMap keyword validation" begin + current_limiter!(u, integrator, p, t) = nothing + legacy_limiter!(u, integrator, p, t) = nothing + + @test FunctionMap(step_limiter = current_limiter!).step_limiter! === current_limiter! + @test FunctionMap(; step_limiter! = legacy_limiter!).step_limiter! === legacy_limiter! + @test_throws ArgumentError("Unsupported keyword argument(s): (:unsupported,)") FunctionMap( + unsupported = true + ) + @test_throws ArgumentError( + "Unsupported keyword argument(s): (:unsupported, :also_unsupported)" + ) FunctionMap(unsupported = true, also_unsupported = true) +end From 6b4b7e372aa0ff86950056c414c53ec57a7819ef Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Wed, 29 Jul 2026 18:07:51 -0400 Subject: [PATCH 2/2] Allow Base.structdiff in FunctionMap QA Co-Authored-By: Chris Rackauckas --- lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl | 11 ++++++----- lib/OrdinaryDiffEqFunctionMap/test/qa/qa.jl | 9 +++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl b/lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl index 00dea4ddd85..4d4ace0e95b 100644 --- a/lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl +++ b/lib/OrdinaryDiffEqFunctionMap/src/algorithms.jl @@ -17,15 +17,16 @@ FunctionMap{scale_by_time}() where {scale_by_time} = FunctionMap{scale_by_time, trivial_limiter! ) function FunctionMap(; scale_by_time = false, step_limiter = trivial_limiter!, kwargs...) + kwargs_nt = values(kwargs) old_kw = Symbol("step_limiter!") - if haskey(kwargs, old_kw) + if haskey(kwargs_nt, old_kw) if step_limiter === trivial_limiter! - step_limiter = get(kwargs, old_kw, trivial_limiter!) + step_limiter = get(kwargs_nt, old_kw, trivial_limiter!) end end - unsupported_kwargs = filter(!=(old_kw), keys(kwargs)) - if !isempty(unsupported_kwargs) - throw(ArgumentError("Unsupported keyword argument(s): $(unsupported_kwargs)")) + extra_kwargs = Base.structdiff(kwargs_nt, NamedTuple{(old_kw,)}) + if !isempty(extra_kwargs) + throw(ArgumentError("Unsupported keyword argument(s): $(keys(extra_kwargs))")) end return FunctionMap{scale_by_time, typeof(step_limiter)}(step_limiter) diff --git a/lib/OrdinaryDiffEqFunctionMap/test/qa/qa.jl b/lib/OrdinaryDiffEqFunctionMap/test/qa/qa.jl index 5ec4e434dae..40e68e930bc 100644 --- a/lib/OrdinaryDiffEqFunctionMap/test/qa/qa.jl +++ b/lib/OrdinaryDiffEqFunctionMap/test/qa/qa.jl @@ -12,9 +12,14 @@ run_qa( # OrdinaryDiffEqCore owner-internal no-op limiter (deliberately not public). ignore = (:trivial_limiter!,), ), - # SciMLBase-owned default-solve sentinels; non-public in SciMLBase. all_qualified_accesses_are_public = (; - ignore = (:DISCRETE_INPLACE_DEFAULT, :DISCRETE_OUTOFPLACE_DEFAULT), + ignore = ( + # SciMLBase-owned default-solve sentinels; non-public in SciMLBase. + :DISCRETE_INPLACE_DEFAULT, + :DISCRETE_OUTOFPLACE_DEFAULT, + # Preserves the statically known NamedTuple type after field removal. + :structdiff, + ), ), ), )