JET employs Julia's type inference system to detect potential bugs and type instabilities.
Note
The current latest version, v0.12 series, supports full JET functionality on Julia v1.12 and v1.13 only.
The JET version that works with v1.11 is the v0.9 series, but please note that bug fixes and new features added in the subsequent series may not necessarily be available.
Warning
Please note that due to JET's tight integration with the Julia compiler, the results
presented by JET can vary significantly depending on the version of Julia you are using.
Additionally, the implementation of the Base module and standard libraries bundled with
Julia can also affect the results.
Moreover, Julia's compiler plugin system is unstable and changes frequently. Each JET release therefore supports full functionality on only a limited set of Julia versions. JET may remain installable on newer Julia versions, but loads empty stubs by default when full functionality is unavailable.
JET distinguishes installation compatibility from functional compatibility.
- The latest JET series keeps its Julia upper compat bound open. This allows packages with JET as a test dependency to instantiate on Julia pre-releases and nightly.
- Full JET functionality is loaded only on explicitly supported Julia versions. On unsupported future Julia versions, JET loads empty stubs and its analysis APIs throw an explanatory error.
- JET minor versions are bumped for ordinary semantic-versioning reasons and may also mark a new Julia compatibility generation. Patch releases normally stay within the same compatibility generation.
- When support for a new Julia minor is released, older JET releases are capped in the official General registry at the last Julia minor they support. This prevents package resolution and lower-bound testing from selecting an old, incompatible JET.
Test suites can remain instantiable on unsupported Julia versions and skip their
JET-specific tests at runtime using the JET_AVAILABLE constant:
using JET
if JET.JET_AVAILABLE
include("jet_tests.jl")
endSetting the JET_DEV_MODE preference to true forces JET to try loading
full functionality on an unsupported Julia version.
See more commands, options and explanations in the documentation.
JET is a standard Julia package. So you can just install it via Julia's built-in package manager and use it just like any other package:
julia> using Pkg; Pkg.add("JET")
[ some output elided ]
julia> using JETImportant
The package manager installs a JET version allowed by registry metadata and
the compatibility constraints of your environment. This does not necessarily
mean full JET functionality is supported on your Julia version; check
JET.JET_AVAILABLE after loading JET.
Existing dependencies may also prevent a working JET version from being
installed.
This can particularly occur when the version of JuliaInterpreter.jl
is incompatible with JET, since JuliaInterpreter is also a dependency of the
very commonly used package Revise.jl.
In such cases, the most reliable way to install and use a working JET is to
set up a temporary environment (e.g., Pkg.temp()) and use JET there.
Type instabilities can be detected in function calls using the @report_opt macro, which works similar to the @code_warntype macro.
Note that, because JET relies on Julia's type inference, if a chain of inference is broken due to dynamic dispatch, then all downstream function calls will be unknown to the compiler, and so JET cannot analyze them.
julia> @report_opt foldl(+, Any[]; init=0)
═════ 2 possible errors found ═════
┌ kwcall(::@NamedTuple{init::Int64}, ::typeof(foldl), op::typeof(+), itr::Vector{Any}) @ Base ./reduce.jl:198
│┌ foldl(op::typeof(+), itr::Vector{Any}; kw::@Kwargs{init::Int64}) @ Base ./reduce.jl:198
││┌ kwcall(::@NamedTuple{init::Int64}, ::typeof(mapfoldl), f::typeof(identity), op::typeof(+), itr::Vector{Any}) @ Base ./reduce.jl:175
│││┌ mapfoldl(f::typeof(identity), op::typeof(+), itr::Vector{Any}; init::Int64) @ Base ./reduce.jl:175
││││┌ mapfoldl_impl(f::typeof(identity), op::typeof(+), nt::Int64, itr::Vector{Any}) @ Base ./reduce.jl:44
│││││┌ foldl_impl(op::Base.BottomRF{typeof(+)}, nt::Int64, itr::Vector{Any}) @ Base ./reduce.jl:48
││││││┌ _foldl_impl(op::Base.BottomRF{typeof(+)}, init::Int64, itr::Vector{Any}) @ Base ./reduce.jl:58
│││││││┌ (::Base.BottomRF{typeof(+)})(acc::Int64, x::Any) @ Base ./reduce.jl:86
││││││││ runtime dispatch detected: +(acc::Int64, x::Any)::Any
│││││││└────────────────────
││││││┌ _foldl_impl(op::Base.BottomRF{typeof(+)}, init::Int64, itr::Vector{Any}) @ Base ./reduce.jl:62
│││││││┌ (::Base.BottomRF{typeof(+)})(acc::Any, x::Any) @ Base ./reduce.jl:86
││││││││ runtime dispatch detected: +(acc::Any, x::Any)::Any
│││││││└────────────────────This works best on type stable code, so use @report_opt liberally before using @report_call.
julia> @report_call foldl(+, Char[])
═════ 2 possible errors found ═════
┌ foldl(op::typeof(+), itr::Vector{Char}) @ Base ./reduce.jl:198
│┌ foldl(op::typeof(+), itr::Vector{Char}; kw::@Kwargs{}) @ Base ./reduce.jl:198
││┌ mapfoldl(f::typeof(identity), op::typeof(+), itr::Vector{Char}) @ Base ./reduce.jl:175
│││┌ mapfoldl(f::typeof(identity), op::typeof(+), itr::Vector{Char}; init::Base._InitialValue) @ Base ./reduce.jl:175
││││┌ mapfoldl_impl(f::typeof(identity), op::typeof(+), nt::Base._InitialValue, itr::Vector{Char}) @ Base ./reduce.jl:44
│││││┌ foldl_impl(op::Base.BottomRF{typeof(+)}, nt::Base._InitialValue, itr::Vector{Char}) @ Base ./reduce.jl:48
││││││┌ _foldl_impl(op::Base.BottomRF{typeof(+)}, init::Base._InitialValue, itr::Vector{Char}) @ Base ./reduce.jl:62
│││││││┌ (::Base.BottomRF{typeof(+)})(acc::Char, x::Char) @ Base ./reduce.jl:86
││││││││ no matching method found `+(::Char, ::Char)`: (op::Base.BottomRF{typeof(+)}).rf::typeof(+)(acc::Char, x::Char)
│││││││└────────────────────
│││││┌ foldl_impl(op::Base.BottomRF{typeof(+)}, nt::Base._InitialValue, itr::Vector{Char}) @ Base ./reduce.jl:49
││││││┌ reduce_empty_iter(op::Base.BottomRF{typeof(+)}, itr::Vector{Char}) @ Base ./reduce.jl:383
│││││││┌ reduce_empty_iter(op::Base.BottomRF{typeof(+)}, itr::Vector{Char}, ::Base.HasEltype) @ Base ./reduce.jl:384
││││││││┌ reduce_empty(op::Base.BottomRF{typeof(+)}, ::Type{Char}) @ Base ./reduce.jl:360
│││││││││┌ reduce_empty(::typeof(+), ::Type{Char}) @ Base ./reduce.jl:343
││││││││││ no matching method found `zero(::Type{Char})`: zero(T::Type{Char})
│││││││││└────────────────────This looks for all method definitions and analyses function calls based on their signatures. Note that this is less accurate than @report_call, because the actual input types cannot be known for generic methods.
julia> using Pkg; Pkg.activate(; temp=true, io=devnull); Pkg.add("AbstractTrees"; io=devnull);
julia> Pkg.status()
Status `/private/var/folders/xh/6zzly9vx71v05_y67nm_s9_c0000gn/T/jl_h07K2m/Project.toml`
[1520ce14] AbstractTrees v0.4.5
julia> using AbstractTrees
julia> report_package(AbstractTrees)
[toplevel-info] Analyzing top-level definition (progress: 256/256)
[toplevel-info] Analyzed all top-level definitions (all: 256 | analyzed: 256 | cached: 0 | took: 7.116 sec)
[ Info: tracking Base
═════ 7 possible errors found ═════
┌ isroot(root::Any, x::Any) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/base.jl:102
│ no matching method found `parent(::Any, ::Any)`: AbstractTrees.parent(root::Any, x::Any)
└────────────────────
┌ StableNode{T}(x::T, ch::Any) where T @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/base.jl:260
│┌ collect(::Type{StableNode{_A}} where _A, itr::Any) @ Base ./array.jl:641
││┌ _collect(::Type{StableNode{_A}}, itr::Any, isz::Union{Base.HasLength, Base.HasShape}) where _A @ Base ./array.jl:643
│││┌ _array_for(::Type{StableNode{_A}} where _A, itr::Base.HasLength, isz::Any) @ Base ./array.jl:673
││││┌ _similar_shape(itr::Base.HasLength, ::Base.HasLength) @ Base ./array.jl:657
│││││ no matching method found `length(::Base.HasLength)`: length(itr::Base.HasLength)
││││└────────────────────
││││┌ _similar_shape(itr::Base.HasLength, ::Base.HasShape) @ Base ./array.jl:658
│││││┌ axes(A::Base.HasLength) @ Base ./abstractarray.jl:98
││││││ no matching method found `size(::Base.HasLength)`: size(A::Base.HasLength)
│││││└────────────────────
┌ IndexNode(tree::Any) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:117
│ no matching method found `rootindex(::Any)`: rootindex(tree::Any)
└────────────────────
┌ parent(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:127
│ no matching method found `parentindex(::Any, ::Any)`: pidx = parentindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any)
└────────────────────
┌ nextsibling(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:132
│ no matching method found `nextsiblingindex(::Any, ::Any)`: sidx = nextsiblingindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any)
└────────────────────
┌ prevsibling(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:137
│ no matching method found `prevsiblingindex(::Any, ::Any)`: sidx = prevsiblingindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any)
└────────────────────
julia> report_package(AbstractTrees; target_modules=(AbstractTrees,)) # ignore errors that occur outside the AbstractTrees module context
[toplevel-info] Skipped analysis for cached definition (256/256)
[toplevel-info] Analyzed all top-level definitions (all: 256 | analyzed: 0 | cached: 256 | took: 0.036 sec)
═════ 5 possible errors found ═════
┌ isroot(root::Any, x::Any) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/base.jl:102
│ no matching method found `parent(::Any, ::Any)`: AbstractTrees.parent(root::Any, x::Any)
└────────────────────
┌ IndexNode(tree::Any) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:117
│ no matching method found `rootindex(::Any)`: rootindex(tree::Any)
└────────────────────
┌ parent(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:127
│ no matching method found `parentindex(::Any, ::Any)`: pidx = parentindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any)
└────────────────────
┌ nextsibling(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:132
│ no matching method found `nextsiblingindex(::Any, ::Any)`: sidx = nextsiblingindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any)
└────────────────────
┌ prevsibling(idx::IndexNode) @ AbstractTrees /Users/aviatesk/.julia/packages/AbstractTrees/Ftf8W/src/indexing.jl:137
│ no matching method found `prevsiblingindex(::Any, ::Any)`: sidx = prevsiblingindex((idx::IndexNode).tree::Any, (idx::IndexNode).index::Any)
└────────────────────JET explores the functions you call directly as well as their inferable callees. However, if the argument types for a call cannot be inferred, JET does not analyze the callee. Consequently, a report of No errors detected does not imply that your entire codebase is free of errors. To increase the confidence in JET's results use @report_opt to make sure your code is inferrible.
This project started as my undergrad thesis project at Kyoto University, supervised by Prof. Takashi Sakuragawa. We were heavily inspired by ruby/typeprof, an experimental type understanding/checking tool for Ruby. The grad thesis about this project is published at https://github.com/aviatesk/grad-thesis, but currently, it's only available in Japanese.