Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compare from functions signatures #8

Merged
merged 1 commit into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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 @@


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 @@
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 @@
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(::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
Loading