Error Analysis

Julia's type system is quite expressive and its type inference is strong enough to generate highly optimized code from its very concise and generic program. But as opposed to other statically-compiled languages, Julia by design does NOT error nor warn anything even if it detects possible errors during its compilation process no matter how serious they are. Julia delays all the errors and warnings to the runtime.

This is actually a core design choice of the language – on the one hand, Julia's dynamism allow it to work in places where data types are not fully decided ahead of runtime just because Julia doesn't require it – on the other hand, with Julia, it's not straightforward to have such modern development experiences that a typical static language can offer, including static type checking and very rich IDE features.

JET is a trial to get the best of both worlds: can we have a sufficiently useful static analysis without losing all the beauty of Julia's dynamism ? JET directly employs Julia's builtin type inference system to enable a static analysis, so in that sense its approach is very different from "gradual typing", which is a common technique to bring static analysis into a dynamic language, as used for mypy for Python and TypeScript for JavaScript. Rather, Julia's type inference system and JET are powered by the technique called "abstract interpretation". As like Julia can effectively optimize your simple and generic program, JET can also analyze just a normal Julia program and smartly detect possible errors, but statically. So in other word, JET doesn't require any additional setups like scattering type annotations just for the sake of analysis.

Quick Start

First you need to install and load JET. JET is an ordinary Julia package, so you can install it via Julia's built-in package manager and use it as like other packages.

julia> ; # ] add JET # install JET via the built-in package manager
julia> using JET

Let's start with a simplest example: how JET can find anything wrong with sum("julia") ? @report_call and report_call analyzes a given function call and get back the detected problems. They can be used in a similar way as @code_typed and code_typed, and those interactive entry points are the most easiest way to use JET:

julia> @report_call sum("julia")═════ 2 possible errors found ═════
┌ @ reduce.jl:549 Base.#sum#265(Base.pairs(Core.NamedTuple()), #self#, a)
┌ @ reduce.jl:549 Base.sum(Base.identity, a)
┌ @ reduce.jl:520 Base.#sum#264(Base.pairs(Core.NamedTuple()), #self#, f, a)
┌ @ reduce.jl:520 Base.mapreduce(f, Base.add_sum, a)
┌ @ reduce.jl:294 Base.#mapreduce#261(Base.pairs(Core.NamedTuple()), #self#, f, op, itr)
┌ @ reduce.jl:294 Base.mapfoldl(f, op, itr)
┌ @ reduce.jl:162 Base.#mapfoldl#257(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 v = Base._foldl_impl(op, nt, itr)
┌ @ reduce.jl:62 v = 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 (Tuple{typeof(+), Char, Char}): Base.+(x::Char, y::Char)
└────────────────
┌ @ reduce.jl:49 Base.reduce_empty_iter(op, itr)
┌ @ reduce.jl:370 Base.reduce_empty_iter(op, itr, Base.IteratorEltype(itr))
┌ @ reduce.jl:371 Base.reduce_empty(op, Base.eltype(itr))
┌ @ reduce.jl:347 Base.reduce_empty(Base.getproperty(op, :rf), _)
┌ @ reduce.jl:339 Base.reduce_empty(Base.+, _)
┌ @ reduce.jl:330 Base.zero(_)
│ no matching method found for call signature (Tuple{typeof(zero), Type{Char}}): Base.zero(_::Type{Char})
└─────────────────

So JET found two possible problems. Now let's see how they can occur in actual execution:

julia> sum("julia") # will lead to `MethodError: +(::Char, ::Char)`ERROR: MethodError: no method matching +(::Char, ::Char)
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:578
  +(::T, ::Integer) where T<:AbstractChar at char.jl:237
  +(::Integer, ::AbstractChar) at char.jl:247
julia> sum("") # will lead to `MethodError: zero(Type{Char})`ERROR: MethodError: no method matching zero(::Type{Char}) Closest candidates are: zero(::Union{Type{P}, P}) where P<:Dates.Period at /opt/hostedtoolcache/julia/nightly/x64/share/julia/stdlib/v1.9/Dates/src/periods.jl:53 zero(::CartesianIndex{N}) where N at multidimensional.jl:106 zero(::AbstractIrrational) at irrationals.jl:151 ...

We should note that @report_call sum("julia") could detect both of those two different errors that can happen at runtime. This is because @report_call does a static analysis – it analyzes the function call in a way that does NOT rely on runtime, and so it can reason about all the possible executions ! This is one of the biggest advantages of static analysis, because other alternatives to check software qualities like "testing" usually rely on runtime and they can only represent a subset of all the possible executions.

As mentioned above, JET is designed to work with just a normal Julia program. Let's define new arbitrary functions and run JET on it:

julia> function foo(s0)
           a = []
           for s in split(s0)
               push!(a, bar(s))
           end
           return sum(a)
       endfoo (generic function with 1 method)
julia> bar(s::String) = parse(Int, s)bar (generic function with 1 method)
julia> @report_call foo("1 2 3")═════ 2 possible errors found ═════ ┌ @ REPL[1]:4 Main.bar(s) │ no matching method found for call signature (Tuple{typeof(Main.bar), SubString{String}}): Main.bar(s::SubString{String}) └───────────── ┌ @ REPL[1]:6 Main.sum(a) ┌ @ reducedim.jl:994 Base.#sum#767(Base.:, Base.pairs(Core.NamedTuple()), #self#, a) ┌ @ reducedim.jl:994 Base._sum(a, dims) ┌ @ reducedim.jl:998 Base.#_sum#769(Base.pairs(Core.NamedTuple()), #self#, a, _3) ┌ @ reducedim.jl:998 Base._sum(Base.identity, a, Base.:) ┌ @ reducedim.jl:999 Base.#_sum#770(Base.pairs(Core.NamedTuple()), #self#, f, a, _4) ┌ @ reducedim.jl:999 Base.mapreduce(f, Base.add_sum, a) ┌ @ reducedim.jl:357 Base.#mapreduce#760(Base.:, Base._InitialValue(), #self#, f, op, A) ┌ @ reducedim.jl:357 Base._mapreduce_dim(f, op, init, A, dims) ┌ @ reducedim.jl:365 Base._mapreduce(f, op, Base.IndexStyle(A), A) ┌ @ reduce.jl:419 Base.mapreduce_empty_iter(f, op, A, Base.IteratorEltype(A)) ┌ @ reduce.jl:367 Base.reduce_empty_iter(Base.MappingRF(f, op), itr, ItrEltype) ┌ @ reduce.jl:371 Base.reduce_empty(op, Base.eltype(itr)) ┌ @ reduce.jl:348 Base.mapreduce_empty(Base.getproperty(op, :f), Base.getproperty(op, :rf), _) ┌ @ reduce.jl:359 Base.reduce_empty(op, T) ┌ @ reduce.jl:339 Base.reduce_empty(Base.+, _) ┌ @ reduce.jl:330 Base.zero(_) ┌ @ missing.jl:106 Base.throw(Base.MethodError(Base.zero, Core.tuple(Base.Any))) │ MethodError: no method matching zero(::Type{Any}): Base.throw(Base.MethodError(Base.zero::typeof(zero), Core.tuple(Base.Any)::Tuple{DataType})::MethodError) └──────────────────

Now let's fix this problematic code. Say, for some reason, we're not interested in errors that may happen in the context of Base – we want to focus on fixing the error that happens from the definition of bar. First, we can fix the definition of bar so that it can accept generic AbstractString input. JET's analysis result can be dynamically updated when we refine a function definition, and so we just need to add a new bar(::AbstractString) definition. As for the second error, we can use the target_modules configuration to limit the analysis scope to the current module context to ignore the possible error that may happen within sum(a)[1].

julia> # hot fix the definition of `bar`
       bar(s::AbstractString) = parse(Int, s)bar (generic function with 2 methods)
julia> # now no errors should be reported ! @report_call target_modules=(@__MODULE__,) foo("1 2 3")No errors detected

So far, we have used the default error analysis pass, which collects problems according to one specific definition of "errors" (see the JET.BasicPass for more details). JET offers other error reporting passes, including the "sound" error detection (JET.SoundPass) as well as the "typo" detection pass (JET.TypoPass)[2]. They can be switched using the mode configuration:

julia> function myifelse(cond, a, b)
           if cond
               return a
           else
               return b
           end
       endmyifelse (generic function with 1 method)
julia> # the default analysis pass doesn't report "non-boolean (T) used in boolean context" error # as far as there is possibility when the condition "can" be bool (NOTE: Bool <: Integer) report_call(myifelse, (Integer, Int, Int))No errors detected
julia> # the sound analyzer doens't permit such a case: it requires the type of a conditional value to be `Bool` strictly report_call(myifelse, (Integer, Int, Int); mode=:sound)═════ 1 possible error found ═════ ┌ @ REPL[1]:2 goto %3 if not cond │ non-boolean (Integer) used in boolean context: goto %3 if not cond::Integer └─────────────
julia> function strange_sum(a) if rand(Bool) undefsum(a) else sum(a) end endstrange_sum (generic function with 1 method)
julia> # the default analysis pass will report both problems: # - `undefsum` is not defined # - `sum(a::Vector{Any})` can throw when `a` is empty @report_call strange_sum([])═════ 2 possible errors found ═════ ┌ @ REPL[4]:3 Main.undefsum(a) │ variable Main.undefsum is not defined └───────────── ┌ @ REPL[4]:5 Main.sum(a) ┌ @ reducedim.jl:994 Base.#sum#767(Base.:, Base.pairs(Core.NamedTuple()), #self#, a) ┌ @ reducedim.jl:994 Base._sum(a, dims) ┌ @ reducedim.jl:998 Base.#_sum#769(Base.pairs(Core.NamedTuple()), #self#, a, _3) ┌ @ reducedim.jl:998 Base._sum(Base.identity, a, Base.:) ┌ @ reducedim.jl:999 Base.#_sum#770(Base.pairs(Core.NamedTuple()), #self#, f, a, _4) ┌ @ reducedim.jl:999 Base.mapreduce(f, Base.add_sum, a) ┌ @ reducedim.jl:357 Base.#mapreduce#760(Base.:, Base._InitialValue(), #self#, f, op, A) ┌ @ reducedim.jl:357 Base._mapreduce_dim(f, op, init, A, dims) ┌ @ reducedim.jl:365 Base._mapreduce(f, op, Base.IndexStyle(A), A) ┌ @ reduce.jl:419 Base.mapreduce_empty_iter(f, op, A, Base.IteratorEltype(A)) ┌ @ reduce.jl:367 Base.reduce_empty_iter(Base.MappingRF(f, op), itr, ItrEltype) ┌ @ reduce.jl:371 Base.reduce_empty(op, Base.eltype(itr)) ┌ @ reduce.jl:348 Base.mapreduce_empty(Base.getproperty(op, :f), Base.getproperty(op, :rf), _) ┌ @ reduce.jl:359 Base.reduce_empty(op, T) ┌ @ reduce.jl:339 Base.reduce_empty(Base.+, _) ┌ @ reduce.jl:330 Base.zero(_) ┌ @ missing.jl:106 Base.throw(Base.MethodError(Base.zero, Core.tuple(Base.Any))) │ MethodError: no method matching zero(::Type{Any}): Base.throw(Base.MethodError(Base.zero::typeof(zero), Core.tuple(Base.Any)::Tuple{DataType})::MethodError) └──────────────────
julia> # the typo dection pass will only report the "typo" @report_call mode=:typo strange_sum([])═════ 1 possible error found ═════ ┌ @ REPL[4]:3 Main.undefsum(a) │ variable Main.undefsum is not defined └─────────────

We can use @test_call and test_call to assert that your program is free from problems that @report_call can detect. They work nicely with Test standard library's unit-testing infrastructure:

julia> @test_call target_modules=(@__MODULE__,) foo("1 2 3")Test Passed
julia> using Test
julia> # we can get the nice summery using `@testset` ! @testset "JET testset" begin @test_call target_modules=(@__MODULE__,) foo("1 2 3") # should pass test_call(myifelse, (Integer, Int, Int); mode=:sound) @test_call broken=true foo("1 2 3") # `broken` and `skip` options are supported @test foo("1 2 3") == 6 # of course other `Test` macros can be used in the same place endJET testset: JET-test failed at /home/runner/work/JET.jl/JET.jl/src/JET.jl:1492 Expression: (JET.test_call)(Main.myifelse, (Integer, Int64, Int64); mode = sound) ═════ 1 possible error found ═════ ┌ @ REPL[1]:2 goto %3 if not cond │ non-boolean (Integer) used in boolean context: goto %3 if not cond::Integer └───────────── Test Summary: | Pass Fail Broken Total Time JET testset | 2 1 1 4 3.1s ERROR: Some tests did not pass: 2 passed, 1 failed, 0 errored, 1 broken.

JET actually uses JET itself in its test pipeline. JET's static analysis has been proven to be very useful and helped its development a lot. If interested, take a peek at JET's "self check !!!" testset.

Lastly, let's see the example that demonstrates JET can analyze "top-level" program. The top-level analysis should be considered as a somewhat experimental feature, and at this moment you may need additional configurations to run it correctly. Please read the descriptions of top-level entry points and choose an appropriate entry point for your use case. Here we run report_file on demo.jl. It automatically extracts and loads "definitions" of functions, structs and such, and then analyzes their "usages" statically:

julia> report_file(normpath(Base.pkgdir(JET), "demo.jl"))[toplevel-info] applied JET configurations in /home/runner/work/JET.jl/JET.jl/.JET.toml
[toplevel-info] virtualized the context of Main (took 0.005 sec)
[toplevel-info] entered into /home/runner/work/JET.jl/JET.jl/demo.jl
[toplevel-info]  exited from /home/runner/work/JET.jl/JET.jl/demo.jl (took 3.384 sec)

[toplevel-info] analyzing from top-level definitions ... 1/9
[toplevel-info] analyzing from top-level definitions ... 2/9
[toplevel-info] analyzing from top-level definitions ... 3/9
[toplevel-info] analyzing from top-level definitions ... 4/9
[toplevel-info] analyzing from top-level definitions ... 5/9
[toplevel-info] analyzing from top-level definitions ... 6/9
[toplevel-info] analyzing from top-level definitions ... 7/9
[toplevel-info] analyzing from top-level definitions ... 8/9
[toplevel-info] analyzing from top-level definitions ... 9/9
═════ 8 possible errors found ═════
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:10 fib(m)
│ variable m is not defined
└──────────────────────────────────────────────
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:11 fib("1000")
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:7 ≤(n, 2)
┌ @ operators.jl:392 Base.<(x, y)
┌ @ operators.jl:343 Base.isless(x, y)
│ no matching method found for call signature (Tuple{typeof(isless), String, Int64}): Base.isless(x::String, y::Int64)
└────────────────────
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:32 foo(1.2)
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:24 bar(v)
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:29 Base.getproperty(v, :fdl)
┌ @ Base.jl:37 Base.getfield(x, f)
│ type Ty{Float64} has no field fdl
└──────────────
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:33 foo("1")
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:24 bar(v)
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:30 convert(Number, Base.getproperty(v, :fld))
│ no matching method found for call signature (Tuple{typeof(convert), Type{Number}, String}): convert(Number, Base.getproperty(v::Ty{String}, :fld::Symbol)::String)
└──────────────────────────────────────────────
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:44 badmerge(Core.apply_type(Core.NamedTuple, (:x, :y))(Core.tuple(1, 2)), Core.apply_type(Core.NamedTuple, (:y, :z))(Core.tuple(3, 1)))
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:37 getfield(a, x)
│ variable x is not defined
└──────────────────────────────────────────────
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:37 getfield(b, y)
│ variable y is not defined
└──────────────────────────────────────────────
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:37 getfield(b, z)
│ variable z is not defined
└──────────────────────────────────────────────
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:24 bar(v)
┌ @ /home/runner/work/JET.jl/JET.jl/demo.jl:29 Base.getproperty(v, :fdl)
┌ @ Base.jl:37 Base.getfield(x, f)
│ type Ty{T} where T<:Number has no field fdl
└──────────────

Entry Points

Interactive Entry Points

JET offers interactive analysis entry points that can be used similarly to code_typed and its family:

JET.@report_callMacro
@report_call [jetconfigs...] f(args...)

Evaluates the arguments to the function call, determines its types, and then calls report_call on the resulting expression. As with @code_typed and its family, any of JET configurations can be given as the optional arguments like this:

# reports `rand(::Type{Bool})` with `aggressive_constant_propagation` configuration turned off
julia> @report_call aggressive_constant_propagation=false rand(Bool)
source
JET.report_callFunction
report_call(f, types = Tuple{};
            analyzer::Type{<:AbstractAnalyzer} = JETAnalyzer,
            jetconfigs...) -> JETCallResult
report_call(tt::Type{<:Tuple};
            analyzer::Type{<:AbstractAnalyzer} = JETAnalyzer,
            jetconfigs...) -> JETCallResult

Analyzes the generic function call with the given type signature with analyzer. And finally returns the analysis result as JETCallResult.

source

Test Integration

JET also exports entries that are fully integrated with Test standard library's unit-testing infrastructure. It can be used in your test suite to assert your program is free from errors that JET can detect:

JET.@test_callMacro
@test_call [jetconfigs...] [broken=false] [skip=false] f(args...)

Runs @report_call jetconfigs... f(args...) and tests that the generic function call f(args...) is free from problems that @report_call can detect. If executed inside @testset, returns a Pass result if it is, a Fail result if it contains any error points detected, or an Error result if this macro encounters an unexpected error. When the test Fails, abstract call stack to each problem location will also be printed to stdout.

julia> @test_call sincos(10)
Test Passed
  Expression: #= none:1 =# JET.@test_call sincos(10)

As with @report_call, any of JET configurations or analyzer specific configurations can be given as the optional arguments jetconfigs... like this:

julia> cond = false

julia> function f(n)
           # `cond` is untyped, and will be reported by the sound analysis pass,
           # while JET's default analysis pass will ignore it
           if cond
               return sin(n)
           else
               return cos(n)
           end
       end;

julia> @test_call f(10)
Test Passed
  Expression: #= none:1 =# JET.@test_call f(10)

julia> @test_call mode=:sound f(10)
JET-test failed at none:1
  Expression: #= none:1 =# JET.@test_call mode = :sound f(10)
  ═════ 1 possible error found ═════
  ┌ @ none:2 goto %4 if not Main.cond
  │ non-boolean (Any) used in boolean context: goto %4 if not Main.cond
  └──────────

ERROR: There was an error during testing

@test_call is fully integrated with Test standard library's unit-testing infrastructure. It means, the result of @test_call will be included in the final @testset summary, it supports skip and broken annotations as like @test and its family:

julia> using JET, Test

# Julia can't propagate the type constraint `ref[]::Number` to `sin(ref[])`, JET will report `NoMethodError`
julia> f(ref) = isa(ref[], Number) ? sin(ref[]) : nothing;

# we can make it type-stable if we extract `ref[]` into a local variable `x`
julia> g(ref) = (x = ref[]; isa(x, Number) ? sin(x) : nothing);

julia> @testset "check errors" begin
           ref = Ref{Union{Nothing,Int}}(0)
           @test_call f(ref)             # fail
           @test_call g(ref)             # fail
           @test_call broken=true f(ref) # annotated as broken, thus still "pass"
       end
check errors: JET-test failed at none:3
  Expression: #= none:3 =# JET.@test_call f(ref)
  ═════ 1 possible error found ═════
  ┌ @ none:1 Main.sin(Base.getindex(ref))
  │ for 1 of union split cases, no matching method found for call signatures (Tuple{typeof(sin), Nothing})): Main.sin(Base.getindex(ref::Base.RefValue{Union{Nothing, Int64}})::Union{Nothing, Int64})
  └──────────

Test Summary: | Pass  Fail  Broken  Total
check errors  |    1     1       1      3
ERROR: Some tests did not pass: 1 passed, 1 failed, 0 errored, 1 broken.
source
JET.test_callFunction
test_call(f, types = Tuple{}; broken::Bool = false, skip::Bool = false, jetconfigs...)
test_call(tt::Type{<:Tuple}; broken::Bool = false, skip::Bool = false, jetconfigs...)

Runs report_call(f, types; jetconfigs... and tests that the generic function call f(args...) is free from problems that report_call can detect. Except that it takes a type signature rather than a call expression, this function works in the same way as @test_call.

source

Top-level Entry Points

JET can also analyze your "top-level" program: it can just take your Julia script or package and will report possible errors.

Note that JET will analyze your code "half-statically": JET will selectively interpret "definitions" (like a function or struct definition) and try to simulate Julia's top-level code execution. While it tries to avoid executing any other parts of code like function calls, but analyzes them based on abstract interpretation instead (and this is a part where JET statically analyzes your code). If you're interested in how JET selects "top-level definitions", please see JET.virtual_process.

Warning

Because JET will actually interpret "definitions" in your code, that part of top-level analysis certainly runs your code. So we should note that JET can cause some side effects from your code; for example JET will try to expand all the macros used in your code, and so the side effects involved with macro expansions will also happen in JET's analysis process.

JET.report_fileFunction
report_file(filename::AbstractString;
            jetconfigs...) -> JETToplevelResult

Analyzes filename and returns JETToplevelResult.

This function will look for .JET.toml configuration file in the directory of filename, and search up the file tree until any .JET.toml is (or isn't) found. When found, the configurations specified in the file will be applied. See JET's configuration file for more details.

Tip

When you want to analyze your package, but any file actually using it isn't available, the analyze_from_definitions option can be useful (see ToplevelConfig's analyze_from_definitions option).
For example, JET can analyze JET itself like below:

# from the root directory of JET.jl
julia> report_file("src/JET.jl";
                   analyze_from_definitions = true)

See also: report_package

Note

This function will enable the toplevel_logger configuration by default with the default logging level. You can still explicitly specify and configure it:

report_file(args...;
            toplevel_logger = nothing, # suppress toplevel logger
            jetconfigs...) # other configurations

See JET's logging configurations for more details.

source
JET.report_and_watch_fileFunction
report_and_watch_file(filename::AbstractString;
                      jetconfigs...)

Watches filename and keeps re-triggering analysis with report_file on code update. JET will try to analyze all the included files reachable from filename, and it will re-trigger analysis if there is code update detected in any of the included files.

This function internally uses Revise.jl to track code updates. Revise also offers possibilities to track changes in files that are not directly analyzed by JET, or even changes in Base files. See watch configurations for more details.

See also: report_file

source
JET.report_packageFunction
report_package(package::Union{AbstractString,Module};
               jetconfigs...) -> JETToplevelResult

Analyzes package in the same way as report_file with the special default configurations, which are especially tuned for package analysis (see below for details). package can be either a Module or a String. In the latter case it must be the name of a package in your current environment.

This function configures analysis with the following configurations:

  • analyze_from_definitions = true: allows JET to enter analysis without top-level call sites; this is useful for package analysis since a package itself usually has only definitions but not usages (i.e. call sites)
  • concretization_patterns = [:(x_)]: concretizes every top-level code in a given package; concretizations are generally preferred for successful analysis as far as they're cheap, and a package definition doesn't contain heavy computations in general cases

See ToplevelConfig for more details.


report_package([io::IO = stdout];
               jetconfigs...) -> res::ReportResult

Like above but analyzes the package of the current project.

See also: report_file

source

Configurations

In addition to general configurations, the error analysis can take the following specific configurations:

JET.JETAnalyzerType

Every entry point of error analysis can accept any of general JET configurations as well as the following additional configurations that are specific to the error analysis.


  • mode::Symbol = :basic:
    Switches the error analysis pass. Each analysis pass reports errors according to their own "error" definition. JET by default offers the following modes:

    • mode = :basic: the default error analysis pass. This analysis pass is tuned to be useful for general Julia development by reporting common problems, but also note that it is not enough strict to guarantee that your program never throws runtime errors.
      See BasicPass for more details.
    • mode = :sound: the sound error analysis pass. If this pass doesn't report any errors, then your program is assured to run without any runtime errors (unless JET's error definition is not accurate and/or there is an implementation flaw).
      See SoundPass for more details.
    • mode = :typo: a typo detection pass A simple analysis pass to detect "typo"s in your program. This analysis pass is essentially a subset of the default basic pass (BasicPass), and it only reports undefined global reference and undefined field access. This might be useful especially for a very complex code base, because even the basic pass tends to be too noisy (spammed with too many errors) for such a case.
      See TypoPass for more details.
    Note

    You can also set up your own analysis using JET's AbstractAnalyzer-Framework.


source
JET.BasicPassType

The basic (default) error analysis pass.

TODO: elaborate the definitions of "error"s.

source
JET.SoundPassType

The sound error analysis pass.

TODO: elaborate the definitions of "error"s.

source
JET.TypoPassType

A typo detection pass.

TODO: elaborate the definitions of "error"s.

source
  • 1We used target_modules just for the sake of demonstration. To make it idiomatic, we can initialize a as typed vector a = Int[], and then we won't get any problem from sum(a) even without the target_modules configuration.
  • 2Actually JET offers the framework to define your own abstract interpretation based analysis. See AbstractAnalyzer-Framework if interested.