Skip to content

v0.13.0

Choose a tag to compare

@github-actions github-actions released this 24 Oct 16:10
· 11 commits to master since this release

JET v0.13.0

Diff since v0.12.3

CHANGELOG

Added

  • Added support for Julia v1.14. JET now loads full functionality on Julia
    1.14 instead of empty stubs.

  • Added the allow_unsupported_julia preference, which loads full JET
    functionality on unsupported Julia versions, e.g. nightly builds, without
    the development features that JET_DEV_MODE also enables:

    LocalPreferences.toml

    [JET]
    allow_unsupported_julia = true # previously `JET_DEV_MODE = true` was needed

    JET emits a warning when it is loaded on an unsupported Julia version this
    way, since it may not work correctly there.

  • JETInterface now exports has_problems, the predicate that call_test_ex
    and func_test use to decide whether an analysis result fails a test.

  • Calls whose matching methods are ambiguous are now reported as
    AmbiguousMethodErrorReport instead of MethodErrorReport saying that no
    matching method is found. For a union-split call, the ambiguous signatures
    are reported separately from those without any matching method:

    k(::Int, ::Any) = 1
    k(::Any, ::Int) = 2
    m(x::Int) = k(x, x)               # reported: ambiguous `k(::Int, ::Int)`
    n(x::Union{Int,String}) = k(x, x) # reported: ambiguous `k(::Int, ::Int)`
                                      # and no method for `k(::String, ::String)`
  • invoke calls that throw MethodError because no method matches the
    specified signature are now reported as MethodErrorReport in the :basic
    and :sound modes, and those whose matching methods are ambiguous are
    reported as AmbiguousMethodErrorReport:

    f(::Int) = 1
    g(x) = invoke(f, Tuple{String}, x) # reported: no method for `f(::String)`
    h(x) = @invoke f(x::Integer)       # reported: no method for `f(::Integer)`
    k(::Int, ::Any) = 1
    k(::Any, ::Int) = 2
    l(x) = @invoke k(x::Int, x::Int)   # reported: ambiguous `k(::Int, ::Int)`
  • Top-level analysis now reports a @test that always fails, i.e. whose
    expression is inferred to evaluate to false or to a non-boolean value, and
    a @test_throws that always fails, i.e. whose expression is inferred to
    throw only exceptions that do not match the expected one. The errors in the
    expression of @test_throws, which is expected to throw, are no longer
    reported:

    @testset "foo" begin
        x = nothing
        @test x isa Int # reported: always `false`
    end
    @testset "bar" begin
        y = rand((0, nothing))
        @test y         # reported: non-boolean `Union{Nothing, Int64}`
    end
    @testset "baz" begin
        @test_throws MethodError sin("x")   # no longer reported
        @test_throws ArgumentError sin("x") # reported: throws `MethodError`
    end

    @test_broken and @test ex broken=true are not reported, nor are the
    tests in function bodies. @test false is also reported, so use e.g.
    error("unreachable") to mark code that should not be reached. The check of
    @test_throws is conservative: the exceptions inferred across function calls
    are often Any, which matches any expected exception, and message patterns
    are not checked.

Changed

  • Top-level processing now stops entirely at the first fatal parsing, macro
    expansion, lowering, concrete execution, dependency, or include failure.
    No later statements, modules, files, or definition analysis are processed.
    Fatal results are displayed as Top-level analysis failed, with guidance
    to fix the error and rerun the incomplete analysis.
    VirtualProcessResult.toplevel_error_report holds that single
    ToplevelErrorReport, or nothing on success. get_reports on a
    JETToplevelResult still returns a vector: only the fatal error on failure,
    or warnings followed by filtered inference reports on success. Module and
    report_config filtering apply only to inference reports; warnings are
    retained, so successful results can contain a mix of report types. The
    console output prints warnings before inference reports and omits the
    "No errors detected" message when warnings are present. The Test
    integration (test_file, test_text, test_package) does not count
    UnsupportedFeatureReports as failures, but ParseWarningReports still
    fail tests.

    [!WARNING]
    Known limitation: forced interruption skips user finally blocks in
    interpreted code, including during timeout, missing-concretization, and
    fatal include failures. Concrete execution can leave the working directory
    changed or locks held after analysis exits; its side effects are not rolled
    back.

  • Nonfatal JET warnings are stored in
    VirtualProcessResult.toplevel_warning_reports, a
    Vector{ToplevelWarningReport}, and are not also emitted as @warn logs.
    Parser warnings now produce nonfatal ParseWarningReports. Unsupported
    include(mapexpr, filename) and Base.include(mapexpr, mod, filename) calls
    produce UnsupportedFeatureReports and analyze the included file without
    applying mapexpr, in the current or explicitly specified module,
    respectively.

  • The explanatory text of top-level reports is wrapped at 90 columns, or at
    the terminal width when results are shown in a narrower REPL. Error
    messages, stacktraces, and source excerpts are not wrapped.

  • The Top-level analysis failed header is now highlighted in red.

  • Top-level report messages now start with a summary, followed by a blank line
    and the details. Errors from parsing, macro expansion, lowering, and concrete
    execution are summarized with what JET was doing and the first line of the
    error message, e.g.
    JET could not execute this top-level code: UndefVarError: ..., and parser
    diagnostics as Syntax error: ... or Syntax warning: ....
    UnsupportedFeatureReports show a common summary above the description of
    the unsupported feature, and RecursiveIncludeErrorReports list the include
    chain. The VS Code integration keeps the summary on the first line of its
    diagnostics.

  • Report boxes are now closed with lines as wide as the widest message line in
    them. Each inference report used to close only the box of its error frame
    with a fixed-width line, leaving the boxes of its caller frames open. Now one
    line closes every box that the next report does not share:

    g(x) = sin(x)
    h(x) = cos(x)
    f(x) = rand(Bool) ? g(x) : h(x)
    report_call(f, (String,))
    ═════ 2 possible errors found ═════
    ┌ f(x::String) @ Main REPL[3]:1
    │┌ g(x::String) @ Main REPL[1]:1
    ││ no matching method found `sin(::String)`: sin(x::String)
    │└─────────────────────────────────────────────────────────
    │┌ h(x::String) @ Main REPL[2]:1
    ││ no matching method found `cos(::String)`: cos(x::String)
    └┴─────────────────────────────────────────────────────────
    

    Previously, both reports ended with │└────────────────────, leaving the
    box of f open. Top-level report boxes, which ended at the right edge of
    their section header, are sized the same way.

  • Top-level analysis now analyzes test sets as if they were declared with
    failfast=true, where a failing test aborts the test set, so it analyzes the
    code after @test ex assuming that ex is true:

    @testset "foo" begin
        x = rand((0, nothing))
        @test x isa Int
        sin(x) # no longer reported as `sin(::Nothing)`: `x` is an `Int` here
    end

    The new testset_failfast configuration controls this. With
    testset_failfast=false, only the test sets that specify failfast=true are
    analyzed this way, together with the test sets nested in them and the files
    included from them:

    # runtests.jl
    @testset "MyPkg" failfast=true begin
        @testset "foo" include("foo.jl") # the test sets in foo.jl are analyzed the same way
    end

    Test sets that specify failfast=false and test sets of a custom type, e.g.
    @testset MyTestSet ..., are analyzed as declared. Note that the code after a
    test that always fails is not analyzed. Likewise, regardless of
    testset_failfast, the code after @test_warn, @test_nowarn, @inferred,
    and @testset let ... end is not analyzed if they always throw, since
    Test.jl does not catch exceptions in them. The values of @test_warn,
    @test_nowarn, and @inferred also keep their inferred types, e.g. y of
    y = @inferred parse(Int, "1") is inferred as Int instead of Any.

  • VirtualFrame has the new pc field, the index of the statement that the
    frame points to in the code analyzed for its MethodInstance, or 0 if the
    frame does not point to a statement. Frames at different statements on the
    same line are no longer equal:

    VirtualFrame(file, line, linfo, pc) # was `VirtualFrame(file, line, linfo)`
    first(report.vst).pc                # the statement where `report` was found

Deprecated

  • The print_toplevel_success and print_inference_success configurations
    are deprecated and will be removed in a future release. Passing either one
    emits Base.depwarn. print_toplevel_success now has no effect; displayed
    analysis results never printed its message. print_inference_success keeps
    working until its removal; use JET.has_problems(result) to check whether
    an analysis result has problems.

  • VirtualProcessResult.toplevel_error_reports is deprecated and will be
    removed in a future release. Access emits Base.depwarn and returns a fresh
    empty or one-element Vector; propertynames lists the property only when
    private names are requested. Migrate to toplevel_error_report: replace
    isempty(res.toplevel_error_reports) with
    isnothing(res.toplevel_error_report) and use the singular report directly
    when non-nothing. For a vector of reports from a JETToplevelResult, use
    JET.get_reports(result) instead.

Fixed

  • Fixed top-level analysis crashing with an AssertionError when a docstring
    is attached to an expression that cannot be documented, such as an if
    block. The analysis now stops with the error that Julia raises for such
    code, cannot document the following expression.

  • Fixed top-level analysis concretizing the code after a try block, such as
    a Base.ScopedValues.@with block, within the block's exception handler and
    dynamic scope when the block contains definitions. Such code could read the
    scoped values set by the block, and an error thrown by it ran the code after
    the block again.

  • Fixed a WARNING: Detected access to binding ... in a world prior to its definition world message that top-level analysis (e.g. report_file) could
    print when reading a constant declared earlier in the same top-level block,
    such as a global const inside a let block.

  • Fixed a WARNING: Detected access to binding ... in a world prior to its definition world message that report_opt could print when reporting a
    runtime dispatch to a function defined after JET was loaded.

  • Fixed top-level analysis treating any function named include as Julia's
    file loader instead of executing its body. Custom wrappers now run their
    own logic, such as path transformations, before calls to the standard
    include are intercepted during recursive interpretation.

  • Fixed printing of a report about an include call with arguments that no
    include method accepts, such as include(Symbol("file.jl")), which threw
    a TypeError instead of showing the MethodError.

  • Fixed reports about an include call, such as a failure to read the
    included file or a RecursiveIncludeErrorReport, pointing to the first line
    of the enclosing top-level statement when the call is inside a block or a
    function. They now point to the line of the include call, or to the line
    that calls the enclosing function when that function is defined in another
    file.

  • Fixed reports in code checked by Test.jl macros, such as the body of a
    @testset or a multi-line @test expression, pointing to the first line of
    the enclosing top-level statement since 0.12.1. They now point to the line
    of the reported code.

  • Fixed package analysis (e.g. report_package) of a package that looks
    itself up with pathof, pkgdir, or pkgversion, which returned nothing
    for the analyzed package module and could stop the analysis:

    module MyPkg
    const SRCDIR = dirname(pathof(@__MODULE__)) # was a `MethodError`
    end

    Base.PkgId(@__MODULE__) of the analyzed package module now differs from
    that of the actual package, so lookups such as
    Base.identify_package(Base.PkgId(@__MODULE__), name) fail.

  • Fixed the loop bodies of Threads.@threads not being analyzed on Julia
    1.13.2 and later, where its tasks call the loop body through a dynamic
    dispatch (JuliaLang/julia#63184). The loop body is now analyzed where
    @threads is used, and report_opt no longer reports that dynamic dispatch
    in Base.Threads._threads_call:

    const g = Ref{Any}(1)
    helper(i) = i + g[]
    function f(n)
        Threads.@threads for i in 1:n
            helper(i) # `@report_opt f(10)` reports this runtime dispatch again
        end
    end
  • Fixed join on a tuple of unknown length, such as one created by splatting
    a vector, being inferred to return Union{AnnotatedString{String},String}
    rather than String, which caused spurious reports for code that requires a
    String (JuliaLang/julia#63999):

    report_call((Vector{String},)) do xs
        pointer(join((xs...,), ", ")) # no longer reported
    end
  • Fixed errors in the function run by a Task not being reported when the
    function had not been analyzed before, as when the task is created by
    Task(f, reserved_stack):

    report_call() do
        t = Task(() -> sum("julia"), 0) # errors in `sum("julia")` now reported
        schedule(t)
        fetch(t)
    end
  • Fixed analysis crashing with Could not find single target method when a
    Task is created from a function whose type is not known, such as
    f::Function:

    g(f::Function) = Task(f)
    report_call(g, (Function,)) # no longer throws
  • Fixed top-level analysis (e.g. report_file) reporting a global variable as
    not defined when the variable is exported after its assignment:

    x = rand(Bool)
    export x
    println(x) # was reported: `x` is not defined
  • Fixed the reports from a call being discarded by the constant propagation of
    another call to the same function on the same line:

    foo(a) = a < 0 ? a + string(a) : a
    bar(x) = (foo(x), foo(1)) # the report from `foo(x)` was discarded
    report_call(bar, (Int,))
  • Fixed conversion intrinsics such as bitcast being reported as called with
    a non-primitive target type when the target type is a Union of primitive
    types:

    # was reported: bitcast: target type not a leaf primitive type
    report_call(signed, (Base.BitUnsigned,))
  • Fixed reports about getfield or setfield! on a type object listing the
    fields of that type instead of the fields of the type object itself, and
    the analysis of field accesses throwing on a field name that is neither a
    Symbol nor an Int, or on a tuple of unknown length:

    struct Foo; x::Int; end
    # now reported: type DataType has no field `y`
    report_call(() -> getfield(Foo, :y))
    report_call(() -> getfield(Foo, "y")) # no longer throws
  • Fixed Core.invoke calls with a CodeInstance being misclassified as invalid
    invoke calls when the invoked code always throws. Callee diagnostics for
    direct CodeInstance calls are not yet collected.

Merged pull requests:

  • toplevel: Stop analysis at the first fatal error (#883) (@aviatesk)
  • toplevel: Record definition locations in SignatureInfo (#886) (@aviatesk)
  • print: Deprecate the success message configurations (#887) (@aviatesk)
  • print: Summarize and wrap top-level report messages (#888) (@aviatesk)
  • build(deps): bump dawidd6/action-download-artifact from 25 to 27 (#889) (@dependabot[bot])
  • Look up callee bindings in the analysis world when building report signatures (#891) (@Beforerr)
  • Fix root-anchored .gitignore entries (#892) (@Beforerr)
  • toplevel: Exit the handlers and scopes entered by concretization (#893) (@aviatesk)
  • toplevel: Narrow types after @test in fail-fast test sets (#895) (@aviatesk)
  • Analyze test sets as fail-fast by default and report @tests that always fail (#896) (@aviatesk)
  • toplevel: Concretize Base.deprecate calls (#897) (@aviatesk)
  • typeinfer: Analyze Threads.@threads loop bodies at call sites (#898) (@aviatesk)
  • toplevel: Keep binding states across partitions replaced by export (#899) (@aviatesk)
  • Propagate exceptions in Test.jl macros and check exceptions of @test_throws (#901) (@aviatesk)
  • jetanalyzer: Add overlay for join on tuples (#902) (@aviatesk)
  • toplevel: Skip native compilation of precompile calls (#903) (@aviatesk)
  • Support Julia v1.14 (#904) (@aviatesk)
  • jetanalyzer: Skip Union target types in conversion checks (#905) (@aviatesk)
  • jetanalyzer: Report MethodError from invoke lookup failures (#906) (@aviatesk)
  • jetanalyzer: Report ambiguous method calls separately (#907) (@aviatesk)

Closed issues:

  • Threads.@threads loop bodies no longer analyzed on Julia 1.13.2 (#890)
  • AssertionError: invalid cache_argtypes for mi (#894)
  • @test_throws MethodError f(x) reports the MethodError the test expects (#900)