From e14057677704843072ebba5857c42c7706b4b8ad Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 3 Oct 2025 18:48:05 +0000 Subject: [PATCH 1/2] Fix compatibility with Julia 1.13+ memhash removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove hash method definition when Base.memhash is not available. On Julia 1.13+, these AbstractString types will use the default AbstractString hash implementation which is now efficient and zero-copy based on codeunit/iterate. For Julia <1.13, continue using the memhash-based implementation for compatibility. Related to JuliaLang/julia#59697 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/poslenstrings.jl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/poslenstrings.jl b/src/poslenstrings.jl index a8220c0..3913897 100644 --- a/src/poslenstrings.jl +++ b/src/poslenstrings.jl @@ -94,16 +94,18 @@ function Base.cmp(a::PosLenString, b::PosLenString) return cmp(codeunits(a), codeunits(b)) end -function Base.hash(s::PosLenString, h::UInt) - h += Base.memhash_seed - if !escaped(s) - ccall(Base.memhash, UInt, (Ptr{UInt8}, Csize_t, UInt32), pointer(s), len(s), h % UInt32) + h - else - # TODO: this is expensive, even for rare escaped PosLenString - # this makes it about 4x slower than hash(::String) - # alternative is to maybe take what's needed from MurmurHash3.jl to operate by codeunit - x = copy(codeunits(s)) - ccall(Base.memhash, UInt, (Ptr{UInt8}, Csize_t, UInt32), pointer(x), sizeof(x), h % UInt32) + h +if isdefined(Base, :memhash) + function Base.hash(s::PosLenString, h::UInt) + h += Base.memhash_seed + if !escaped(s) + ccall(Base.memhash, UInt, (Ptr{UInt8}, Csize_t, UInt32), pointer(s), len(s), h % UInt32) + h + else + # TODO: this is expensive, even for rare escaped PosLenString + # this makes it about 4x slower than hash(::String) + # alternative is to maybe take what's needed from MurmurHash3.jl to operate by codeunit + x = copy(codeunits(s)) + ccall(Base.memhash, UInt, (Ptr{UInt8}, Csize_t, UInt32), pointer(x), sizeof(x), h % UInt32) + h + end end end From 23177a9749cec55780052a2a28c42ee30e510db1 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Thu, 23 Apr 2026 11:49:25 -0600 Subject: [PATCH 2/2] fix(hash): finish memhash compatibility Cover the remaining nightly compatibility gap in WeakRefString hashing. - keep the old memhash fast path only when both Base hooks still exist - add a safe fallback hash for newer Julia versions and regressions for it - update actions/cache to v4 so GitHub Actions runs again - bump the project version to 1.4.3 for the release cut --- .github/workflows/ci.yml | 2 +- Project.toml | 2 +- src/WeakRefStrings.jl | 13 ++++++++++--- src/poslenstrings.jl | 2 +- test/runtests.jl | 15 +++++++++++++++ 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 086ac76..f34b956 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 + - uses: actions/cache@v4 env: cache-name: cache-artifacts with: diff --git a/Project.toml b/Project.toml index fd74038..4ffb702 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "WeakRefStrings" uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" authors = ["quinnj "] -version = "1.4.2" +version = "1.4.3" [deps] DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" diff --git a/src/WeakRefStrings.jl b/src/WeakRefStrings.jl index 12def29..a0571b7 100644 --- a/src/WeakRefStrings.jl +++ b/src/WeakRefStrings.jl @@ -58,9 +58,16 @@ function Base.cmp(a::WeakRefString{T}, b::WeakRefString{T}) where T return c < 0 ? -1 : c > 0 ? +1 : cmp(al,bl) end -function Base.hash(s::WeakRefString{T}, h::UInt) where {T} - h += Base.memhash_seed - ccall(Base.memhash, UInt, (Ptr{T}, Csize_t, UInt32), s.ptr, s.len, h % UInt32) + h +if isdefined(Base, :memhash) && isdefined(Base, :memhash_seed) + function Base.hash(s::WeakRefString{T}, h::UInt) where {T} + h += Base.memhash_seed + ccall(Base.memhash, UInt, (Ptr{T}, Csize_t, UInt32), s.ptr, s.len, h % UInt32) + h + end +elseif isdefined(Base, :hash_bytes) && isdefined(Base, :HASH_SECRET) + Base.hash(s::WeakRefString{UInt8}, h::UInt) = Base.hash_bytes(s.ptr, s.len, UInt64(h), Base.HASH_SECRET) % UInt + Base.hash(s::WeakRefString, h::UInt) = hash(String(s), h) +else + Base.hash(s::WeakRefString, h::UInt) = hash(String(s), h) end function Base.show(io::IO, x::WeakRefString{T}) where {T} diff --git a/src/poslenstrings.jl b/src/poslenstrings.jl index 3913897..afc436e 100644 --- a/src/poslenstrings.jl +++ b/src/poslenstrings.jl @@ -94,7 +94,7 @@ function Base.cmp(a::PosLenString, b::PosLenString) return cmp(codeunits(a), codeunits(b)) end -if isdefined(Base, :memhash) +if isdefined(Base, :memhash) && isdefined(Base, :memhash_seed) function Base.hash(s::PosLenString, h::UInt) h += Base.memhash_seed if !escaped(s) diff --git a/test/runtests.jl b/test/runtests.jl index abbe0c6..efbc4fe 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,6 +15,11 @@ include("poslenstrings.jl") @test str[1] === 'h' @test str[2] === 'e' @test str[3] === 'y' + @test hash(str) isa UInt + @test hash(str, UInt(0x1234)) isa UInt + if !isdefined(Base, :memhash_seed) + @test hash(str, UInt(0x1234)) == hash("hey", UInt(0x1234)) + end io = IOBuffer() show(io, str) @@ -32,6 +37,11 @@ end @test typeof(str) == WeakRefStrings.WeakRefString{UInt16} @test String(str) == "hey" @test str.len == 3 + @test hash(str) isa UInt + @test hash(str, UInt(0x1234)) isa UInt + if !isdefined(Base, :memhash_seed) + @test hash(str, UInt(0x1234)) == hash("hey", UInt(0x1234)) + end end @testset "WeakRefString{UInt32}" begin @@ -42,6 +52,11 @@ end @test typeof(str) == WeakRefStrings.WeakRefString{UInt32} @test String(str) == "hey" @test str.len == 4 + @test hash(str) isa UInt + @test hash(str, UInt(0x1234)) isa UInt + if !isdefined(Base, :memhash_seed) + @test hash(str, UInt(0x1234)) == hash("hey", UInt(0x1234)) + end end @testset "StringVector" begin