Skip to content
Draft
Show file tree
Hide file tree
Changes from 16 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
3 changes: 3 additions & 0 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
- min # Project's oldest supported version
- lts # Long-Term Stable
- 1 # Latest release
# Versions to validate backwards compatibility logic
- 1.11
- 1.12

test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
Aqua = "0.8.7"
Compat = "3.9, 4"
ExprTools = "0.1"
julia = "1"
julia = "1.6"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Expand Down
1 change: 1 addition & 0 deletions src/Mocking.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ end

export @patch, @mock, Patch, apply

include("compat.jl")
include("expr.jl")
include("dispatch.jl")
include("debug.jl")
Expand Down
63 changes: 63 additions & 0 deletions src/compat.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const MAX_WORLD_AGE = typemax(UInt)

function delete_method(m::Method)
@static if VERSION >= v"1.12.0"
# On Julia 1.12 deleting a method re-activates the previous version of method
Base.delete_method(m)
else
# The method table associated with the generic function.
mt = Base.get_methodtable(m)

world_age = Base.get_world_counter()
current_method = nothing
old_method = nothing

# The `Core.MethodTable` stores each method as a linked list with the newest method
# definitions occurring first.
def = mt.defs
while def !== nothing
if def.sig == m.sig
if def.min_world == m.primary_world
current_method = def.func
elseif current_method !== nothing
old_method = def.func
break
end
end
def = def.next
end

# When the method table contains 2+ methods for the signature we'll restore the
# previous method definition. Otherwise, we'll just limit the world age for the only
# existing method.
if old_method !== nothing
# Using `primary_world == 1` causes Julia to increase the world counter
replacement_method = deepcopy(old_method)

@static if VERSION >= v"1.11"
@atomic replacement_method.primary_world = 1
@atomic replacement_method.deleted_world = MAX_WORLD_AGE
else
replacement_method.primary_world = 1
replacement_method.deleted_world = MAX_WORLD_AGE
end

# Adding a new method into the function's method table will increase world age
# (requires `primary_world == 1`) and invalidate backedges.
ccall(
:jl_method_table_insert,
Cvoid,
(Any, Any, Any),
mt,
replacement_method,
replacement_method.sig,
)
else
# On Julia versions below 1.12 this simply limits the world age for the
# specified method.
Base.delete_method(m)
end
end

return nothing
end
Loading
Loading