Revise.jl

repository·master·Indexed 23 days ago

https://github.com/timholy/revise.jl

A tool for Julia developers that allows modifying code and immediately using those changes in an active REPL session without restarting Julia. It supports updating packages, switching git branches, and editing source code, eliminating the overhead of restarting and JIT compilation. Features include automatic startup configuration, support for various Julia release cycles, integration with IJulia, and the ability to track individual files via `includet` or packages via `Pkg.dev`.

Tokens
8.6K
Snippets
18
Records
58
Agent score
80%

What's inside Revise.jl

  1. What is Revise.jl

    master
    Revise.jl allows you to modify code and use the changes without restarting your Julia session. It enables a workflow where you can update packages, switch git branches, or edit source code in your editor, and the changes are typically incorporated into the very next command you issue from the REPL. This eliminates the overhead of restarting Julia, reloading packages, and waiting for JIT compilation.
  2. Core Revise functions for manual use

    master

    Most users interact with Revise through a small set of primary functions used to load code, track changes, or manage the state of the REPL session. Key functions include:

    • revise: The primary entry point for managing code revisions.
    • includet or @includet: Used to include a file and track it for changes.
    • Revise.track: Manually tells Revise to track a specific module or file.
    • entr: Used for handling entry points or specific file entries.
    • Revise.retry: Attempts to re-run a failed command.
    • Revise.errors: Accesses or manages error states.
    • Revise.duplicate_methods: Identifies or manages duplicate method definitions caused by reloading.
    • Revise.stale_load: Handles loading of stale modules.
  3. Toplevel binding changes do not propagate

    master

    Revise does not track implicit dependencies between top-level bindings. If a struct or function depends on a top-level binding (like a const or a global variable), changing that binding will not trigger a re-evaluation of the dependent code.

    Example of failure:

    MyVecType{T} = Vector{T}
    struct MyVec{T}
        v::MyVecType{T}
    end

    Changing MyVecType{T} to AbstractVector{T} will not update MyVec automatically.

    Workaround: Manually call Revise.revise(ModuleName) to force a re-evaluation of all definitions within that module.

  4. How struct revision works in Revise.jl

    master

    Starting with Julia 1.12, Revise supports changes to struct definitions. When you modify a struct, Revise automatically re-evaluates the struct definition and any dependent methods or types.

    Example of automatic propagation: If you have a struct Inner used as a field in Outer, and a function print_value(o::Outer), changing the fields of Inner will trigger a re-evaluation of Outer and print_value automatically.

    Limitation: On Julia versions older than 1.12, struct revisions are not supported and require a session restart.

  5. How Revise computes method signatures using lowered code

    master

    Since version 2.0, Revise uses lowered-code representations to compute method signatures. This approach is more robust than using standard Julia code because:

    • Keyword Arguments: It correctly handles the expansion of keyword-argument methods into multiple specialized methods.
    • Generated Methods: It can reliably compute signatures for methods generated by code (e.g., inside a loop using @eval) by stepping through the code using JuliaInterpreter.

    Revise steps through the lowered code and looks for :method expressions. When it finds one, it extracts the signature (the arguments and types) without evaluating the entire expression, which avoids unnecessary recompilation.

    Note on Code Execution: To avoid running dangerous or side-effect-heavy code (like init_c_library()) during signature computation, Revise uses backedges to analyze dependencies. It only interprets code blocks that are strictly necessary to reach a :method, :struct_type, or :eval expression. However, code inside an @eval block may still be executed because Revise cannot reliably predict its expansion.

  6. What Revise can track

    master

    Revise can track changes in several types of code, including:

    • Any package loaded with import or using.
    • Any script loaded with includet (note: default restrictions may apply).
    • Base Julia itself (requires Revise.track(Base)).
    • Julia standard libraries (e.g., Revise.track(Unicode)).
    • Core.Compiler (requires building Julia from source).

    To track Base, use:

    Revise.track(Base)
  7. Understand 'skipping git tests' warning

    master

    The warning skipping git tests because Revise is not under development is harmless.

    This occurs because Revise uses its own git repository for testing its git-based source extraction functionality. These tests only run if you have checked out Revise for development using pkg> dev Revise or if you are running on a CI server.

  8. How Revise works

    master

    Revise enables live code updates in a running Julia session by evaluating only the specific changes (diffs) made to your code, rather than reloading entire modules. This approach minimizes re-JITting and allows for changes to be detected independently of line numbers (e.g., moving code within a file does not trigger a full re-evaluation).

    Conceptually, Revise implements a diff and patch mechanism for the running session. Its core logic follows this pattern:

    1. Identify expressions (def) present in the old state but missing in the new state, and delete the corresponding methods.
    2. Identify expressions (def) present in the new state but missing in the old state, and evaluate them using Core.eval in the appropriate module.

    Revise achieves this through several automated steps:

    • Callbacks: Hooks into Base to detect when new packages are loaded or files are included.
    • Source-code Caching: Maintains a cache for every file to detect changes. For precompiled packages, it uses the source in *.ji files; for non-precompiled packages, it parses the source immediately upon inclusion.
    • File Monitoring: Watches the file system for changes to dependent files.
    • REPL Interception: Intercepts the REPL backend to trigger the revision process after every command execution.
    • Revision Loop: Re-parses changed files, creates a diff against the cache, evals the diff, and updates the cache.
  9. Configure the `__revise_mode__` for modules

    master

    You can control how Revise tracks changes within a specific module by defining a __revise_mode__ variable inside that module. This variable must be a Symbol.

    Available modes:

    • :eval: Evaluate everything (default for packages).
    • :evalmeth: Evaluate changes to method definitions only (default for includet).
    • :evalassign: Evaluate method definitions and top-level assignment statements (e.g., a = Int[]).
    • : :sigs`: Only scan method signatures to update their locations; no changes are implemented.
  10. How Revise manages code updates (Forward and Backward Workflows)

    master

    Revise operates using two complementary workflows to bridge the gap between source text and compiled machine code:

    1. Forward Workflow: Converts source code into running code. It starts with strings from text files, parses them into Julia expressions, evaluates them into Julia objects, and compiles them into machine code.
    2. Backward Workflow: Converts compiled code back to source information. This is used to provide accurate stack traces and debugging information. Because Julia's internal 'breadcrumbs' (filename/line number) are static and don't update when files change, Revise corrects these line numbers so debuggers and error messages point to the correct location in your modified source files.

    Revise maintains internal data structures that parallel Julia's own processing to ensure these workflows stay synchronized.

  11. Track package development with `dev` instead of `add`

    master

    To ensure Revise tracks your changes, you must use the dev command in the Julia package manager (Pkg) rather than add.

    When you use add, Julia installs a registered version of the package into your .julia/packages directory, which is often read-only and not tracked by Revise. Using dev tells the package manager to treat the package as being under active development, typically placing it in your ~/.julia/dev folder where Revise can monitor file changes.

  12. Understand Revise terminology: definition, signature-expression, and signature-type

    master

    Revise uses specific terms to describe the components of a method:

    • Definition: The full expression that defines a method (e.g., the entire function ... end block).
    • Signature-expression: The part of the definition specifying the name, argument names, types, and type-parameters (e.g., print_item(io::IO, item, ntimes::Integer=1, pre::String="")).
    • Signature-type: The specific types generated from a signature-expression that represent valid ways to call the method. For example, a method with two default arguments will generate three distinct signature-types, each corresponding to a different combination of arguments.

    In internal Revise code, a definition is often referred to as def, a signature-type as sigt, and a method table/signature-type pair as mt_sigt.