Skip to content

Commit

Permalink
Compare from functions signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
Keluaa committed Apr 19, 2024
1 parent 9576bfb commit be93b5f
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 8 deletions.
5 changes: 4 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ authors = ["Luc Briand <[email protected]> and contributo
version = "1.0.0-DEV"

[deps]
CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
Expand All @@ -13,6 +14,7 @@ WidthLimitedIO = "b8c1c048-cf81-46c6-9da0-18c1d99e41f2"

[compat]
Aqua = "0.7"
CodeTracking = "1"
DeepDiffs = "1"
InteractiveUtils = "1"
MacroTools = "0.5"
Expand All @@ -29,7 +31,8 @@ DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
OhMyREPL = "5fb14364-9ced-5910-84b2-373655c76a03"
ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Aqua", "InteractiveUtils", "OhMyREPL", "ReferenceTests", "Test"]
test = ["Aqua", "InteractiveUtils", "OhMyREPL", "ReferenceTests", "Revise", "Test"]
4 changes: 2 additions & 2 deletions src/CodeDiffs.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module CodeDiffs

# TODO: option to ignore differences in code comments (such as when comparing methods in different worlds)
# TODO: add `using CodeTracking: definition`, then do like `Cthuhlu.jl` to retrive the function def from its call: https://github.com/JuliaDebug/Cthulhu.jl/blob/9ba8bfc53efed453cb150c9f3e4c279521c5cb17/src/codeview.jl#L54C9-L54C33
# TODO: GPU assembly / LLVM IR support
# TODO: explain in the docs how to interface with this package

using CodeTracking
using DeepDiffs
using InteractiveUtils
using MacroTools
Expand All @@ -16,6 +15,7 @@ export @code_diff

const ANSI_REGEX = r"(?>\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))+"
const OhMYREPL_PKG_ID = Base.PkgId(Base.UUID("5fb14364-9ced-5910-84b2-373655c76a03"), "OhMyREPL")
const Revise_PKG_ID = Base.PkgId(Base.UUID("295af30f-e4ad-537b-8983-00126c2a3abe"), "Revise")

include("CodeDiff.jl")
include("compare.jl")
Expand Down
68 changes: 68 additions & 0 deletions src/compare.jl
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ end


function method_instance(sig, world)
@nospecialize(sig)
@static if VERSION < v"1.10"
mth_match = Base._which(sig, world)
else
Expand All @@ -224,6 +225,12 @@ function method_instance(sig, world)
end


function method_instance(f, types, world)
@nospecialize(f, types)
return method_instance(Base.signature_type(f, types), world)

Check warning on line 230 in src/compare.jl

View check run for this annotation

Codecov / codecov/patch

src/compare.jl#L228-L230

Added lines #L228 - L230 were not covered by tests
end


"""
compare_code_native(
f::Base.Callable, types::Type{<:Tuple}, world₁, world₂;
Expand Down Expand Up @@ -500,13 +507,73 @@ function compare_ast(code₁::Markdown.MD, code₂::Markdown.MD; color=true)
return compare_show(code₁, code₂; color, force_no_ansi=true)
end


function compare_ast(code₁::AbstractString, code₂::AbstractString; color=true)
code_md₁ = Markdown.MD(Markdown.julia, Markdown.Code("julia", code₁))
code_md₂ = Markdown.MD(Markdown.julia, Markdown.Code("julia", code₂))
return compare_ast(code_md₁, code_md₂; color)
end


function method_to_ast(method::Method)
ast = CodeTracking.definition(Expr, method)
if isnothing(ast)
if !haskey(Base.loaded_modules, Revise_PKG_ID)
error("cannot retrieve the AST definition of `$(method.name)` as Revise.jl is not loaded")

Check warning on line 522 in src/compare.jl

View check run for this annotation

Codecov / codecov/patch

src/compare.jl#L521-L522

Added lines #L521 - L522 were not covered by tests
else
error("could not retrieve the AST definition of `$(method.sig)` at world age $(method.primary_world)")

Check warning on line 524 in src/compare.jl

View check run for this annotation

Codecov / codecov/patch

src/compare.jl#L524

Added line #L524 was not covered by tests
end
end
return ast
end

method_to_ast(mi::Core.MethodInstance) = method_to_ast(mi.def)

function method_to_ast(f::Base.Callable, types::Type{<:Tuple}, world=Base.get_world_counter())
@nospecialize(f, types)
sig = Base.signature_type(f, types)
mi = method_instance(sig, world)
return method_to_ast(mi)
end


"""
compare_ast(
f₁::Base.Callable, types₁::Type{<:Tuple},
f₂::Base.Callable, types₂::Type{<:Tuple};
color=true, kwargs...
)
Retrieve the AST for the definitions of the methods matching the calls to `f₁` and `f₂`
using [`CodeTracking.jl`](https://github.com/timholy/CodeTracking.jl), then compare them.
For `CodeTracking.jl` to work, [`Revise.jl`](https://github.com/timholy/Revise.jl) must be
loaded.
"""
function compare_ast(
f₁::Base.Callable, types₁::Type{<:Tuple},
f₂::Base.Callable, types₂::Type{<:Tuple};
kwargs...
)
@nospecialize(f₁, types₁, f₂, types₂)
code₁ = method_to_ast(f₁, types₁)
code₂ = method_to_ast(f₂, types₂)
return compare_ast(code₁, code₂; kwargs...)
end


function compare_ast(
f::Base.Callable, types::Type{<:Tuple}, world₁::Integer, world₂::Integer;
kwargs...
)
@nospecialize(f, types)
# While this does work if both versions of `f` are defined in the REPL at different
# lines, this isn't testable.
# This is here solely to have a homogenous interface.
error("Revise.jl does not keep track of previous definitions: cannot compare")
end


"""
code_diff(::Val{:ast}, code₁, code₂; kwargs...)
Expand Down Expand Up @@ -537,6 +604,7 @@ code_diff(code₁, code₂; type::Symbol=:native, kwargs...) =
code_diff(::Val{:native}, f₁, types₁, f₂, types₂; kwargs...) = compare_code_native(f₁, types₁, f₂, types₂; kwargs...)
code_diff(::Val{:llvm}, f₁, types₁, f₂, types₂; kwargs...) = compare_code_llvm(f₁, types₁, f₂, types₂; kwargs...)
code_diff(::Val{:typed}, f₁, types₁, f₂, types₂; kwargs...) = compare_code_typed(f₁, types₁, f₂, types₂; kwargs...)
code_diff(::Val{:ast}, f₁, types₁, f₂, types₂; kwargs...) = compare_ast(f₁, types₁, f₂, types₂; kwargs...)

code_diff(code₁::Tuple, code₂::Tuple; type::Symbol=:native, kwargs...) =
code_diff(Val(type), code₁..., code₂...; kwargs...)
Expand Down
57 changes: 52 additions & 5 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@

using Aqua
using CodeDiffs
using DeepDiffs
using InteractiveUtils
using ReferenceTests
using Revise
using Test


Expand Down Expand Up @@ -39,15 +41,31 @@ macro no_overwrite_warning(expr)
end


function eval_for_revise(str, path=tempname(), init=true)
open(path, "w") do file
println(file, str)
end

if init
Revise.includet(path)
else
main_pkg_data = Revise.pkgdatas[Base.PkgId(nothing, "Main")]
Revise.revise_file_now(main_pkg_data, path)
end

return path
end


# OhMyREPL is quite reluctant from loading its Markdown highlighting overload in a testing
# environment. See https://github.com/KristofferC/OhMyREPL.jl/blob/b0071f5ee785a81ca1e69a561586ff270b4dc2bb/src/OhMyREPL.jl#L106
prev = jl_options_overload(:isinteractive, Int8(1))
@no_overwrite_warning using OhMyREPL
jl_options_overload(:isinteractive, prev)


# Disable printing diffs to stdout by setting `ENV["TEST_PRINT_DIFFS"] = false`
const TEST_PRINT_DIFFS = parse(Bool, get(ENV, "TEST_PRINT_DIFFS", "true"))
# Enable printing diffs to stdout only in CI by default
const TEST_PRINT_DIFFS = parse(Bool, get(ENV, "TEST_PRINT_DIFFS", get(ENV, "CI", "false")))
const TEST_IO = TEST_PRINT_DIFFS ? stdout : IOContext(IOBuffer(), stdout)


Expand Down Expand Up @@ -117,8 +135,10 @@ end
end

@testset "Basic function" begin
eval_for_revise("""
f1() = 1
f2() = 2
""")

@testset "Typed" begin
diff = CodeDiffs.compare_code_typed(f1, Tuple{}, f1, Tuple{}; color=false)
Expand Down Expand Up @@ -149,6 +169,15 @@ end
@test !CodeDiffs.issame(diff)
@test diff == (@code_diff type=:native color=false f1() f2())
end

@testset "AST" begin
diff = CodeDiffs.compare_ast(f1, Tuple{}, f1, Tuple{}; color=false)
@test CodeDiffs.issame(diff)

diff = CodeDiffs.compare_ast(f1, Tuple{}, f2, Tuple{}; color=false)
@test !CodeDiffs.issame(diff)
@test diff == (@code_diff type=:ast color=false f1() f2())
end
end

@testset "Changes" begin
Expand Down Expand Up @@ -220,6 +249,15 @@ end
println(TEST_IO)
end

@testset "AST" begin
diff = CodeDiffs.compare_ast(f₁, args₁, f₂, args₂; color=true)
@test findfirst(CodeDiffs.ANSI_REGEX, diff.before) === nothing
@test !endswith(diff.before, '\n') && !endswith(diff.after, '\n')
println(TEST_IO, "\nAST: $(nameof(f₁)) vs. $(nameof(f₂))")
printstyled(TEST_IO, display_str(diff; columns=120))
println(TEST_IO)
end

@testset "Line numbers" begin
diff = CodeDiffs.compare_code_typed(f₁, args₁, f₂, args₂; color=false)
@test findfirst(CodeDiffs.ANSI_REGEX, diff.before) === nothing
Expand All @@ -232,11 +270,14 @@ end
end

@testset "f1" begin
eval_for_revise("""
f() = 1
""")
test_cmp_display(f, Tuple{}, f, Tuple{})
end

@testset "saxpy" begin
eval_for_revise("""
function saxpy(r, a, x, y)
for i in eachindex(r)
r[i] = a * x[i] + y[i]
Expand All @@ -248,6 +289,7 @@ end
r[i] = a * x[i] + y[i]
end
end
""")

saxpy_args = Tuple{Vector{Int}, Int, Vector{Int}, Vector{Int}}
test_cmp_display(saxpy, saxpy_args, saxpy_simd, saxpy_args)
Expand Down Expand Up @@ -311,10 +353,11 @@ end
end

@testset "World age" begin
@no_overwrite_warning @eval begin
f() = 1
@no_overwrite_warning begin
file_name = eval_for_revise("f() = 1")
w₁ = Base.get_world_counter()
f() = 2

eval_for_revise("f() = 2", file_name, false)
w₂ = Base.get_world_counter()
end

Expand Down Expand Up @@ -345,6 +388,10 @@ end
diff = CodeDiffs.compare_code_native(f, Tuple{}, w₁, w₂; color=false, debuginfo=:none)
@test !CodeDiffs.issame(diff)
end

@testset "AST" begin
@test_throws ErrorException CodeDiffs.compare_ast(f, Tuple{}, w₁, w₁; color=false)
end
end

@testset "Tabs" begin
Expand Down

0 comments on commit be93b5f

Please sign in to comment.