From 10236840419ca421b0068342cd57dc64f0c706bf Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Mon, 18 Aug 2025 19:12:30 +0000 Subject: [PATCH 1/5] Add non-allocating `get` --- src/ScopedValues.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ScopedValues.jl b/src/ScopedValues.jl index 1cafbe3..15c6f06 100644 --- a/src/ScopedValues.jl +++ b/src/ScopedValues.jl @@ -251,6 +251,20 @@ struct ScopedFunctor{F} end (sf::ScopedFunctor)() = @enter_scope sf.scope sf.f() +""" + get(val::ScopedValue{T}, default::T)::T + +Like the single-argument [`ScopedValues.get`](@ref), but returns the +provided `default` rather than the default in `val` and does not wrap +the return in `Some` (to save the allocation). +""" +function get(val::ScopedValue{T}, default::T) where {T} + scope = current_scope()::Union{Nothing, Scope} + scope === nothing && return default + scope = scope::Scope + return Base.get(scope.values, val, default) +end + @deprecate scoped with end # module ScopedValues From 28c377b58f4cbe5e2567270d8f2c9c894d74b63e Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Tue, 19 Aug 2025 17:04:16 -0400 Subject: [PATCH 2/5] Allow `default` to have a different type Without requiring the ScopedValue to have a Union type. --- src/ScopedValues.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ScopedValues.jl b/src/ScopedValues.jl index 15c6f06..091035b 100644 --- a/src/ScopedValues.jl +++ b/src/ScopedValues.jl @@ -252,14 +252,14 @@ end (sf::ScopedFunctor)() = @enter_scope sf.scope sf.f() """ - get(val::ScopedValue{T}, default::T)::T + get(val::ScopedValue{T1}, default::T2)::Union{T1, T2} Like the single-argument [`ScopedValues.get`](@ref), but returns the provided `default` rather than the default in `val` and does not wrap the return in `Some` (to save the allocation). """ -function get(val::ScopedValue{T}, default::T) where {T} - scope = current_scope()::Union{Nothing, Scope} +function get(val::ScopedValue{T1}, default::T2) where {T1, T2} + scope = current_scope() scope === nothing && return default scope = scope::Scope return Base.get(scope.values, val, default) From 4944a41263c21869385ee20f814f0f27383a9640 Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Tue, 19 Aug 2025 17:04:52 -0400 Subject: [PATCH 3/5] Add test --- test/runtests.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 7801d14..3c9510f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -141,3 +141,13 @@ end end sf() end + +@testset "get with default" begin + const sval_unassigned = ScopedValue{Int}() + @test ScopedValues.get(sval_unassigned, nothing) == nothing + @test ScopedValues.get(sval_unassigned, -1) == -1 + @with sval_unassigned=>10 begin + @test ScopedValues.get(sval_unassigned, -1) == 10 + end + @test 0 == @allocations ScopedValues.get(sval_unassigned, nothing) +end From 28ca05a4222857ac194261bc4d434d545211b6b1 Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Tue, 19 Aug 2025 17:06:37 -0400 Subject: [PATCH 4/5] Fix test --- test/runtests.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 3c9510f..6a7b90b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -142,12 +142,14 @@ end sf() end +const sval_unassigned = ScopedValue{Int}() @testset "get with default" begin - const sval_unassigned = ScopedValue{Int}() @test ScopedValues.get(sval_unassigned, nothing) == nothing @test ScopedValues.get(sval_unassigned, -1) == -1 @with sval_unassigned=>10 begin @test ScopedValues.get(sval_unassigned, -1) == 10 end + @static if VERSION >= v"1.9" @test 0 == @allocations ScopedValues.get(sval_unassigned, nothing) + end end From 6026aabf6e134d397dff65d61febfdd01a2908d1 Mon Sep 17 00:00:00 2001 From: K Pamnany Date: Wed, 20 Aug 2025 11:22:46 -0400 Subject: [PATCH 5/5] Address review comment --- src/ScopedValues.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ScopedValues.jl b/src/ScopedValues.jl index 091035b..4151190 100644 --- a/src/ScopedValues.jl +++ b/src/ScopedValues.jl @@ -255,14 +255,14 @@ end get(val::ScopedValue{T1}, default::T2)::Union{T1, T2} Like the single-argument [`ScopedValues.get`](@ref), but returns the -provided `default` rather than the default in `val` and does not wrap -the return in `Some` (to save the allocation). +provided `default` (rather than `nothing`) if `val` has no default. +Also, does not wrap the return in `Some`. """ function get(val::ScopedValue{T1}, default::T2) where {T1, T2} scope = current_scope() - scope === nothing && return default + scope === nothing && return isassigned(val) ? val.default : default scope = scope::Scope - return Base.get(scope.values, val, default) + return Base.get(scope.values, val, isassigned(val) ? val.default : default) end @deprecate scoped with