Configurations
JET analysis can be flexibly fine-tuned. Any entry point explained in Usages can accept any of the configuration parameter described below as keyword arguments (or optional parameters for macros). For example, if you want to analyze your_awesome_file.jl
with turning on strict_condition_check
configuration and also logs inference process into stdout
, your can do:
report_file("your_awesome_file.jl";
strict_condition_check = true,
inference_logger = stdout)
Please ignore the names of documented objects below, like "JET.ToplevelConfig
". They are just remnants of documentation internals, and you will never directly interact with them.
Configurations for Top-level Analysis
JET.ToplevelConfig
— TypeConfigurations for top-level analysis. These configurations will be active for all the top-level entries explained in Analysis entry points.
analyze_from_definitions::Bool = false
Iftrue
, JET will start analysis using signatures of top-level definitions (e.g. method signatures), after the top-level interpretation has been done (unless no serious top-level error has happened, like errors involved within a macro expansion).This is useful when you want to analyze a package, which usually contains only definitions but not top-level callsites. With this option, JET can enter analysis just with method or type definitions, and we don't need to pass a file that uses the target package.
Warning This feature is very experimental at this point, and you may face lots of false positive errors, especially when trying to analyze a big package with lots of dependencies. If a file that contains top-level callsites (e.g.
test/runtests.jl
) is available, JET analysis entered from there will produce more accurate analysis results than using with this configuration.Also see:
report_file
,report_and_watch_file
concretization_patterns::Vector{<:Any} = Expr[]
Specifies a customized top-level code concretization strategy.When analyzing a top-level code, JET first splits the entire code and then iterate a virtual top-level code execution process on each code block, in order to simulate Julia's sequential top-level code execution. However, with this approach, JET can't track the "inter-code-block" level dependencies, and so a partial interpretation of top-level definitions can fail if it needs an access to global variables defined in other code blocks that are not actually interpreted ("concretized") but just abstract-interpreted ("abstracted").
For example, the issue happens when your macro accesses to a global variable during its expansion, e.g.:
test/fixtures/concretization_patterns.jl
# JET doesn't conretize this by default, but just analyze its type const GLOBAL_CODE_STORE = Dict() macro with_code_record(a) GLOBAL_CODE_STORE[__source__] = a # record the code location in the global store esc(a) end # here JET will try to actually expand `@with_code_record`, # but since `GLOBAL_CODE_STORE` didn't get concretized (i.e. instantiated), JET analysis fails at this point @with_code_record foo(a) = identity(a) foo(10) # top-level callsite, abstracted away
To circumvent this issue, JET offers the
concretization_patterns::Vector{<:Any}
configuration, which allows us to customize JET's top-level code concretization strategy.concretization_patterns
specifies the patterns of code that should be concretized. JET internally uses MacroTools.jl's expression pattern match, and we can specify any expression pattern that is expected byMacroTools.@capture
macro. For example, in order to solve the issue explained above, we can have:concretization_patterns = [:(GLOBAL_CODE_STORE = x_)]
Please note that we must use
:(GLOBAL_CODE_STORE = x_)
rather than:(const GLOBAL_CODE_STORE = x_)
. This is because currently the specified patterns will be matched against the lowered code representation, in whichconst x = y
has been lowered to the sequence of 1.) the declarationconst x
, 2.) value computation%2 = Dict()
and 3.) actual assignment partx = %2
. Although this could be really tricky, we can effectively debug JET's top-level code concretization plan usingJETLogger
'stoplevel_logger
with the logging level above than1
("debug") level, wheret
-annotated statements will be concretize whilef
-annotated statements will be analyzed by abstract interpretation.julia> report_file("test/fixtures/concretization_patterns.jl"; concretization_patterns = [:(GLOBAL_CODE_STORE = x_)], toplevel_logger = IOContext(stdout, :JET_LOGGER_LEVEL => 1))
[toplevel-debug] entered into test/fixtures/concretization_patterns.jl [toplevel-debug] concretization plan: 1 f 1 ─ const GLOBAL_CODE_STORE 2 t │ %2 = Dict() 3 t │ GLOBAL_CODE_STORE = %2 4 f └── return %2 [toplevel-debug] concretization plan: 1 f 1 ─ $(Expr(:thunk, CodeInfo( @ none within `top-level scope' 1 ─ return $(Expr(:method, Symbol("@with_code_record"))) ))) 2 t │ $(Expr(:method, Symbol("@with_code_record"))) 3 t │ %3 = Core.Typeof(var"@with_code_record") 4 t │ %4 = Core.svec(%3, Core.LineNumberNode, Core.Module, Core.Any) 5 t │ %5 = Core.svec() 6 t │ %6 = Core.svec(%4, %5, $(QuoteNode(:(#= test/fixtures/concretization_patterns.jl:4 =#)))) 7 t │ $(Expr(:method, Symbol("@with_code_record"), :(%6), CodeInfo( @ test/fixtures/concretization_patterns.jl:5 within `none' 1 ─ $(Expr(:meta, :nospecialize, :(a))) │ Base.setindex!(GLOBAL_CODE_STORE, a, __source__) │ @ test/fixtures/concretization_patterns.jl:6 within `none' │ %3 = esc(a) └── return %3 ))) 8 f └── return var"@with_code_record" [toplevel-debug] concretization plan: 1 f 1 ─ $(Expr(:thunk, CodeInfo( @ none within `top-level scope' 1 ─ return $(Expr(:method, :foo)) ))) 2 t │ $(Expr(:method, :foo)) 3 t │ %3 = Core.Typeof(foo) 4 t │ %4 = Core.svec(%3, Core.Any) 5 t │ %5 = Core.svec() 6 t │ %6 = Core.svec(%4, %5, $(QuoteNode(:(#= test/fixtures/concretization_patterns.jl:11 =#)))) 7 t │ $(Expr(:method, :foo, :(%6), CodeInfo( @ test/fixtures/concretization_patterns.jl:11 within `none' 1 ─ %1 = identity(a) └── return %1 ))) 8 f └── return foo [toplevel-debug] concretization plan: 1 f 1 ─ %1 = foo(10) 2 f └── return %1 [toplevel-debug] exited from test/fixtures/concretization_patterns.jl (took 0.018 sec)
Also see: Logging Configurations,
virtual_process
.
Configurations for Abstract Interpretation
Currently JET doesn't invalidate the previous analysis results cached with the different analysis configurations (see https://github.com/aviatesk/JET.jl/issues/130 for an example). So you need to refresh Julia session to get updated analysis results with different inference configurations.
JET.JETAnalysisParams
— TypeConfigurations for JET analysis. These configurations will be active for all the entries.
strict_condition_check::Bool = false
Enables strict condition check. JET reports an error if a condition expression type is "non-boolean". In a case when the condition type isUnion
, JET will report if either of union split case is non-boolean type, but this can lead to lots of false positive error reports when the code is not well-typed, because JuliaBase
defines generic functions that are commonly used at a conditional context but also may return "non-boolean" values, e.g.:!(::Function) -> Function
!(::Missing) -> Missing
==(::Missing, ::Any) -> Missing
==(::Any, ::Missing) -> Missing
and thus loosely-typed conditional expression often becomes e.g.
Union{Bool, Missing}
, and consequently JET will report it as "non-boolean" type (NOTE: in JuliaMissing
is certainly not valid conditional type). If this configuration is set tofalse
, JET enables an heuristic to avoid those false positive error reports and won't report an error if a condition expression type isUnion
and either of its union split case isFunction
orMissing
.The effect of this configuration can be described with the following examples:
with
strict_condition_check::Bool = false
(default)julia> test_f() = Dict('a' => 1, :b => 2) # ::Dict{Any,Int} test_f (generic function with 1 method) julia> @report_call test_f() No errors ! Dict{Any, Int64}
with
strict_condition_check::Bool = true
julia> test_f() = Dict('a' => 1, :b => 2) # ::Dict{Any,Int} test_f (generic function with 1 method) julia> @report_call strict_condition_check = true test_f() ═════ 1 possible error found ═════ ┌ @ REPL[2]:1 Main.Dict(Main.=>('a', 1), Main.=>(:b, 2)) │┌ @ dict.jl:125 Base.Dict(ps) ││┌ @ dict.jl:129 Base.dict_with_eltype(#308, kv, Base.eltype(kv)) │││┌ @ abstractdict.jl:539 Base.grow_to!(Base.dict_with_eltype(DT_apply, _5), kv) ││││┌ @ dict.jl:145 Base.grow_to!(dest2, itr, st) │││││┌ @ dict.jl:159 Base.setindex!(new, v, k) ││││││┌ @ dict.jl:383 Base.ht_keyindex2!(h, key) │││││││┌ @ dict.jl:328 goto %35 if not Base.isequal(key, Base.getindex(keys, index)) ││││││││ for 1 of union split cases, non-boolean (Missing) used in boolean context: goto %35 if not Base.isequal(key::Symbol, Base.getindex(keys::Vector{Any}, index::Int64)::Any)::Union{Missing, Bool} │││││││└─────────────── Dict{Any, Int64}
ignore_native_remarks::Bool = true
Iftrue
, JET won't construct nor cache reports of "native remarks", which may speed up analysis time. "Native remarks" are information that Julia's native compiler emits about how type inference routine goes, and those remarks are less interesting in term of "error checking", so JET ignores them by default.
JET.JETInferenceParams
— FunctionConfigurations for Julia's native type inference routine. These configurations will be active for all the entries.
You can specify all the keyword parameters of Core.Compiler.InferenceParams
, e.g. max_methods::Int = 3
, union_splitting::Int = 4
. Listed here are selections of those parameters that can have a potent influence on JET analysis.
ipo_constant_propagation::Bool = true
Enables constant propagation in abstract interpretation. It is highly recommended that you keep this configurationtrue
to get reasonable analysis, because constant propagation can cut off lots of false positive errorenous code paths and thus lead to more accurate and useful analysis results.
aggressive_constant_propagation::Bool = true
Iftrue
, JET will try to do constant propagation more "aggressively". As explained above, it can lead to more accurate analysis, but also lead to worse analysis performance at the cost of that.
unoptimize_throw_blocks::Bool = false
Turn this on to skip analysis on code blocks that will eventually lead to athrow
call. This configuration may improve the analysis performance, but it's better to be turned off for JET analysis, because there may be other errors even in those code blocks.
Print Configurations
JET.PrintConfig
— TypeJET configurations for error printing. If the entry renders the collected error points, the configurations below will be active.
print_toplevel_success::Bool = false
Iftrue
, prints a message when there is no toplevel errors found.
print_inference_success::Bool = true
Iftrue
, print a message when there is no errors found in abstract interpretation based analysis pass.
annotate_types::Bool = false
When set totrue
, annotates types when printing analyzed call stack. Here are examples:- with
annotate_types = false
(default):julia> @report_call sum("julia") ═════ 2 possible errors found ═════ ┌ @ reduce.jl:530 Base.#sum#241(Base.pairs(Core.NamedTuple()), #self#, a) │┌ @ reduce.jl:530 Base.sum(Base.identity, a) ││┌ @ reduce.jl:503 Base.#sum#240(Base.pairs(Core.NamedTuple()), #self#, f, a) │││┌ @ reduce.jl:503 Base.mapreduce(f, Base.add_sum, a) ││││┌ @ reduce.jl:289 Base.#mapreduce#237(Base.pairs(Core.NamedTuple()), #self#, f, op, itr) │││││┌ @ reduce.jl:289 Base.mapfoldl(f, op, itr) ││││││┌ @ reduce.jl:162 Base.#mapfoldl#233(Base._InitialValue(), #self#, f, op, itr) │││││││┌ @ reduce.jl:162 Base.mapfoldl_impl(f, op, init, itr) ││││││││┌ @ reduce.jl:44 Base.foldl_impl(op′, nt, itr′) │││││││││┌ @ reduce.jl:48 Base._foldl_impl(op, nt, itr) ││││││││││┌ @ reduce.jl:62 op(v, Base.getindex(y, 1)) │││││││││││┌ @ reduce.jl:81 Base.getproperty(op, :rf)(acc, x) ││││││││││││┌ @ reduce.jl:24 Base.+(x, y) │││││││││││││ no matching method found for call signature: Base.+(x::Char, y::Char) ││││││││││││└──────────────── │││││││││┌ @ reduce.jl:49 Base.reduce_empty_iter(op, itr) ││││││││││┌ @ reduce.jl:356 Base.reduce_empty_iter(op, itr, Base.IteratorEltype(itr)) │││││││││││┌ @ reduce.jl:357 Base.reduce_empty(op, Base.eltype(itr)) ││││││││││││┌ @ reduce.jl:330 Base.reduce_empty(Base.getproperty(op, :rf), _) │││││││││││││┌ @ reduce.jl:322 Base.reduce_empty(Base.+, _) ││││││││││││││┌ @ reduce.jl:313 Base.zero(_) │││││││││││││││ no matching method found for call signature: Base.zero(_::Type{Char}) ││││││││││││││└───────────────── Char
- with
annotate_types = true
julia> @report_call annotate_types = true sum("julia") ═════ 2 possible errors found ═════ ┌ @ reduce.jl:530 Base.#sum#241(Base.pairs(Core.NamedTuple()::NamedTuple{(), Tuple{}})::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, #self#::typeof(sum), a::String) │┌ @ reduce.jl:530 Base.sum(Base.identity, a::String) ││┌ @ reduce.jl:503 Base.#sum#240(Base.pairs(Core.NamedTuple()::NamedTuple{(), Tuple{}})::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, #self#::typeof(sum), f::typeof(identity), a::String) │││┌ @ reduce.jl:503 Base.mapreduce(f::typeof(identity), Base.add_sum, a::String) ││││┌ @ reduce.jl:289 Base.#mapreduce#237(Base.pairs(Core.NamedTuple()::NamedTuple{(), Tuple{}})::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, #self#::typeof(mapreduce), f::typeof(identity), op::typeof(Base.add_sum), itr::String) │││││┌ @ reduce.jl:289 Base.mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::String) ││││││┌ @ reduce.jl:162 Base.#mapfoldl#233(Base._InitialValue()::Base._InitialValue, #self#::typeof(mapfoldl), f::typeof(identity), op::typeof(Base.add_sum), itr::String) │││││││┌ @ reduce.jl:162 Base.mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), init::Base._InitialValue, itr::String) ││││││││┌ @ reduce.jl:44 Base.foldl_impl(op′::Base.BottomRF{typeof(Base.add_sum)}, nt::Base._InitialValue, itr′::String) │││││││││┌ @ reduce.jl:49 Base.reduce_empty_iter(op::Base.BottomRF{typeof(Base.add_sum)}, itr::String) ││││││││││┌ @ reduce.jl:356 Base.reduce_empty_iter(op::Base.BottomRF{typeof(Base.add_sum)}, itr::String, Base.IteratorEltype(itr::String)::Base.HasEltype) │││││││││││┌ @ reduce.jl:357 Base.reduce_empty(op::Base.BottomRF{typeof(Base.add_sum)}, Base.eltype(itr::String)::Type{Char}) ││││││││││││┌ @ reduce.jl:330 Base.reduce_empty(Base.getproperty(op::Base.BottomRF{typeof(Base.add_sum)}, :rf::Symbol)::typeof(Base.add_sum), _::Type{Char}) │││││││││││││┌ @ reduce.jl:322 Base.reduce_empty(Base.+, _::Type{Char}) ││││││││││││││┌ @ reduce.jl:313 Base.zero(_::Type{Char}) │││││││││││││││ no matching method found for call signature: Base.zero(_::Type{Char}) ││││││││││││││└───────────────── │││││││││┌ @ reduce.jl:48 Base._foldl_impl(op::Base.BottomRF{typeof(Base.add_sum)}, nt::Base._InitialValue, itr::String) ││││││││││┌ @ reduce.jl:62 op::Base.BottomRF{typeof(Base.add_sum)}(v::Char, Base.getindex(y::Tuple{Char, Int64}, 1)::Char) │││││││││││┌ @ reduce.jl:81 Base.getproperty(op::Base.BottomRF{typeof(Base.add_sum)}, :rf::Symbol)::typeof(Base.add_sum)(acc::Char, x::Char) ││││││││││││┌ @ reduce.jl:24 Base.+(x::Char, y::Char) │││││││││││││ no matching method found for call signature: Base.+(x::Char, y::Char) ││││││││││││└──────────────── Char
Note JET always annotates types when printing the error point, e.g. in the example above, the error points below are always type-annotated regardless of this configuration:
no matching method found for call signature: Base.zero(_::Type{Char})
no matching method found for call signature: Base.+(x::Char, y::Char)
- with
fullpath::Bool = false
Controls whether or not expand a file path to full path when printing analyzed call stack. Note that paths of Julia'sBase
files will also be expanded when set totrue
.
Watch Configurations
JET.WatchConfig
— TypeConfigurations for "watch" mode. The configurations will only be active when used with report_and_watch_file
.
revise_all::Bool = true
Redirected toRevise.entr
'sall
keyword argument. When set totrue
, JET will retrigger analysis as soon as code updates are detected in any module tracked by Revise. Currently when encounteringimport/using
statements, JET won't perform analysis, but rather will just load the modules as usual execution (this also means Revise will track those modules). So if you're editing both files analyzed by JET and modules that are used within the files, this configuration should be enabled.
revise_modules = nothing
Redirected toRevise.entr
'smodules
positional argument. If a iterator ofModule
is given, JET will retrigger analysis whenever code inmodules
updates.Tip This configuration is useful when your're also editing files that are not tracked by Revise, e.g. editing functions defined in
Base
:# re-performe analysis when you make a change to `Base` julia> report_and_watch_file(yourfile; revise_modules = [Base])
Logging Configurations
JET.JETLogger
— TypeLogging configurations for JET analysis.
toplevel_logger::Union{Nothing,IO} = nothing
IfIO
object is given, it will track JET's toplevel analysis. Logging level can be specified with:JET_LOGGER_LEVEL
IO
property. Currently supported logging levels are either of0
("info" level, default),1
("debug" level).Examples:
- logs into
stdout
julia> report_file(filename; toplevel_logger = stdout)
- logs into
io::IOBuffer
with "debug" logger leveljulia> report_file(filename; toplevel_logger = IOContext(io, :JET_LOGGER_LEVEL => 1));
- logs into
inference_logger::Union{Nothing,IO} = nothing
IfIO
object is given, it will track JET's abstract interpretation routine. Logging level can be specified with:JET_LOGGER_LEVEL
IO
property. Currently supported logging levels are either of0
("info" level, default),1
("debug" level).Examples:
- logs into
stdout
analyze_call(f, args...; inference_logger = stdout)
- logs into
io::IOBuffer
with "debug" logger leveljulia> analyze_call(f, args...; inference_logger = IOContext(io, :JET_LOGGER_LEVEL => 1))
- logs into
Of course you can specify both toplevel_logger
and inference_logger
at the same time like below:
julia> report_and_watch_file(filename;
toplevel_logger = IOContext(logger_io, :JET_LOGGER_LEVEL => 1),
inference_logger = inference_io)
Configuration File
JET.parse_config
— FunctionJET.jl offers .prettierrc
style configuration file support. This means you can use .JET.toml
configuration file to specify any of configurations explained above and share that with others.
When report_file
or report_and_watch_file
is called, it will look for .JET.toml
in the directory of the given file, and search up the file tree until a JET configuration file is (or isn't) found. When found, the configurations specified in the file will be applied.
A configuration file can specify any of JET configurations like:
aggressive_constant_propagation = false # turn off aggressive constant propagation
... # other configurations
Note that the following configurations should be string(s) of valid Julia code:
concretization_patterns
: vector of string of Julia code, which can beparse
d into a Julia expression pattern expected byMacroTools.@capture
macro.toplevel_logger
: string of Julia code, which can beparse
d andeval
uated intoUnion{IO,Nothing}
inference_logger
: string of Julia code, which can beparse
d andeval
uated intoUnion{IO,Nothing}
E.g. the configurations below are equivalent:
- configurations via keyword arguments
report_file(somefile; concretization_patterns = [:(GLOBAL_CODE_STORE = x_)], toplevel_logger = IOContext(open("toplevel.txt", "w"), :JET_LOGGER_LEVEL => 1))
- configurations via a configuration file
# supposed to concretize `const GLOBAL_CODE_STORE = Dict()` in test/fixtures/concretization_patterns.jl concretization_patterns = ["GLOBAL_CODE_STORE = x_"] # logs toplevel analysis into toplevel.txt with debug logging level toplevel_logger = """IOContext(open("toplevel.txt", "w"), :JET_LOGGER_LEVEL => 1)"""