After building Lean from source, the next step is wiring up your editor so it uses the freshly builtDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/leanprover/lean4/llms.txt
Use this file to discover all available pages before exploring further.
stage1 binary when you edit files inside the Lean repository. This page walks through the full development cycle: editor setup, the bootstrapping pipeline, the test suite, the commit convention, and debugging.
Setting Up Your Editor with elan
elan is Lean’s toolchain manager. It reads lean-toolchain files in directories to know which binary to invoke for lean, leanc, and leanmake. The Lean 4 repository contains two such files:
- Files under
src/use thelean4-stage0toolchain (the bootstrap compiler). - Files under
tests/use thelean4toolchain (thestage1output).
src/ from VS Code or Emacs will automatically use stage0/bin/lean for elaboration, and any file in tests/ will use stage1/bin/lean.
Install elan without a default toolchain if you do not want to affect your system-wide Lean setup:
Switching toolchains on the fly
Use the+toolchain prefix for one-off invocations:
VS Code setup
The repository ships a.vscode/ directory with pre-configured settings, recommended extensions, and build tasks. Simply open the repository root:
lean you can reload the server inside VS Code:
- Refresh File Dependencies — reloads the worker for the current file. Use this after most changes (does not require restarting the watchdog).
- Restart Server — restarts both the watchdog and all workers. Use this after changing watchdog code or updating
stage0.
The Bootstrapping Pipeline
Lean is a bootstrapped compiler: the frontend, elaborator, and compiler backend are written in Lean itself. This creates a chicken-and-egg dependency that is broken using pre-built C sources checked intostage0/src/.
The three-stage build
stage1/bin/lean contains your changes but was compiled by stage0, so its own compilation was not influenced by them. This means:
- Changes to the elaborator, parser, or tactic system are exercised when
stage1compiles files intests/. The stage1 compiler itself is not self-hosting your changes yet. - Changes that affect the compiled output (
.oleanformat, code generator) may requirestage2to be fully tested, sincestage2is compiled bystage1and both builds and expects the new format.
When to update stage0
stage0 must be updated when you want to use new language features (syntax, tactics, library functions) inside the compiler source itself. The update process is automated by CI:
- Modify
stage0/src/stdlib_flags.h(e.g., add or change a comment) to signal that stage0 is out of date. - Push to your branch. The
update-stage0GitHub Actions workflow runs automatically whensrc/stdlib_flags.handstage0/src/stdlib_flags.hare out of sync.
CCache during development
Lean’s build system uses CCache automatically when available. Without it, everystage0 update triggers a full recompile of all extracted C files. Install CCache and make sure it is on your PATH:
The prelude keyword
All Lean submodules start with the prelude keyword, which disables the automatic import Init. This means each file must explicitly import whatever subset of Init it needs. The benefit is that editing a file in Init does not trigger a full rebuild of the entire Lean module tree.
Testing
Running the full test suite
Running a specific test
Test suite layout
Thetests/ directory uses two conventions:
- Test directory: a directory containing
run_test.sh. The script is run once with the test directory as the working directory. - Test pile: a directory with
run_test.shwhere each.leanfile is a separate test. The script is run once per file.
| Directory | What it tests |
|---|---|
tests/elab/ | Elaboration output (expect exit 0) |
tests/elab_fail/ | Elaboration that should fail (expect exit 1) |
tests/compile/ | Compiled executables, checks stdout |
tests/server/ | LSP server requests |
tests/lake/ | Lake build system |
tests/pkg/ | Tests that run inside a Lake package |
Testing against Mathlib
To check that your PR does not break Mathlib, rebase your branch ontonightly-with-mathlib:
lean-pr-testing-NNNN branch in leanprover-community/mathlib4-nightly-testing and report results back to your PR.
Commit Convention
Lean 4 follows a structured commit message format derived from the AngularJS convention:Type prefixes
| Type | Meaning |
|---|---|
feat | New feature |
fix | Bug fix |
doc | Documentation only |
style | Formatting, missing semicolons, etc. |
refactor | Code restructuring without behavior change |
test | Adding missing tests |
chore | Maintenance (CI, tooling, etc.) |
perf | Performance improvement |
Subject rules
- Use imperative, present tense: “add” not “added” or “adds”
- Do not capitalize the first letter
- No trailing period
Body rules
- Use imperative, present tense
- Explain why the change was made and contrast with previous behavior
- Every
featorfixcommit must have achangelog-*label and a body starting with"This PR "
Footer
- Breaking changes: describe the change, justification, and migration notes
- Closed issues:
Closes #123, #456
Example commit message
PR squash policy
All PRs are squash merged. The final commit message is taken directly from the PR title and description. There is no need to clean up individual commits on your branch. Put questions or extra context that should not appear in the commit history as a PR comment, not in the PR description.Debugging
GDB / LLDB
gdb and lldb can attach to stage1/bin/lean and display C++ stack traces. Lean variables are not yet printable in a human-readable form, but breakpoints and stack traces work:
debug or sandebug preset when you need full debug symbols:
Tracing in the elaborator
InCoreM and derived monads, use trace[myClass] "message {expr}" to emit structured traces viewable with:
| Class | What it traces |
|---|---|
Elab.command | Command macro expansion and elaboration steps |
Elab.step | Individual elaboration steps |
Meta.synthInstance | Type-class synthesis |
Meta.isDefEq | Definitional equality checking |
interpreter | Full interpreter execution (debug builds only) |
dbg_trace:
The rr reverse debugger
For rare but hard-to-reproduce bugs such as reference-counting errors leading to segfaults, the rr reverse debugger allows you to record a trace and replay it backwards:
Avoiding downstream rebuilds
When testing changes against a downstream project without wanting to rebuild its entire.olean cache: