This PR makes the external checker lean4checker available as the existing `leanchecker` binary already known to elan, allowing for out-of-the-box access to it. --------- Co-authored-by: Kim Morrison <kim@tqft.net> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
115 lines
4.7 KiB
Text
115 lines
4.7 KiB
Text
/-
|
|
Copyright (c) 2023 Kim Morrison. All rights reserved.
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
Authors: Kim Morrison, Sebastian Ullrich
|
|
-/
|
|
import Lean.CoreM
|
|
import Lean.Replay
|
|
import LeanChecker.Replay
|
|
import Lake.Load.Manifest
|
|
|
|
open Lean
|
|
|
|
unsafe def replayFromImports (module : Name) : IO Unit := do
|
|
let mFile ← findOLean module
|
|
unless (← mFile.pathExists) do
|
|
throw <| IO.userError s!"object file '{mFile}' of module {module} does not exist"
|
|
-- Load all module data parts (exported, server, private)
|
|
let mut fnames := #[mFile]
|
|
let sFile := OLeanLevel.server.adjustFileName mFile
|
|
if (← sFile.pathExists) then
|
|
fnames := fnames.push sFile
|
|
let pFile := OLeanLevel.private.adjustFileName mFile
|
|
if (← pFile.pathExists) then
|
|
fnames := fnames.push pFile
|
|
let parts ← readModuleDataParts fnames
|
|
if h : parts.size = 0 then throw <| IO.userError "failed to read module data" else
|
|
let (mod, _) := parts[0]
|
|
let (_, s) ← importModulesCore mod.imports |>.run
|
|
let env ← finalizeImport s mod.imports {} 0 false false (isModule := true)
|
|
let mut newConstants := {}
|
|
-- Collect constants from last ("most private") part, which subsumes all prior ones
|
|
for name in parts[parts.size-1].1.constNames, ci in parts[parts.size-1].1.constants do
|
|
newConstants := newConstants.insert name ci
|
|
let env' ← env.replay' newConstants
|
|
env'.freeRegions
|
|
|
|
unsafe def replayFromFresh (module : Name) : IO Unit := do
|
|
Lean.withImportModules #[{module}] {} fun env => do
|
|
discard <| (← mkEmptyEnvironment).replay' env.constants.map₁
|
|
|
|
/-- Read the name of the main module from the `lake-manifest`. -/
|
|
-- This has been copied from `ImportGraph.getCurrentModule` in the
|
|
-- https://github.com/leanprover-community/import-graph repository.
|
|
def getCurrentModule : IO Name := do
|
|
match (← Lake.Manifest.load? ⟨"lake-manifest.json"⟩) with
|
|
| none =>
|
|
-- TODO: should this be caught?
|
|
pure .anonymous
|
|
| some manifest =>
|
|
-- TODO: This assumes that the `package` and the default `lean_lib`
|
|
-- have the same name up to capitalisation.
|
|
-- Would be better to read the `.defaultTargets` from the
|
|
-- `← getRootPackage` from `Lake`, but I can't make that work with the monads involved.
|
|
return manifest.name.capitalize
|
|
|
|
|
|
/--
|
|
Run as e.g. `leanchecker` to check everything in the current project.
|
|
or e.g. `leanchecker Mathlib.Data.Nat` to check everything with module name
|
|
starting with `Mathlib.Data.Nat`.
|
|
|
|
This will replay all the new declarations from the target file into the `Environment`
|
|
as it was at the beginning of the file, using the kernel to check them.
|
|
|
|
You can also use `leanchecker --fresh Mathlib.Data.Nat.Prime.Basic`
|
|
to replay all the constants (both imported and defined in that file) into a fresh environment.
|
|
This can only be used on a single file.
|
|
|
|
This is not an external verifier, simply a tool to detect "environment hacking".
|
|
-/
|
|
unsafe def main (args : List String) : IO UInt32 := do
|
|
-- Contributor's note: lean4lean is intended to have a CLI interface matching leanchecker,
|
|
-- so if you want to make a change here please either make a sibling PR to
|
|
-- https://github.com/digama0/lean4lean or ping @digama0 (Mario Carneiro) to go fix it.
|
|
initSearchPath (← findSysroot)
|
|
let (flags, args) := args.partition fun s => s.startsWith "-"
|
|
let verbose := "-v" ∈ flags || "--verbose" ∈ flags
|
|
let fresh := "--fresh" ∈ flags
|
|
let targets ← do
|
|
match args with
|
|
| [] => pure [← getCurrentModule]
|
|
| args => args.mapM fun arg => do
|
|
let mod := arg.toName
|
|
if mod.isAnonymous then
|
|
throw <| IO.userError s!"Could not resolve module: {arg}"
|
|
else
|
|
pure mod
|
|
let mut targetModules := []
|
|
let sp ← searchPathRef.get
|
|
for target in targets do
|
|
let mut found := false
|
|
for path in (← SearchPath.findAllWithExt sp "olean") do
|
|
if let some m := (← searchModuleNameOfFileName path sp) then
|
|
if !fresh && target.isPrefixOf m || target == m then
|
|
targetModules := targetModules.insert m
|
|
found := true
|
|
if not found then
|
|
throw <| IO.userError s!"Could not find any oleans for: {target}"
|
|
if fresh then
|
|
if targetModules.length != 1 then
|
|
throw <| IO.userError s!"--fresh flag is only valid when specifying a single module:\n\
|
|
{targetModules}"
|
|
for m in targetModules do
|
|
if verbose then IO.println s!"replaying {m} with --fresh"
|
|
replayFromFresh m
|
|
else
|
|
let mut tasks := #[]
|
|
for m in targetModules do
|
|
tasks := tasks.push (m, ← IO.asTask (replayFromImports m))
|
|
for (m, t) in tasks do
|
|
if verbose then IO.println s!"replaying {m}"
|
|
if let .error e := t.get then
|
|
IO.eprintln s!"leanchecker found a problem in {m}"
|
|
throw e
|
|
return 0
|