diff --git a/README.md b/README.md index dd8a5829c..3a69a18a4 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ You can have TypeProfiler.jl detect possible errors: ```julia julia> using TypeProfiler -julia> profile_and_watch_file("demo.jl") +julia> profile_and_watch_file("demo.jl"; annotate_types = true) profiling from demo.jl (finished in 2.745 sec) ════ 4 possible errors found ═════ diff --git a/src/print.jl b/src/print.jl index aa0387431..18e60d2e0 100644 --- a/src/print.jl +++ b/src/print.jl @@ -110,7 +110,7 @@ function print_reports(io::IO, print_inference_sucess::Bool = true, color::Bool = get(io, :color, false), fullpath::Bool = false, - dont_annotate_types::Bool = false, + annotate_types::Bool = false, __kwargs...) uniquify_reports!(reports) @@ -134,7 +134,7 @@ function print_reports(io::IO, toplevel_linfo_hash = new_toplevel_linfo_hash wrote_linfos = Set{UInt64}() end - print_report(io, report, wrote_linfos; fullpath, dont_annotate_types) + print_report(io, report, wrote_linfos; fullpath, annotate_types) end end |> postprocess |> Fix1(print, io) @@ -184,7 +184,7 @@ function print_error_frame(io, report, depth; kwargs...) print_rails(io, depth-1) printstyled(io, "│ ", report.msg; color) print_signature(io, report.sig; - dont_annotate_types = false, # always don't suppress annotations for errored signatures + annotate_types = true, # always annotate types for errored signatures bold = true, ) @@ -194,14 +194,14 @@ end function print_frame(io, (file, line, sig), depth, is_err; fullpath = false, - dont_annotate_types = false, + annotate_types = false, ) print_rails(io, depth-1) color = is_err ? ERROR_COLOR : RAIL_COLORS[(depth)%N_RAILS+1] s = string("┌ @ ", (fullpath ? tofullpath : identity)(string(file)), ':', line) printstyled(io, s, ' '; color) - print_signature(io, sig; dont_annotate_types) + print_signature(io, sig; annotate_types) return length(s) # the length of frame info string end @@ -213,11 +213,11 @@ function print_signature(io, sig; kwargs...) println(io) end _print_signature(io, a::Union{AbstractChar,AbstractString}; - dont_annotate_types = false, + annotate_types = false, kwargs...) = printstyled(io, a; kwargs...) -function _print_signature(io, @nospecialize(typ); dont_annotate_types = false, kwargs...) - dont_annotate_types && return +function _print_signature(io, @nospecialize(typ); annotate_types = false, kwargs...) + annotate_types || return printstyled(io, "::", string(typ); color = TYPE_ANNOTATION_COLOR, kwargs...) end diff --git a/src/reports.jl b/src/reports.jl index f73a319e9..5dd18ded4 100644 --- a/src/reports.jl +++ b/src/reports.jl @@ -128,12 +128,12 @@ macro reportdef(ex, kwargs...) isroot($(sv)) ? st : track_abstract_call_stack!(cache_report!, $(sv).parent, st) # postwalk end) - function strip_type_annotations(x) - isexpr(x, :escape) && return Expr(:escape, strip_type_annotations(first(x.args))) # keep escape + function strip_type_decls(x) + isexpr(x, :escape) && return Expr(:escape, strip_type_decls(first(x.args))) # keep escape return isexpr(x, :(::)) ? first(x.args) : x end - args′ = strip_type_annotations.(args) - spec_args′ = strip_type_annotations.(spec_args) + args′ = strip_type_decls.(args) + spec_args′ = strip_type_decls.(spec_args) constructor_body = quote msg = get_msg(#= T, interp, sv, ... =# $(args′...)) sig = get_sig(#= T, interp, sv, ... =# $(args′...)) @@ -291,16 +291,36 @@ function _get_sig_type(sv::InferenceState, expr::Expr) return if head === :call f = first(expr.args) args = expr.args[2:end] - nargs = length(args) - - sig = _get_sig(sv, f) - push!(sig, '(') - for (i, arg) in enumerate(args) - arg_sig = _get_sig(sv, arg) - append!(sig, arg_sig) - i ≠ nargs && push!(sig, ", ") + + # special case splat call signature + if isa(f, GlobalRef) && f.name === :_apply_iterate && begin + itf = first(args) + isa(itf, GlobalRef) && itf.name === :iterate + end + f = args[2] + args = args[3:end] + + sig = _get_sig(sv, f) + push!(sig, '(') + + nargs = length(args) + for (i, arg) in enumerate(args) + arg_sig = _get_sig(sv, arg) + append!(sig, arg_sig) + i ≠ nargs ? push!(sig, ", ") : push!(sig, "...)") + end + else + sig = _get_sig(sv, f) + push!(sig, '(') + + nargs = length(args) + for (i, arg) in enumerate(args) + arg_sig = _get_sig(sv, arg) + append!(sig, arg_sig) + i ≠ nargs && push!(sig, ", ") + end + push!(sig, ')') end - push!(sig, ')') sig, nothing elseif head === :(=) diff --git a/test/runtests.jl b/test/runtests.jl index 1e2ae23f0..a86cab9b6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,7 +9,8 @@ import Core.Compiler: import TypeProfiler: TPInterpreter, profile_call, get_result, virtual_process!, report_errors, get_virtual_globalvar, - ToplevelErrorReport, InferenceErrorReport + ToplevelErrorReport, InferenceErrorReport, + print_reports for sym in Symbol.(last.(Base.Fix2(split, '.').(string.(vcat(subtypes(TypeProfiler, ToplevelErrorReport), subtypes(TypeProfiler, InferenceErrorReport), @@ -98,6 +99,10 @@ end include("test_tfuncs.jl") end + @testset "print" begin + include("test_print.jl") + end + @testset "is it truly necessary ?" begin let # constant propagation can help to exclude false positive alerts; diff --git a/test/test_print.jl b/test/test_print.jl new file mode 100644 index 000000000..99a3284cd --- /dev/null +++ b/test/test_print.jl @@ -0,0 +1,70 @@ +# NOTE: +# tests in this file may not be so robust against the future changes in TypeProfiler.jl or +# even those in julia itself + +@testset "print toplevel errors" begin + let + io = IOBuffer() + s = """ + a = begin + b = + end + """ + + res, interp = profile_toplevel(s; filename = @__FILE__) + print_reports(io, res.toplevel_error_reports) + let s = String(take!(io)) + @test occursin("1 toplevel error found", s) + @test occursin(Regex("@ $(@__FILE__):\\d"), s) + @test occursin("syntax: unexpected \"end\"", s) + end + + res, interp = profile_toplevel(s; filename = "foo") + print_reports(io, res.toplevel_error_reports) + let s = String(take!(io)) + @test occursin("1 toplevel error found", s) + @test occursin(r"@ foo:\d", s) + @test occursin("syntax: unexpected \"end\"", s) + end + end +end + +# tests with Windows-paths is just an hell +@static Sys.iswindows() || begin + +@testset "print inference errors" begin + let + res, interp = @profile_toplevel begin + s = "julia" + sum(s) + end + + io = IOBuffer() + @test print_reports(io, res.inference_error_reports) + let s = String(take!(io)) + @test occursin("2 possible errors found", s) + @test occursin(Regex("@ $(escape_string(@__FILE__)):$((@__LINE__)-7)"), s) # toplevel call signature + end + end + + @testset "special case splat call signature" begin + let + vmod = gen_virtualmod() + res, interp = @profile_toplevel vmod begin + foo(args...) = sum(args) + foo(rand(Char, 1000000000)...) + end + + io = IOBuffer() + postprocess = TypeProfiler.gen_postprocess(vmod, Main) + @test print_reports(io, res.inference_error_reports, postprocess) + let s = String(take!(io)) + @test occursin("1 possible error found", s) + @test occursin(Regex("@ $(escape_string(@__FILE__)):$((@__LINE__)-8)"), s) # toplevel call signature + @test occursin("foo(rand(Char, 1000000000)...)", s) + end + end + end +end + +end # @static Sys.iswindows ||