Compare commits

..

2 Commits

Author SHA1 Message Date
Leonardo de Moura
80767c8172 chore: fix test 2025-08-25 15:01:03 -07:00
Leonardo de Moura
a55604651f chore: use ofConstName in error messages 2025-08-25 14:58:19 -07:00
7017 changed files with 56488 additions and 214148 deletions

View File

@@ -1,51 +0,0 @@
To build Lean you should use `make -j -C build/release`.
To run a test you should use `cd tests/lean/run && ./test_single.sh example_test.lean`.
## New features
When asked to implement new features:
* begin by reviewing existing relevant code and tests
* write comprehensive tests first (expecting that these will initially fail)
* and then iterate on the implementation until the tests pass.
All new tests should go in `tests/lean/run/`. These tests don't have expected output; we just check there are no errors. You should use `#guard_msgs` to check for specific messages.
## Success Criteria
*Never* report success on a task unless you have verified both a clean build without errors, and that the relevant tests pass.
## Build System Safety
**NEVER manually delete build directories** (build/, stage0/, stage1/, etc.) even when builds fail.
- ONLY use the project's documented build command: `make -j -C build/release`
- If a build is broken, ask the user before attempting any manual cleanup
## LSP and IDE Diagnostics
After rebuilding, LSP diagnostics may be stale until the user interacts with files. Trust command-line test results over IDE diagnostics.
## Update prompting when the user is frustrated
If the user expresses frustration with you, stop and ask them to help update this `.claude/CLAUDE.md` file with missing guidance.
## Creating pull requests
Follow the commit convention in `doc/dev/commit_convention.md`.
**Title format:** `<type>: <subject>` where type is one of: `feat`, `fix`, `doc`, `style`, `refactor`, `test`, `chore`, `perf`.
Subject should use imperative present tense ("add" not "added"), no capitalization, no trailing period.
**Body format:** The first paragraph must start with "This PR". This paragraph is automatically incorporated into release notes. Use imperative present tense. Include motivation and contrast with previous behavior when relevant.
Example:
```
feat: add optional binder limit to `mkPatternFromTheorem`
This PR adds a `num?` parameter to `mkPatternFromTheorem` to control how many
leading quantifiers are stripped when creating a pattern.
```
## CI Log Retrieval
When CI jobs fail, investigate immediately - don't wait for other jobs to complete. Individual job logs are often available even while other jobs are still running. Try `gh run view <run-id> --log` or `gh run view <run-id> --log-failed`, or use `gh run view <run-id> --job=<job-id>` to target the specific failed job. Sleeping is fine when asked to monitor CI and no failures exist yet, but once any job fails, investigate that failure immediately.

View File

@@ -1,79 +0,0 @@
# Release Management Command
Execute the release process for a given version by running the release checklist and following its instructions.
## Before Starting
**IMPORTANT**: Before beginning the release process, read the in-file documentation:
- Read `script/release_checklist.py` for what the checklist script does
- Read `script/release_steps.py` for what the release steps script does
These comments explain the scripts' behavior, which repositories get special handling, and how errors are handled.
## Arguments
- `version`: The version to release (e.g., v4.24.0)
## Process
1. Run `script/release_checklist.py {version}` to check the current status
2. **CRITICAL: If preliminary lean4 checks fail, STOP immediately and alert the user**
- Check for: release branch exists, CMake version correct, tag exists, release page exists, release notes exist
- **IMPORTANT**: The release page is created AUTOMATICALLY by CI after pushing the tag - DO NOT create it manually
- Do NOT create any PRs or proceed with repository updates if these checks fail
3. Create a todo list tracking all repositories that need updates
4. **CRITICAL RULE: You can ONLY run `release_steps.py` for a repository if `release_checklist.py` explicitly says to do so**
- The checklist output will say "Run `script/release_steps.py {version} {repo_name}` to create it"
- If a repository shows "🟡 Dependencies not ready", you CANNOT create a PR for it yet
- You MUST rerun `release_checklist.py` before attempting to create PRs for any new repositories
5. For each repository that the checklist says needs updating:
- Run `script/release_steps.py {version} {repo_name}` to create the PR
- Mark it complete when the PR is created
6. After creating PRs, notify the user which PRs need review and merging
7. **MANDATORY: Rerun `release_checklist.py` to check current status**
- Do this after creating each batch of PRs
- Do this after the user reports PRs have been merged
- NEVER assume a repository is ready without checking the checklist output
8. As PRs are merged and tagged, dependent repositories will become ready
9. Continue the cycle: run checklist → create PRs for ready repos → wait for merges → repeat
10. Continue until all repositories are updated and the release is complete
## Important Notes
- **NEVER merge PRs autonomously** - always wait for the user to merge PRs themselves
- The `release_steps.py` script is idempotent - it's safe to rerun
- The `release_checklist.py` script is idempotent - it's safe to rerun
- Some repositories depend on others (e.g., mathlib4 depends on batteries, aesop, etc.)
- Wait for user to merge PRs before dependent repos can be updated
- Alert user if anything unusual or scary happens
- Use appropriate timeouts for long-running builds (verso can take 10+ minutes)
- ProofWidgets4 uses semantic versioning (v0.0.X) - it's okay to create and push the next sequential tag yourself when needed for a release
## PR Status Reporting
Every time you run `release_checklist.py`, you MUST:
1. Parse the output to identify ALL open PRs mentioned (lines with "✅ PR with title ... exists")
2. Provide a summary to the user listing ALL open PRs that need review
3. Group them by status:
- PRs for repositories that are blocked by dependencies (show these but note they're blocked)
- PRs for repositories that are ready to merge (highlight these)
4. Format the summary clearly with PR numbers and URLs
This summary should be provided EVERY time you run the checklist, not just after creating new PRs.
The user needs to see the complete picture of what's waiting for review.
## Nightly Infrastructure
The nightly build system uses branches and tags across two repositories:
- `leanprover/lean4` has **branches** `nightly` and `nightly-with-mathlib` tracking the latest nightly builds
- `leanprover/lean4-nightly` has **dated tags** like `nightly-2026-01-23`
When a nightly succeeds with mathlib, all three should point to the same commit. Don't confuse these: branches are in the main lean4 repo, dated tags are in lean4-nightly.
## Error Handling
**CRITICAL**: If something goes wrong or a command fails:
- **DO NOT** try to manually reproduce the failing steps yourself
- **DO NOT** try to fix things by running git commands or other manual operations
- Both scripts are idempotent and designed to handle partial completion gracefully
- If a script continues to fail after retrying, report the error to the user and wait for instructions

7
.gitattributes vendored
View File

@@ -4,10 +4,3 @@ RELEASES.md merge=union
stage0/** binary linguist-generated
# The following file is often manually edited, so do show it in diffs
stage0/src/stdlib_flags.h -binary -linguist-generated
doc/std/grove/GroveStdlib/Generated/** linguist-generated
# These files should not have line endings translated on Windows, because
# it throws off parser tests. Later lines override earlier ones, so the
# runner code is still treated as ordinary text.
tests/lean/docparse/* eol=lf
tests/lean/docparse/*.lean eol=auto
tests/lean/docparse/*.sh eol=auto

View File

@@ -9,7 +9,7 @@ assignees: ''
### Prerequisites
<!-- Please put an X between the brackets as you perform the following steps: -->
Please put an X between the brackets as you perform the following steps:
* [ ] Check that your issue is not already filed:
https://github.com/leanprover/lean4/issues

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: actionlint
uses: raven-actions/actionlint@v2
with:

View File

@@ -12,7 +12,7 @@ jobs:
- name: Check awaiting-manual label
id: check-awaiting-manual-label
if: github.event_name == 'pull_request'
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const { labels, number: prNumber } = context.payload.pull_request;

View File

@@ -12,7 +12,7 @@ jobs:
- name: Check awaiting-mathlib label
id: check-awaiting-mathlib-label
if: github.event_name == 'pull_request'
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const { labels, number: prNumber } = context.payload.pull_request;

View File

@@ -3,6 +3,9 @@ name: build-template
on:
workflow_call:
inputs:
check-level:
type: string
required: true
config:
type: string
required: true
@@ -33,7 +36,7 @@ jobs:
include: ${{fromJson(inputs.config)}}
# complete all jobs
fail-fast: false
runs-on: ${{ endsWith(matrix.os, '-with-cache') && fromJSON(format('["{0}", "nscloud-git-mirror-1gb"]', matrix.os)) || matrix.os }}
runs-on: ${{ matrix.os }}
defaults:
run:
shell: ${{ matrix.shell || 'nix develop -c bash -euxo pipefail {0}' }}
@@ -66,16 +69,10 @@ jobs:
brew install ccache tree zstd coreutils gmp libuv
if: runner.os == 'macOS'
- name: Checkout
if: (!endsWith(matrix.os, '-with-cache'))
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
# the default is to use a virtual merge commit between the PR and master: just use the PR
ref: ${{ github.event.pull_request.head.sha }}
- name: Namespace Checkout
if: endsWith(matrix.os, '-with-cache')
uses: namespacelabs/nscloud-checkout-action@v8
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Open Nix shell once
run: true
if: runner.os == 'Linux'
@@ -113,10 +110,10 @@ jobs:
build/stage1/**/*.ir
build/stage1/**/*.c
build/stage1/**/*.c.o*' || '' }}
key: ${{ matrix.name }}-build-v4-${{ github.sha }}
key: ${{ matrix.name }}-build-v3-${{ github.sha }}
# fall back to (latest) previous cache
restore-keys: |
${{ matrix.name }}-build-v4
${{ matrix.name }}-build-v3
# open nix-shell once for initial setup
- name: Setup
run: |
@@ -172,9 +169,7 @@ jobs:
# Should be done as early as possible and in particular *before* "Check rebootstrap" which
# changes the state of stage1/
- name: Save Cache
# Caching on cancellation created some mysterious issues perhaps related to improper build
# shutdown
if: steps.restore-cache.outputs.cache-hit != 'true' && !cancelled()
if: steps.restore-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
# NOTE: must be in sync with `restore` above
@@ -213,21 +208,21 @@ jobs:
else
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -o pack/$dir.tar.zst
fi
- uses: actions/upload-artifact@v5
- uses: actions/upload-artifact@v4
if: matrix.release
with:
name: build-${{ matrix.name }}
path: pack/*
- name: Lean stats
run: |
build/$TARGET_STAGE/bin/lean --stats src/Lean.lean
build/$TARGET_STAGE/bin/lean --stats src/Lean.lean -Dexperimental.module=true
if: ${{ !matrix.cross }}
- name: Test
id: test
run: |
ulimit -c unlimited # coredumps
time ctest --preset ${{ matrix.CMAKE_PRESET || 'release' }} --test-dir build/$TARGET_STAGE -j$NPROC --output-junit test-results.xml ${{ matrix.CTEST_OPTIONS }}
if: matrix.test
time ctest --preset ${{ matrix.CMAKE_PRESET || 'release' }} --test-dir build/$TARGET_STAGE -j$NPROC --output-junit test-results.xml
if: (matrix.wasm || !matrix.cross) && (inputs.check-level >= 1 || matrix.test)
- name: Test Summary
uses: test-summary/action@v2
with:

View File

@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
# the default is to use a virtual merge commit between the PR and master: just use the PR
ref: ${{ github.event.pull_request.head.sha }}

View File

@@ -8,11 +8,11 @@ jobs:
check-stage0-on-queue:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
filter: blob:none
fetch-depth: 0
filter: tree:0
- name: Find base commit
if: github.event_name == 'pull_request'
@@ -31,7 +31,7 @@ jobs:
- if: github.event_name == 'pull_request'
name: Set label
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const { owner, repo, number: issue_number } = context.issue;

View File

@@ -1,57 +0,0 @@
name: Check stdlib_flags.h modifications
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
check-stdlib-flags:
runs-on: ubuntu-latest
steps:
- name: Check if stdlib_flags.h was modified
uses: actions/github-script@v8
with:
script: |
// Get the list of files changed in this PR
const files = await github.paginate(
github.rest.pulls.listFiles,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
}
);
// Check if stdlib_flags.h was modified
const stdlibFlagsModified = files.some(file =>
file.filename === 'src/stdlib_flags.h'
);
if (stdlibFlagsModified) {
console.log('src/stdlib_flags.h was modified in this PR');
// Check if the unlock label is present
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
const hasUnlockLabel = pr.labels.some(label =>
label.name === 'unlock-upstream-stdlib-flags'
);
if (!hasUnlockLabel) {
core.setFailed(
'src/stdlib_flags.h was modified. This is likely a mistake. If you would like to change ' +
'bootstrapping settings or request a stage0 update, you should modify stage0/src/stdlib_flags.h. ' +
'If you really want to change src/stdlib_flags.h (which should be extremely rare), set the ' +
'unlock-upstream-stdlib-flags label.'
);
} else {
console.log('Found unlock-upstream-stdlib-flags');
}
} else {
console.log('src/stdlib_flags.h was not modified');
}

View File

@@ -31,6 +31,10 @@ jobs:
configure:
runs-on: ubuntu-latest
outputs:
# 0: PRs without special label
# 1: PRs with `merge-ci` label, merge queue checks, master commits
# 2: PRs with `release-ci` label, releases (incl. nightlies)
check-level: ${{ steps.set-level.outputs.check-level }}
# The build matrix, dynamically generated here
matrix: ${{ steps.set-matrix.outputs.matrix }}
# secondary build jobs that should not block the CI success/merge queue
@@ -50,9 +54,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
# don't schedule nightlies on forks
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly' || (startsWith(github.ref, 'refs/tags/') && github.repository == 'leanprover/lean4')
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly'
- name: Set Nightly
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly'
id: set-nightly
@@ -106,54 +110,6 @@ jobs:
TAG_NAME="${GITHUB_REF##*/}"
echo "RELEASE_TAG=$TAG_NAME" >> "$GITHUB_OUTPUT"
- name: Validate CMakeLists.txt version matches tag
if: steps.set-release.outputs.RELEASE_TAG != ''
run: |
echo "Validating CMakeLists.txt version matches tag ${{ steps.set-release.outputs.RELEASE_TAG }}"
# Extract version values from CMakeLists.txt
CMAKE_MAJOR=$(grep -E "^set\(LEAN_VERSION_MAJOR " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_MINOR=$(grep -E "^set\(LEAN_VERSION_MINOR " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_PATCH=$(grep -E "^set\(LEAN_VERSION_PATCH " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | sed -nE 's/^set\(LEAN_VERSION_IS_RELEASE ([0-9]+)\).*/\1/p')
# Expected values from tag parsing
TAG_MAJOR="${{ steps.set-release.outputs.LEAN_VERSION_MAJOR }}"
TAG_MINOR="${{ steps.set-release.outputs.LEAN_VERSION_MINOR }}"
TAG_PATCH="${{ steps.set-release.outputs.LEAN_VERSION_PATCH }}"
ERRORS=""
if [[ "$CMAKE_MAJOR" != "$TAG_MAJOR" ]]; then
ERRORS+="LEAN_VERSION_MAJOR: expected $TAG_MAJOR, found $CMAKE_MAJOR\n"
fi
if [[ "$CMAKE_MINOR" != "$TAG_MINOR" ]]; then
ERRORS+="LEAN_VERSION_MINOR: expected $TAG_MINOR, found $CMAKE_MINOR\n"
fi
if [[ "$CMAKE_PATCH" != "$TAG_PATCH" ]]; then
ERRORS+="LEAN_VERSION_PATCH: expected $TAG_PATCH, found $CMAKE_PATCH\n"
fi
if [[ "$CMAKE_IS_RELEASE" != "1" ]]; then
ERRORS+="LEAN_VERSION_IS_RELEASE: expected 1, found $CMAKE_IS_RELEASE\n"
fi
if [[ -n "$ERRORS" ]]; then
echo "::error::Version mismatch between tag and src/CMakeLists.txt"
echo ""
echo "Tag ${{ steps.set-release.outputs.RELEASE_TAG }} expects version $TAG_MAJOR.$TAG_MINOR.$TAG_PATCH"
echo "But src/CMakeLists.txt has mismatched values:"
echo -e "$ERRORS"
echo ""
echo "Fix src/CMakeLists.txt, delete the tag, and re-tag."
exit 1
fi
echo "Version validation passed: $TAG_MAJOR.$TAG_MINOR.$TAG_PATCH"
# 0: PRs without special label
# 1: PRs with `merge-ci` label, merge queue checks, master commits
# 2: nightlies
# 3: PRs with `release-ci` label, full releases
- name: Set check level
id: set-level
# We do not use github.event.pull_request.labels.*.name here because
@@ -161,39 +117,31 @@ jobs:
# rerun the workflow run after setting the `release-ci`/`merge-ci` labels.
run: |
check_level=0
fast=false
if [[ -n "${{ steps.set-release.outputs.RELEASE_TAG }}" || -n "${{ steps.set-release-custom.outputs.RELEASE_TAG }}" ]]; then
check_level=3
elif [[ -n "${{ steps.set-nightly.outputs.nightly }}" ]]; then
if [[ -n "${{ steps.set-nightly.outputs.nightly }}" || -n "${{ steps.set-release.outputs.RELEASE_TAG }}" || -n "${{ steps.set-release-custom.outputs.RELEASE_TAG }}" ]]; then
check_level=2
elif [[ "${{ github.event_name }}" != "pull_request" ]]; then
check_level=1
else
labels="$(gh api repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }} --jq '.labels')"
if echo "$labels" | grep -q "release-ci"; then
check_level=3
check_level=2
elif echo "$labels" | grep -q "merge-ci"; then
check_level=1
fi
if echo "$labels" | grep -q "fast-ci"; then
fast=true
fi
fi
echo "check-level=$check_level" >> "$GITHUB_OUTPUT"
echo "fast=$fast" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}
- name: Configure build matrix
id: set-matrix
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const level = ${{ steps.set-level.outputs.check-level }};
const fast = ${{ steps.set-level.outputs.fast }};
console.log(`level: ${level}, fast: ${fast}`);
console.log(`level: ${level}`);
// use large runners where available (original repo)
let large = ${{ github.repository == 'leanprover/lean4' }};
const isPr = "${{ github.event_name }}" == "pull_request";
@@ -204,8 +152,7 @@ jobs:
"name": "Linux LLVM",
"os": "ubuntu-latest",
"release": false,
"enabled": level >= 2,
"test": true,
"check-level": 2,
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
@@ -218,19 +165,17 @@ jobs:
{
// portable release build: use channel with older glibc (2.26)
"name": "Linux release",
// usually not a bottleneck so make exclusive to `fast-ci`
"os": large && fast ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
"os": "ubuntu-latest",
"release": true,
// Special handling for release jobs. We want:
// 1. To run it in PRs so developers get PR toolchains (so secondary without tests is sufficient)
// 1. To run it in PRs so developers get PR toolchains (so secondary is sufficient)
// 2. To skip it in merge queues as it takes longer than the
// Linux lake build and adds little value in the merge queue
// 3. To run it in release (obviously)
// 4. To run it for pushes to master so that pushes to master have a Linux toolchain
// available as an artifact for Grove to use.
"enabled": isPr || level != 1 || isPushToMaster,
"test": level >= 1,
"secondary": level == 0,
"check-level": (isPr || isPushToMaster) ? 0 : 2,
"secondary": isPr,
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
@@ -240,62 +185,49 @@ jobs:
},
{
"name": "Linux Lake",
"os": large ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
"enabled": true,
"os": large ? "nscloud-ubuntu-22.04-amd64-8x16" : "ubuntu-latest",
"check-level": 0,
"test": true,
"check-rebootstrap": level >= 1,
"check-stage3": level >= 2,
"test": true,
// NOTE: `test-speedcenter` currently seems to be broken on `ubuntu-latest`
"test-speedcenter": large && level >= 2,
// We are not warning-free yet on all platforms, start here
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-Werror",
// made explicit until it can be assumed to have propagated to PRs
"CMAKE_OPTIONS": "-DUSE_LAKE=ON",
},
{
"name": "Linux Reldebug",
"os": "ubuntu-latest",
"enabled": level >= 2,
"test": true,
"check-level": 2,
"CMAKE_PRESET": "reldebug",
// exclude seriously slow/stackoverflowing tests
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest|bv_bitblast_stress|3807'"
},
{
// TODO: suddenly started failing in CI
/*{
"name": "Linux fsanitize",
// Always run on large if available, more reliable regarding timeouts
"os": large ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
"enabled": level >= 2,
// do not fail nightlies on this for now
"secondary": level <= 2,
"test": true,
"os": "ubuntu-latest",
"check-level": 2,
// turn off custom allocator & symbolic functions to make LSAN do its magic
"CMAKE_PRESET": "sanitize",
// * `StackOverflow*` correctly triggers ubsan.
// * `reverse-ffi` fails to link in sanitizers.
// * `interactive` and `async_select_channel` fail nondeterministically, would need
// to be investigated..
// * 9366 is too close to timeout.
// * `bv_` sometimes times out calling into cadical even though we should be using
// the standard compile flags for it.
// * `grind_guide` always times out.
// * `pkg/|lake/` tests sometimes time out (likely even hang), related to Lake CI
// failures?
"CTEST_OPTIONS": "-E 'StackOverflow|reverse-ffi|interactive|async_select_channel|9366|run/bv_|grind_guide|pkg/|lake/'"
},
// exclude seriously slow/problematic tests (laketests crash)
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
},*/
{
"name": "macOS",
"os": "macos-15-intel",
"os": "macos-13",
"release": true,
"test": false, // Tier 2 platform
"enabled": level >= 2,
"check-level": 2,
"shell": "bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-apple-darwin.tar.zst",
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
"binary-check": "otool -L",
"tar": "gtar", // https://github.com/actions/runner-images/issues/2619
"CTEST_OPTIONS": "-E 'leanlaketest_hello'", // started failing from unpack
},
{
"name": "macOS aarch64",
// standard GH runner only comes with 7GB so use large runner if possible when running tests
"os": large && (fast || level >= 1) ? "nscloud-macos-sequoia-arm64-6x14" : "macos-15",
"os": large && !isPr ? "nscloud-macos-sonoma-arm64-6x14" : "macos-14",
"CMAKE_OPTIONS": "-DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
"release": true,
"shell": "bash -euxo pipefail {0}",
@@ -304,18 +236,18 @@ jobs:
"binary-check": "otool -L",
"tar": "gtar", // https://github.com/actions/runner-images/issues/2619
// See "Linux release" for release job levels; Grove is not a concern here
"enabled": isPr || level != 1,
"test": level >= 1,
"secondary": level == 0,
"check-level": isPr ? 0 : 2,
"secondary": isPr,
},
{
"name": "Windows",
"os": large && (fast || level >= 2) ? "namespace-profile-windows-amd64-4x16" : "windows-2022",
"os": large && level == 2 ? "namespace-profile-windows-amd64-4x16" : "windows-2022",
"release": true,
"enabled": level >= 2,
"test": true,
"check-level": 2,
"shell": "msys2 {0}",
"CMAKE_OPTIONS": "-G \"Unix Makefiles\"",
// for reasons unknown, interactivetests are flaky on Windows
"CTEST_OPTIONS": "--repeat until-pass:2",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-w64-windows-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-mingw.sh lean-llvm*",
"binary-check": "ldd",
@@ -325,8 +257,7 @@ jobs:
"os": "nscloud-ubuntu-22.04-arm64-4x16",
"CMAKE_OPTIONS": "-DLEAN_INSTALL_SUFFIX=-linux_aarch64",
"release": true,
"enabled": level >= 2,
"test": true,
"check-level": 2,
"shell": "nix develop .#oldGlibcAArch -c bash -euxo pipefail {0}",
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-aarch64-linux-gnu.tar.zst",
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
@@ -339,7 +270,7 @@ jobs:
// "CMAKE_OPTIONS": "-DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_MMAP=OFF -DUSE_GMP=OFF -DLEAN_EXTRA_CXX_FLAGS='-m32' -DLEANC_OPTS='-m32' -DMMAP=OFF -DLEAN_INSTALL_SUFFIX=-linux_x86 -DCMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/ -DSTAGE0_CMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/ -DPKG_CONFIG_EXECUTABLE=/usr/bin/i386-linux-gnu-pkg-config",
// "cmultilib": true,
// "release": true,
// "enabled": level >= 2,
// "check-level": 2,
// "cross": true,
// "shell": "bash -euxo pipefail {0}"
//}
@@ -351,7 +282,7 @@ jobs:
// "wasm": true,
// "cmultilib": true,
// "release": true,
// "enabled": level >= 2,
// "check-level": 2,
// "cross": true,
// "shell": "bash -euxo pipefail {0}",
// // Just a few selected tests because wasm is slow
@@ -365,7 +296,7 @@ jobs:
}
}
console.log(`matrix:\n${JSON.stringify(matrix, null, 2)}`);
matrix = matrix.filter((job) => job["enabled"]);
matrix = matrix.filter((job) => level >= job["check-level"]);
core.setOutput('matrix', matrix.filter((job) => !job["secondary"]));
core.setOutput('matrix-secondary', matrix.filter((job) => job["secondary"]));
@@ -375,6 +306,7 @@ jobs:
uses: ./.github/workflows/build-template.yml
with:
config: ${{needs.configure.outputs.matrix}}
check-level: ${{ needs.configure.outputs.check-level }}
nightly: ${{ needs.configure.outputs.nightly }}
LEAN_VERSION_MAJOR: ${{ needs.configure.outputs.LEAN_VERSION_MAJOR }}
LEAN_VERSION_MINOR: ${{ needs.configure.outputs.LEAN_VERSION_MINOR }}
@@ -390,6 +322,7 @@ jobs:
uses: ./.github/workflows/build-template.yml
with:
config: ${{needs.configure.outputs.matrix-secondary}}
check-level: ${{ needs.configure.outputs.check-level }}
nightly: ${{ needs.configure.outputs.nightly }}
LEAN_VERSION_MAJOR: ${{ needs.configure.outputs.LEAN_VERSION_MAJOR }}
LEAN_VERSION_MINOR: ${{ needs.configure.outputs.LEAN_VERSION_MINOR }}
@@ -420,7 +353,7 @@ jobs:
content: |
A build of `${{ github.ref_name }}`, triggered by event `${{ github.event_name }}`, [failed](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).
- if: contains(needs.*.result, 'failure')
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
core.setFailed('Some jobs failed')
@@ -433,11 +366,11 @@ jobs:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8
with:
files: artifacts/*/*
fail_on_unmatched_files: true
@@ -458,14 +391,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
# needed for tagging
fetch-depth: 0
# Doesn't seem to be working when additionally fetching from lean4-nightly
#filter: tree:0
token: ${{ secrets.PUSH_NIGHTLY_TOKEN }}
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare Nightly Release
@@ -483,7 +414,7 @@ jobs:
echo -e "\n*Full commit log*\n" >> diff.md
git log --oneline "$last_tag"..HEAD | sed 's/^/* /' >> diff.md
- name: Release Nightly
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8
with:
body_path: diff.md
prerelease: true

View File

@@ -6,7 +6,7 @@ jobs:
check-lean-files:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Verify .lean files start with a copyright header.
run: |

View File

@@ -51,7 +51,7 @@ jobs:
- name: Fetch upstream invalidated facts
if: ${{ steps.should-run.outputs.should-run == 'true' && steps.workflow-info.outputs.pullRequestNumber != '' }}
id: fetch-upstream
uses: TwoFx/grove-action/fetch-upstream@v0.5
uses: TwoFx/grove-action/fetch-upstream@v0.4
with:
artifact-name: grove-invalidated-facts
base-ref: master
@@ -65,7 +65,6 @@ jobs:
workflow: ci.yml
path: artifacts
name: "build-Linux release"
allow_forks: true
name_is_regexp: true
- name: Unpack toolchain
@@ -96,7 +95,7 @@ jobs:
- name: Build
if: ${{ steps.should-run.outputs.should-run == 'true' }}
id: build
uses: TwoFx/grove-action/build@v0.5
uses: TwoFx/grove-action/build@v0.4
with:
project-path: doc/std/grove
script-name: grove-stdlib
@@ -111,7 +110,7 @@ jobs:
# material.
- id: deploy-alias
if: ${{ steps.should-run.outputs.should-run == 'true' }}
uses: actions/github-script@v8
uses: actions/github-script@v7
name: Compute Alias
with:
result-encoding: string

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Add label based on comment
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |

View File

@@ -11,7 +11,7 @@ jobs:
steps:
- name: Check PR body
if: github.event_name == 'pull_request'
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const { title, body, labels, draft } = context.payload.pull_request;

View File

@@ -48,17 +48,17 @@ jobs:
git -C lean4.git remote add origin https://github.com/${{ github.repository_owner }}/lean4.git
git -C lean4.git fetch -n origin master
git -C lean4.git fetch -n origin "${{ steps.workflow-info.outputs.sourceHeadSha }}"
# Create both the original tag and the SHA-suffixed tag
SHORT_SHA="${{ steps.workflow-info.outputs.sourceHeadSha }}"
SHORT_SHA="${SHORT_SHA:0:7}"
# Export the short SHA for use in subsequent steps
echo "SHORT_SHA=${SHORT_SHA}" >> "$GITHUB_ENV"
git -C lean4.git tag -f pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} "${{ steps.workflow-info.outputs.sourceHeadSha }}"
git -C lean4.git tag -f pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-"${SHORT_SHA}" "${{ steps.workflow-info.outputs.sourceHeadSha }}"
git -C lean4.git remote add pr-releases https://foo:'${{ secrets.PR_RELEASES_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-pr-releases.git
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-"${SHORT_SHA}"
@@ -71,7 +71,7 @@ jobs:
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
- name: Release (short format)
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8
with:
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }}
# There are coredumps files here as well, but all in deeper subdirectories.
@@ -86,7 +86,7 @@ jobs:
- name: Release (SHA-suffixed format)
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8
with:
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }} (${{ steps.workflow-info.outputs.sourceHeadSha }})
# There are coredumps files here as well, but all in deeper subdirectories.
@@ -101,7 +101,7 @@ jobs:
- name: Report release status (short format)
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
await github.rest.repos.createCommitStatus({
@@ -115,7 +115,7 @@ jobs:
- name: Report release status (SHA-suffixed format)
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
await github.rest.repos.createCommitStatus({
@@ -127,9 +127,9 @@ jobs:
description: "${{ github.repository_owner }}/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}",
});
- name: Add toolchain-available label
- name: Add label
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.addLabels({
@@ -166,14 +166,22 @@ jobs:
if [ "$NIGHTLY_SHA" = "$MERGE_BASE_SHA" ]; then
echo "The merge base of this PR coincides with the nightly release"
BATTERIES_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover-community/batteries.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
MATHLIB_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover-community/mathlib4-nightly-testing.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
if [[ -n "$MATHLIB_REMOTE_TAGS" ]]; then
echo "... and Mathlib has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
if [[ -n "$BATTERIES_REMOTE_TAGS" ]]; then
echo "... and Batteries has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE=""
if [[ -n "$MATHLIB_REMOTE_TAGS" ]]; then
echo "... and Mathlib has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
else
echo "... but Mathlib does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE="- ❗ Mathlib CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Mathlib CI should run now."
fi
else
echo "... but Mathlib does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE="- ❗ Mathlib CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Mathlib CI should run now."
echo "... but Batteries does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
MESSAGE="- ❗ Batteries CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Batteries CI should run now."
fi
else
echo "The most recently nightly tag on this branch has SHA: $NIGHTLY_SHA"
@@ -192,7 +200,7 @@ jobs:
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/labels" \
| jq -r '.[].name')"
if echo "$LABELS" | grep -q "^force-mathlib-ci$"; then
echo "force-mathlib-ci label detected, forcing CI despite issues"
MESSAGE="Forcing Mathlib CI because the \`force-mathlib-ci\` label is present, despite problem: $MESSAGE"
@@ -293,7 +301,7 @@ jobs:
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/labels" \
| jq -r '.[].name')"
if echo "$LABELS" | grep -q "^force-manual-ci$"; then
echo "force-manual-ci label detected, forcing CI despite issues"
MESSAGE="Forcing reference manual CI because the \`force-manual-ci\` label is present, despite problem: $MESSAGE"
@@ -360,7 +368,7 @@ jobs:
- name: Report mathlib base
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true' }}
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const description =
@@ -387,13 +395,12 @@ jobs:
# Checkout the Batteries repository with all branches
- name: Checkout Batteries repository
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
repository: leanprover-community/batteries
token: ${{ secrets.MATHLIB4_BOT }}
ref: nightly-testing
fetch-depth: 0 # This ensures we check out all tags and branches.
filter: tree:0
- name: Check if tag exists
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
@@ -418,7 +425,7 @@ jobs:
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
git add lean-toolchain
git commit --allow-empty -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
else
echo "Branch already exists, updating lean-toolchain."
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
@@ -427,7 +434,7 @@ jobs:
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
git add lean-toolchain
git commit --allow-empty -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
git commit -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
fi
- name: Push changes
@@ -447,13 +454,12 @@ jobs:
# Checkout the mathlib4 repository with all branches
- name: Checkout mathlib4 repository
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
repository: leanprover-community/mathlib4-nightly-testing
token: ${{ secrets.MATHLIB4_BOT }}
ref: nightly-testing
fetch-depth: 0 # This ensures we check out all tags and branches.
filter: tree:0
- name: install elan
run: |
@@ -488,7 +494,7 @@ jobs:
sed -i 's,require "leanprover-community" / "batteries" @ git ".\+",require "leanprover-community" / "batteries" @ git "lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}",' lakefile.lean
lake update batteries
git add lakefile.lean lake-manifest.json
git commit --allow-empty -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
else
echo "Branch already exists, updating lean-toolchain and bumping Batteries."
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
@@ -499,7 +505,7 @@ jobs:
git add lean-toolchain
lake update batteries
git add lake-manifest.json
git commit --allow-empty -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
git commit -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
fi
- name: Push changes
@@ -507,18 +513,6 @@ jobs:
run: |
git push origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
- name: Add mathlib4-nightly-available label
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
uses: actions/github-script@v8
with:
script: |
await github.rest.issues.addLabels({
issue_number: ${{ steps.workflow-info.outputs.pullRequestNumber }},
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['mathlib4-nightly-available']
})
# We next automatically create a reference manual branch using this toolchain.
# Reference manual CI will be responsible for reporting back success or failure
# to the PR comments asynchronously (and thus transitively SubVerso/Verso).
@@ -530,13 +524,12 @@ jobs:
# Checkout the reference manual repository with all branches
- name: Checkout mathlib4 repository
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.reference-manual-ready.outputs.manual_ready == 'true'
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
repository: leanprover/reference-manual
token: ${{ secrets.MANUAL_PR_BOT }}
ref: nightly-testing
fetch-depth: 0 # This ensures we check out all tags and branches.
filter: tree:0
- name: Check if tag in reference manual exists
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.reference-manual-ready.outputs.manual_ready == 'true'
@@ -562,7 +555,7 @@ jobs:
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
git add lean-toolchain
git add lakefile.lean lake-manifest.json
git commit --allow-empty -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
else
echo "Branch already exists, updating lean-toolchain."
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
@@ -572,7 +565,7 @@ jobs:
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
git add lean-toolchain
git add lake-manifest.json
git commit --allow-empty -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
git commit -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
fi
- name: Push changes

View File

@@ -10,11 +10,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check PR title
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const msg = context.payload.pull_request? context.payload.pull_request.title : context.payload.merge_group.head_commit.message;
console.log(`Message: ${msg}`)
if (!/^(feat|fix|doc|style|refactor|test|chore|perf): (?![A-Z][a-z]).*[^.]($|\n\n)/.test(msg)) {
if (!/^(feat|fix|doc|style|refactor|test|chore|perf): .*[^.]($|\n\n)/.test(msg)) {
core.setFailed('PR title does not follow the Commit Convention (https://leanprover.github.io/lean4/doc/dev/commit_convention.html).');
}

View File

@@ -11,7 +11,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
- uses: actions/stale@v9
with:
days-before-stale: -1
days-before-pr-stale: 30

View File

@@ -19,15 +19,11 @@ concurrency:
jobs:
update-stage0:
runs-on: nscloud-ubuntu-22.04-amd64-8x16
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPRESS: true
CCACHE_MAXSIZE: 400M
steps:
# This action should push to an otherwise protected branch, so it
# uses a deploy key with write permissions, as suggested at
# https://stackoverflow.com/a/76135647/946226
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
ssh-key: ${{secrets.STAGE0_SSH_KEY}}
- run: echo "should_update_stage0=yes" >> "$GITHUB_ENV"
@@ -69,19 +65,15 @@ jobs:
build/stage1/**/*.ir
build/stage1/**/*.c
build/stage1/**/*.c.o*
key: Linux Lake-build-v4-${{ github.sha }}
key: Linux Lake-build-v3-${{ github.sha }}
# fall back to (latest) previous cache
restore-keys: |
Linux Lake-build-v4
Linux Lake-build-v3
- if: env.should_update_stage0 == 'yes'
# sync options with `Linux Lake` to ensure cache reuse
run: |
mkdir -p build
cmake --preset release -B build -DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true
run: cmake --preset release
shell: 'nix develop -c bash -euxo pipefail {0}'
- if: env.should_update_stage0 == 'yes'
run: |
make -j$NPROC -C build update-stage0-commit
run: make -j$NPROC -C build/release update-stage0-commit
shell: 'nix develop -c bash -euxo pipefail {0}'
- if: env.should_update_stage0 == 'yes'
run: git show --stat

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@ tasks.json
settings.json
.gdb_history
.vscode/*
!.vscode/settings.json
script/__pycache__
*.produced.out
CMakeSettings.json

View File

@@ -44,9 +44,7 @@ if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
set(CADICAL_CXX c++)
if (CADICAL_USE_CUSTOM_CXX)
set(CADICAL_CXX ${CMAKE_CXX_COMPILER})
# Use same platform flags as for Lean executables, in particular from `prepare-llvm-linux.sh`,
# but not Lean-specific `LEAN_EXTRA_CXX_FLAGS` such as fsanitize.
set(CADICAL_CXXFLAGS "${CMAKE_CXX_FLAGS}")
set(CADICAL_CXXFLAGS "${LEAN_EXTRA_CXX_FLAGS}")
set(CADICAL_LDFLAGS "-Wl,-rpath=\\$$ORIGIN/../lib")
endif()
find_program(CCACHE ccache)

View File

@@ -41,7 +41,7 @@
"SMALL_ALLOCATOR": "OFF",
"USE_MIMALLOC": "OFF",
"BSYMBOLIC": "OFF",
"LEAN_TEST_VARS": "MAIN_STACK_SIZE=16000 LSAN_OPTIONS=max_leaks=10"
"LEAN_TEST_VARS": "MAIN_STACK_SIZE=16000"
},
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/sanitize"

View File

@@ -7,9 +7,9 @@
/.github/ @kim-em
/RELEASES.md @kim-em
/src/kernel/ @leodemoura
/src/library/compiler/ @hargoniX
/src/library/compiler/ @zwarich
/src/lake/ @tydeu
/src/Lean/Compiler/ @leodemoura @hargoniX
/src/Lean/Compiler/ @leodemoura @zwarich
/src/Lean/Data/Lsp/ @mhuisi
/src/Lean/Elab/Deriving/ @kim-em
/src/Lean/Elab/Tactic/ @kim-em

View File

@@ -2,19 +2,19 @@ This is the repository for **Lean 4**.
# About
- [Quickstart](https://lean-lang.org/install/)
- [Quickstart](https://lean-lang.org/documentation/setup/)
- [Homepage](https://lean-lang.org)
- [Theorem Proving Tutorial](https://lean-lang.org/theorem_proving_in_lean4/)
- [Functional Programming in Lean](https://lean-lang.org/functional_programming_in_lean/)
- [Documentation Overview](https://lean-lang.org/learn/)
- [Documentation Overview](https://lean-lang.org/documentation/)
- [Language Reference](https://lean-lang.org/doc/reference/latest/)
- [Release notes](RELEASES.md) starting at v4.0.0-m3
- [Examples](https://lean-lang.org/examples/)
- [Examples](https://lean-lang.org/lean4/doc/examples.html)
- [External Contribution Guidelines](CONTRIBUTING.md)
# Installation
See [Install Lean](https://lean-lang.org/install/).
See [Setting Up Lean](https://lean-lang.org/documentation/setup/).
# Contributing

View File

@@ -72,9 +72,6 @@ update the archived C source code of the stage 0 compiler in `stage0/src`.
The github repository will automatically update stage0 on `master` once
`src/stdlib_flags.h` and `stage0/src/stdlib_flags.h` are out of sync.
To trigger this, modify `stage0/src/stdlib_flags.h` (e.g., by adding or changing
a comment). When `update-stage0` runs, it will overwrite `stage0/src/stdlib_flags.h`
with the contents of `src/stdlib_flags.h`, bringing them back in sync.
NOTE: A full rebuild of stage 1 will only be triggered when the *committed* contents of `stage0/` are changed.
Thus if you change files in it manually instead of through `update-stage0-commit` (see below) or fetching updates from git, you either need to commit those changes first or run `make -C build/release clean-stdlib`.

View File

@@ -1,9 +1,190 @@
# Foreign Function Interface
The Lean FFI documentation is now part of the [Lean language reference](https://lean-lang.org/doc/reference/latest/).
NOTE: The current interface was designed for internal use in Lean and should be considered **unstable**.
It will be refined and extended in the future.
* [General FFI](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=ffi)
* [Representation of inductive types](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=inductive-types-ffi)
* [String](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=string-ffi)
* [Array](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=array-ffi)
As Lean is written partially in Lean itself and partially in C++, it offers efficient interoperability between the two languages (or rather, between Lean and any language supporting C interfaces).
This support is however currently limited to transferring Lean data types; in particular, it is not possible yet to pass or return compound data structures such as C `struct`s by value from or to Lean.
There are two primary attributes for interoperating with other languages:
* `@[extern "sym"] constant leanSym : ...` binds a Lean declaration to the external symbol `sym`.
It can also be used with `def` to provide an internal definition, but ensuring consistency of both definitions is up to the user.
* `@[export sym] def leanSym : ...` exports `leanSym` under the unmangled symbol name `sym`.
For simple examples of how to call foreign code from Lean and vice versa, see <https://github.com/leanprover/lean4/blob/master/src/lake/examples/ffi> and <https://github.com/leanprover/lean4/blob/master/src/lake/examples/reverse-ffi>, respectively.
## The Lean ABI
The Lean Application Binary Interface (ABI) describes how the signature of a Lean declaration is encoded as a native calling convention.
It is based on the standard C ABI and calling convention of the target platform.
For a Lean declaration marked with either `@[extern "sym"]` or `@[export sym]` for some symbol name `sym`, let `α₁ → ... → αₙ → β` be the normalized declaration's type.
If `n` is 0, the corresponding C declaration is
```c
extern s sym;
```
where `s` is the C translation of `β` as specified in the next section.
In the case of an `@[extern]` definition, the symbol's value is guaranteed to be initialized only after calling the Lean module's initializer or that of an importing module; see [Initialization](#initialization).
If `n` is greater than 0, the corresponding C declaration is
```c
s sym(t, ..., tₘ);
```
where the parameter types `tᵢ` are the C translation of the `αᵢ` as in the next section.
In the case of `@[extern]` all *irrelevant* types are removed first; see next section.
### Translating Types from Lean to C
* The integer types `UInt8`, ..., `UInt64`, `USize` are represented by the C types `uint8_t`, ..., `uint64_t`, `size_t`, respectively
* `Char` is represented by `uint32_t`
* `Float` is represented by `double`
* An *enum* inductive type of at least 2 and at most 2^32 constructors, each of which with no parameters, is represented by the first type of `uint8_t`, `uint16_t`, `uint32_t` that is sufficient to represent all constructor indices.
For example, the type `Bool` is represented as `uint8_t` with values `0` for `false` and `1` for `true`.
* `Decidable α` is represented the same way as `Bool`
* An inductive type with a *trivial structure*, that is,
* it is none of the types described above
* it is not marked `unsafe`
* it has a single constructor with a single parameter of *relevant* type
is represented by the representation of that parameter's type.
For example, `{ x : α // p }`, the `Subtype` structure of a value of type `α` and an irrelevant proof, is represented by the representation of `α`.
Similarly, the signed integer types `Int8`, ..., `Int64`, `ISize` are also represented by the unsigned C types `uint8_t`, ..., `uint64_t`, `size_t`, respectively, because they have a trivial structure.
* `Nat` and `Int` are represented by `lean_object *`.
Their runtime values is either a pointer to an opaque bignum object or, if the lowest bit of the "pointer" is 1 (`lean_is_scalar`), an encoded unboxed natural number or integer (`lean_box`/`lean_unbox`).
* A universe `Sort u`, type constructor `... → Sort u`, or proposition `p : Prop` is *irrelevant* and is either statically erased (see above) or represented as a `lean_object *` with the runtime value `lean_box(0)`
* Any other type is represented by `lean_object *`.
Its runtime value is a pointer to an object of a subtype of `lean_object` (see the "Inductive types" section below) or the unboxed value `lean_box(cidx)` for the `cidx`th constructor of an inductive type if this constructor does not have any relevant parameters.
Example: the runtime value of `u : Unit` is always `lean_box(0)`.
#### Inductive types
For inductive types which are in the fallback `lean_object *` case above and not trivial constructors, the type is stored as a `lean_ctor_object`, and `lean_is_ctor` will return true. A `lean_ctor_object` stores the constructor index in the header, and the fields are stored in the `m_objs` portion of the object.
The memory order of the fields is derived from the types and order of the fields in the declaration. They are ordered as follows:
* Non-scalar fields stored as `lean_object *`
* Fields of type `USize`
* Other scalar fields, in decreasing order by size
Within each group the fields are ordered in declaration order. Trivial wrapper types count as their underlying wrapped type for this purpose.
* To access fields of the first kind, use `lean_ctor_get(val, i)` to get the `i`th non-scalar field.
* To access `USize` fields, use `lean_ctor_get_usize(val, n+i)` to get the `i`th usize field and `n` is the total number of fields of the first kind.
* To access other scalar fields, use `lean_ctor_get_uintN(val, off)` or `lean_ctor_get_usize(val, off)` as appropriate. Here `off` is the byte offset of the field in the structure, starting at `n*sizeof(void*)` where `n` is the number of fields of the first two kinds.
For example, a structure such as
```lean
structure S where
ptr_1 : Array Nat
usize_1 : USize
sc64_1 : UInt64
sc64_2 : { x : UInt64 // x > 0 } -- wrappers of scalars count as scalars
sc64_3 : Float -- `Float` is 64 bit
sc8_1 : Bool
sc16_1 : UInt16
sc8_2 : UInt8
sc64_4 : UInt64
usize_2 : USize
sc32_1 : Char -- trivial wrapper around `UInt32`
sc32_2 : UInt32
sc16_2 : UInt16
```
would get re-sorted into the following memory order:
* `S.ptr_1` - `lean_ctor_get(val, 0)`
* `S.usize_1` - `lean_ctor_get_usize(val, 1)`
* `S.usize_2` - `lean_ctor_get_usize(val, 2)`
* `S.sc64_1` - `lean_ctor_get_uint64(val, sizeof(void*)*3)`
* `S.sc64_2` - `lean_ctor_get_uint64(val, sizeof(void*)*3 + 8)`
* `S.sc64_3` - `lean_ctor_get_float(val, sizeof(void*)*3 + 16)`
* `S.sc64_4` - `lean_ctor_get_uint64(val, sizeof(void*)*3 + 24)`
* `S.sc32_1` - `lean_ctor_get_uint32(val, sizeof(void*)*3 + 32)`
* `S.sc32_2` - `lean_ctor_get_uint32(val, sizeof(void*)*3 + 36)`
* `S.sc16_1` - `lean_ctor_get_uint16(val, sizeof(void*)*3 + 40)`
* `S.sc16_2` - `lean_ctor_get_uint16(val, sizeof(void*)*3 + 42)`
* `S.sc8_1` - `lean_ctor_get_uint8(val, sizeof(void*)*3 + 44)`
* `S.sc8_2` - `lean_ctor_get_uint8(val, sizeof(void*)*3 + 45)`
### Borrowing
By default, all `lean_object *` parameters of an `@[extern]` function are considered *owned*, i.e. the external code is passed a "virtual RC token" and is responsible for passing this token along to another consuming function (exactly once) or freeing it via `lean_dec`.
To reduce reference counting overhead, parameters can be marked as *borrowed* by prefixing their type with `@&`.
Borrowed objects must only be passed to other non-consuming functions (arbitrarily often) or converted to owned values using `lean_inc`.
In `lean.h`, the `lean_object *` aliases `lean_obj_arg` and `b_lean_obj_arg` are used to mark this difference on the C side.
Return values and `@[export]` parameters are always owned at the moment.
## Initialization
When including Lean code as part of a larger program, modules must be *initialized* before accessing any of their declarations.
Module initialization entails
* initialization of all "constants" (nullary functions), including closed terms lifted out of other functions
* execution of all `[init]` functions
* execution of all `[builtin_init]` functions, if the `builtin` parameter of the module initializer has been set
The module initializer is automatically run with the `builtin` flag for executables compiled from Lean code and for "plugins" loaded with `lean --plugin`.
For all other modules imported by `lean`, the initializer is run without `builtin`.
Thus `[init]` functions are run iff their module is imported, regardless of whether they have native code available or not, while `[builtin_init]` functions are only run for native executable or plugins, regardless of whether their module is imported or not.
`lean` uses built-in initializers for e.g. registering basic parsers that should be available even without importing their module (which is necessary for bootstrapping).
The initializer for module `A.B` is called `initialize_A_B` and will automatically initialize any imported modules.
Module initializers are idempotent (when run with the same `builtin` flag), but not thread-safe.
**Important for process-related functionality**: If your application needs to use process-related functions from libuv, such as `Std.Internal.IO.Process.getProcessTitle` and `Std.Internal.IO.Process.setProcessTitle`, you must call `lean_setup_args(argc, argv)` (which returns a potentially modified `argv` that must be used in place of the original) **before** calling `lean_initialize()` or `lean_initialize_runtime_module()`. This sets up process handling capabilities correctly, which is essential for certain system-level operations that Lean's runtime may depend on.
Together with initialization of the Lean runtime, you should execute code like the following exactly once before accessing any Lean declarations:
```c
void lean_initialize_runtime_module();
void lean_initialize();
char ** lean_setup_args(int argc, char ** argv);
lean_object * initialize_A_B(uint8_t builtin, lean_object *);
lean_object * initialize_C(uint8_t builtin, lean_object *);
...
argv = lean_setup_args(argc, argv); // if using process-related functionality
lean_initialize_runtime_module();
//lean_initialize(); // necessary (and replaces `lean_initialize_runtime_module`) if you (indirectly) access the `Lean` package
lean_object * res;
// use same default as for Lean executables
uint8_t builtin = 1;
res = initialize_A_B(builtin, lean_io_mk_world());
if (lean_io_result_is_ok(res)) {
lean_dec_ref(res);
} else {
lean_io_result_show_error(res);
lean_dec(res);
return ...; // do not access Lean declarations if initialization failed
}
res = initialize_C(builtin, lean_io_mk_world());
if (lean_io_result_is_ok(res)) {
...
//lean_init_task_manager(); // necessary if you (indirectly) use `Task`
lean_io_mark_end_initialization();
```
In addition, any other thread not spawned by the Lean runtime itself must be initialized for Lean use by calling
```c
void lean_initialize_thread();
```
and should be finalized in order to free all thread-local resources by calling
```c
void lean_finalize_thread();
```
## `@[extern]` in the Interpreter
The interpreter can run Lean declarations for which symbols are available in loaded shared libraries, which includes `@[extern]` declarations.
Thus to e.g. run `#eval` on such a declaration, you need to
1. compile (at least) the module containing the declaration and its dependencies into a shared library, and then
1. pass this library to `lean --load-dynlib=` to run code `import`ing this module.
Note that it is not sufficient to load the foreign library containing the external symbol because the interpreter depends on code that is emitted for each `@[extern]` declaration.
Thus it is not possible to interpret an `@[extern]` declaration in the same file.
See [`tests/compiler/foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) for an example.

View File

@@ -8,7 +8,7 @@ You should not edit the `stage0` directory except using the commands described i
## Development Setup
You can use any of the [supported editors](https://lean-lang.org/install/manual/) for editing the Lean source code.
You can use any of the [supported editors](../setup.md) for editing the Lean source code.
Please see below for specific instructions for VS Code.
### Dev setup using elan
@@ -99,19 +99,3 @@ on to `nightly-with-manual` branch. (It is fine to force push after rebasing.)
CI will generate a branch of the reference manual called `lean-pr-testing-NNNN`
in `leanprover/reference-manual`. This branch uses the toolchain for your PR,
and will report back to the Lean PR with results from Mathlib CI.
### Avoiding rebuilds for downstream projects
If you want to test changes to Lean on downstream projects and would like to avoid rebuilding modules you have already built/fetched using the project's configured Lean toolchain, you can often do so as long as your build of Lean is close enough to that Lean toolchain (compatible .olean format including structure of all relevant environment extensions).
To override the toolchain without rebuilding for a single command, for example `lake build` or `lake lean`, you can use the prefix
```
LEAN_GITHASH=$(lean --githash) lake +lean4 ...
```
Alternatively, use
```
export LEAN_GITHASH=$(lean --githash)
export ELAN_TOOLCHAIN=lean4
```
to persist these changes for the lifetime of the current shell, which will affect any processes spawned from it such as VS Code started via `code .`.
If you use a setup where you cannot directly start your editor from the command line, such as VS Code Remote, you might want to consider using [direnv](https://direnv.net/) together with an editor extension for it instead so that you can put the lines above into `.envrc`.

View File

@@ -69,10 +69,6 @@ We'll use `v4.6.0` as the intended release version as a running example.
- `repl`:
There are two copies of `lean-toolchain`/`lakefile.lean`:
in the root, and in `test/Mathlib/`. Edit both, and run `lake update` in both directories.
- `lean-fro.org`:
After updating the toolchains and running `lake update`, you must run `scripts/update.sh` to regenerate
the site content. This script updates generated files that depend on the Lean version.
The `release_steps.py` script handles this automatically.
- An awkward situation that sometimes occurs (e.g. with Verso) is that the `master`/`main` branch has already been moved
to a nightly toolchain that comes *after* the stable toolchain we are
targeting. In this case it is necessary to create a branch `releases/v4.6.0` from the last commit which was on

View File

@@ -51,10 +51,6 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
codes and do not check the expected output even though output is
produced, it is ignored.
**Note:** Tests in this directory run with `-Dlinter.all=false` to reduce noise.
If your test needs to verify linter behavior (e.g., deprecation warnings),
explicitly enable the relevant linter with `set_option linter.<name> true`.
- [`tests/lean/interactive`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/): are designed to test server requests at a
given position in the input file. Each .lean file contains comments
that indicate how to simulate a client request at that position.
@@ -63,7 +59,7 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
open Foo in
theorem tst2 (h : a ≤ b) : a + 2 ≤ b + 2 :=
Bla.
--^ completion
--^ textDocument/completion
```
In this example, the test driver [`test_single.sh`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/test_single.sh) will simulate an
auto-completion request at `Bla.`. The expected output is stored in

View File

@@ -94,8 +94,10 @@ theorem List.palindrome_of_eq_reverse (h : as.reverse = as) : Palindrome as := b
next => exact Palindrome.nil
next a => exact Palindrome.single a
next a b as ih =>
obtain rfl, h, - := by simpa using h
exact Palindrome.sandwich b (ih h)
have : a = b := by simp_all
subst this
have : as.reverse = as := by simp_all
exact Palindrome.sandwich a (ih this)
/-!
We now define a function that returns `true` iff `as` is a palindrome.

View File

@@ -1,6 +1,6 @@
These are instructions to set up a working development environment for those who wish to make changes to Lean itself. It is part of the [Development Guide](../dev/index.md).
We strongly suggest that new users instead follow the [Installation Instructions](https://lean-lang.org/install/) to get started using Lean, since this sets up an environment that can automatically manage multiple Lean toolchain versions, which is necessary when working within the Lean ecosystem.
We strongly suggest that new users instead follow the [Quickstart](../quickstart.md) to get started using Lean, since this sets up an environment that can automatically manage multiple Lean toolchain versions, which is necessary when working within the Lean ecosystem.
Requirements
------------

View File

@@ -4,7 +4,6 @@ import GroveStdlib.Generated.«associative-creation-operations»
import GroveStdlib.Generated.«associative-modification-operations»
import GroveStdlib.Generated.«associative-create-then-query»
import GroveStdlib.Generated.«associative-all-operations-covered»
import GroveStdlib.Generated.«slice-producing»
/-
This file is autogenerated by grove. You can manually edit it, for example to resolve merge
@@ -21,4 +20,3 @@ def restoreState : RestoreStateM Unit := do
«associative-modification-operations».restoreState
«associative-create-then-query».restoreState
«associative-all-operations-covered».restoreState
«slice-producing».restoreState

View File

@@ -1,459 +0,0 @@
import Grove.Framework
/-
This file is autogenerated by grove. You can manually edit it, for example to resolve merge
conflicts, but be careful.
-/
open Grove.Framework Widget
namespace GroveStdlib.Generated.«slice-producing»
def «c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7"
rowId := "c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7"
rowState := #["String", "String.slice", Declaration.def {
name := `String.slice
renderedStatement := "String.slice (s : String) (startInclusive endExclusive : s.Pos)\n (h : startInclusive ≤ endExclusive) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.slice", Declaration.def {
name := `String.Slice.slice
renderedStatement := "String.Slice.slice (s : String.Slice) (newStart newEnd : s.Pos) (h : newStart ≤ newEnd) :\n String.Slice"
isDeprecated := false
}
,"string-pos-forwards", "String.Pos.slice", Declaration.def {
name := `String.Pos.slice
renderedStatement := "String.Pos.slice {s : String} (pos p₀ p₁ : s.Pos) (h₁ : p₀ ≤ pos) (h₂ : pos ≤ p₁) :\n (s.slice p₀ p₁ ⋯).Pos"
isDeprecated := false
}
,"string-pos-backwards", "String.Pos.ofSlice", Declaration.def {
name := `String.Pos.ofSlice
renderedStatement := "String.Pos.ofSlice {s : String} {p₀ p₁ : s.Pos} {h : p₀ ≤ p₁} (pos : (s.slice p₀ p₁ h).Pos) : s.Pos"
isDeprecated := false
}
,"string-slice-pos-forwards", "String.Slice.Pos.slice", Declaration.def {
name := `String.Slice.Pos.slice
renderedStatement := "String.Slice.Pos.slice {s : String.Slice} (pos p₀ p₁ : s.Pos) (h₁ : p₀ ≤ pos) (h₂ : pos ≤ p₁) :\n (s.slice p₀ p₁ ⋯).Pos"
isDeprecated := false
}
,"string-slice-pos-backwards", "String.Slice.Pos.ofSlice", Declaration.def {
name := `String.Slice.Pos.ofSlice
renderedStatement := "String.Slice.Pos.ofSlice {s : String.Slice} {p₀ p₁ : s.Pos} {h : p₀ ≤ p₁}\n (pos : (s.slice p₀ p₁ h).Pos) : s.Pos"
isDeprecated := false
}
,"string-pos-noproof", "String.Pos.sliceOrPanic", Declaration.def {
name := `String.Pos.sliceOrPanic
renderedStatement := "String.Pos.sliceOrPanic {s : String} (pos p₀ p₁ : s.Pos) {h : p₀ ≤ p₁} : (s.slice p₀ p₁ h).Pos"
isDeprecated := false
}
,"string-slice-pos-noproof", "String.Slice.Pos.sliceOrPanic", Declaration.def {
name := `String.Slice.Pos.sliceOrPanic
renderedStatement := "String.Slice.Pos.sliceOrPanic {s : String.Slice} (pos p₀ p₁ : s.Pos) {h : p₀ ≤ p₁} :\n (s.slice p₀ p₁ h).Pos"
isDeprecated := false
}
,]
metadata := {
status := .done
comment := ""
}
def «21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819"
rowId := "21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819"
rowState := #["String", "String.slice?", Declaration.def {
name := `String.slice?
renderedStatement := "String.slice? (s : String) (startInclusive endExclusive : s.Pos) : Option String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.slice?", Declaration.def {
name := `String.Slice.slice?
renderedStatement := "String.Slice.slice? (s : String.Slice) (newStart newEnd : s.Pos) : Option String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .postponed
comment := "Would be good to have better support"
}
def «6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0"
rowId := "6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0"
rowState := #["String", "String.slice!", Declaration.def {
name := `String.slice!
renderedStatement := "String.slice! (s : String) (p₁ p₂ : s.Pos) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.slice!", Declaration.def {
name := `String.Slice.slice!
renderedStatement := "String.Slice.slice! (s : String.Slice) (newStart newEnd : s.Pos) : String.Slice"
isDeprecated := false
}
,"string-pos-forwards", "String.Pos.slice!", Declaration.def {
name := `String.Pos.slice!
renderedStatement := "String.Pos.slice! {s : String} (pos p₀ p₁ : s.Pos) : (s.slice! p₀ p₁).Pos"
isDeprecated := false
}
,"string-pos-backwards", "String.Pos.ofSlice!", Declaration.def {
name := `String.Pos.ofSlice!
renderedStatement := "String.Pos.ofSlice! {s : String} {p₀ p₁ : s.Pos} (pos : (s.slice! p₀ p₁).Pos) : s.Pos"
isDeprecated := false
}
,"string-slice-pos-forwards", "String.Slice.Pos.slice!", Declaration.def {
name := `String.Slice.Pos.slice!
renderedStatement := "String.Slice.Pos.slice! {s : String.Slice} (pos p₀ p₁ : s.Pos) : (s.slice! p₀ p₁).Pos"
isDeprecated := false
}
,"string-slice-pos-backwards", "String.Slice.Pos.ofSlice!", Declaration.def {
name := `String.Slice.Pos.ofSlice!
renderedStatement := "String.Slice.Pos.ofSlice! {s : String.Slice} {p₀ p₁ : s.Pos} (pos : (s.slice! p₀ p₁).Pos) : s.Pos"
isDeprecated := false
}
,]
metadata := {
status := .done
comment := ""
}
def «a3bdf66d-bc11-4019-aee9-2f1c1701de52» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "a3bdf66d-bc11-4019-aee9-2f1c1701de52"
rowId := "a3bdf66d-bc11-4019-aee9-2f1c1701de52"
rowState := #["String", "String.trimAsciiStart", Declaration.def {
name := `String.trimAsciiStart
renderedStatement := "String.trimAsciiStart (s : String) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.trimAsciiStart", Declaration.def {
name := `String.Slice.trimAsciiStart
renderedStatement := "String.Slice.trimAsciiStart (s : String.Slice) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing `of` version at least"
}
def «f12b2730-7a4d-465c-8a6d-9d051c300fd5» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "f12b2730-7a4d-465c-8a6d-9d051c300fd5"
rowId := "f12b2730-7a4d-465c-8a6d-9d051c300fd5"
rowState := #["String", "String.trimAsciiEnd", Declaration.def {
name := `String.trimAsciiEnd
renderedStatement := "String.trimAsciiEnd (s : String) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.trimAsciiEnd", Declaration.def {
name := `String.Slice.trimAsciiEnd
renderedStatement := "String.Slice.trimAsciiEnd (s : String.Slice) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing `of` version at least"
}
def «32307b55-d6d1-4756-a947-dbe4dfde573c» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "32307b55-d6d1-4756-a947-dbe4dfde573c"
rowId := "32307b55-d6d1-4756-a947-dbe4dfde573c"
rowState := #["String", "String.trimAscii", Declaration.def {
name := `String.trimAscii
renderedStatement := "String.trimAscii (s : String) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.trimAscii", Declaration.def {
name := `String.Slice.trimAscii
renderedStatement := "String.Slice.trimAscii (s : String.Slice) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing `of` version at least\n"
}
def «dce95a38-f55a-4d6a-ae79-078ffe4b5c15» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "dce95a38-f55a-4d6a-ae79-078ffe4b5c15"
rowId := "dce95a38-f55a-4d6a-ae79-078ffe4b5c15"
rowState := #["String", "String.toSlice", Declaration.def {
name := `String.toSlice
renderedStatement := "String.toSlice (s : String) : String.Slice"
isDeprecated := false
}
,"string-pos-forwards", "String.Pos.toSlice", Declaration.def {
name := `String.Pos.toSlice
renderedStatement := "String.Pos.toSlice {s : String} (pos : s.Pos) : s.toSlice.Pos"
isDeprecated := false
}
,"string-pos-backwards", "String.Pos.ofToSlice", Declaration.def {
name := `String.Pos.ofToSlice
renderedStatement := "String.Pos.ofToSlice {s : String} (pos : s.toSlice.Pos) : s.Pos"
isDeprecated := false
}
,]
metadata := {
status := .done
comment := ""
}
def «005a3f30-5dab-493f-b168-32c36a2bdf7c» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "005a3f30-5dab-493f-b168-32c36a2bdf7c"
rowId := "005a3f30-5dab-493f-b168-32c36a2bdf7c"
rowState := #["String.Slice", "String.Slice.str", Declaration.def {
name := `String.Slice.str
renderedStatement := "String.Slice.str (self : String.Slice) : String"
isDeprecated := false
}
,"string-slice-pos-forwards", "String.Slice.Pos.str", Declaration.def {
name := `String.Slice.Pos.str
renderedStatement := "String.Slice.Pos.str {s : String.Slice} (pos : s.Pos) : s.str.Pos"
isDeprecated := false
}
,"string-slice-pos-backwards", "String.Slice.Pos.ofStr", Declaration.def {
name := `String.Slice.Pos.ofStr
renderedStatement := "String.Slice.Pos.ofStr {s : String.Slice} (pos : s.str.Pos) (h₁ : s.startInclusive ≤ pos)\n (h₂ : pos ≤ s.endExclusive) : s.Pos"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing `no proof` version\n"
}
def «5f1a154c-ae2f-43a1-9409-2ce95b163ef3» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "5f1a154c-ae2f-43a1-9409-2ce95b163ef3"
rowId := "5f1a154c-ae2f-43a1-9409-2ce95b163ef3"
rowState := #["String", "String.drop", Declaration.def {
name := `String.drop
renderedStatement := "String.drop (s : String) (n : Nat) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.drop", Declaration.def {
name := `String.Slice.drop
renderedStatement := "String.Slice.drop (s : String.Slice) (n : Nat) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «179518d1-ad07-4b2b-8ffe-3b7616e4c4ab» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "179518d1-ad07-4b2b-8ffe-3b7616e4c4ab"
rowId := "179518d1-ad07-4b2b-8ffe-3b7616e4c4ab"
rowState := #["String", "String.take", Declaration.def {
name := `String.take
renderedStatement := "String.take (s : String) (n : Nat) : String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.take", Declaration.def {
name := `String.Slice.take
renderedStatement := "String.Slice.take (s : String.Slice) (n : Nat) : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «55c587fd-a7a8-4633-a4ae-e2c4e768ad28» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "55c587fd-a7a8-4633-a4ae-e2c4e768ad28"
rowId := "55c587fd-a7a8-4633-a4ae-e2c4e768ad28"
rowState := #["String", "String.dropWhile", Declaration.def {
name := `String.dropWhile
renderedStatement := "String.dropWhile {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.ForwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropWhile", Declaration.def {
name := `String.Slice.dropWhile
renderedStatement := "String.Slice.dropWhile {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.ForwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «d4444684-4279-4400-9be2-561a7cdb32c1» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "d4444684-4279-4400-9be2-561a7cdb32c1"
rowId := "d4444684-4279-4400-9be2-561a7cdb32c1"
rowState := #["String", "String.takeWhile", Declaration.def {
name := `String.takeWhile
renderedStatement := "String.takeWhile {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.ForwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.takeWhile", Declaration.def {
name := `String.Slice.takeWhile
renderedStatement := "String.Slice.takeWhile {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.ForwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «1c9e6689-65a0-4d4b-b001-256e83917d98» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "1c9e6689-65a0-4d4b-b001-256e83917d98"
rowId := "1c9e6689-65a0-4d4b-b001-256e83917d98"
rowState := #["String", "String.dropEndWhile", Declaration.def {
name := `String.dropEndWhile
renderedStatement := "String.dropEndWhile {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.BackwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropEndWhile", Declaration.def {
name := `String.Slice.dropEndWhile
renderedStatement := "String.Slice.dropEndWhile {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.BackwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «b836052b-3470-4a8e-8989-6951c898de37» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "b836052b-3470-4a8e-8989-6951c898de37"
rowId := "b836052b-3470-4a8e-8989-6951c898de37"
rowState := #["String", "String.takeEndWhile", Declaration.def {
name := `String.takeEndWhile
renderedStatement := "String.takeEndWhile {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.BackwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.takeEndWhile", Declaration.def {
name := `String.Slice.takeEndWhile
renderedStatement := "String.Slice.takeEndWhile {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.BackwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «5aa777d8-9642-43d8-9e20-30400fb8bb9d» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "5aa777d8-9642-43d8-9e20-30400fb8bb9d"
rowId := "5aa777d8-9642-43d8-9e20-30400fb8bb9d"
rowState := #["String", "String.dropPrefix", Declaration.def {
name := `String.dropPrefix
renderedStatement := "String.dropPrefix {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.ForwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropPrefix", Declaration.def {
name := `String.Slice.dropPrefix
renderedStatement := "String.Slice.dropPrefix {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.ForwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «80e3869d-fcfe-459d-8433-fe221f7b3c7a» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "80e3869d-fcfe-459d-8433-fe221f7b3c7a"
rowId := "80e3869d-fcfe-459d-8433-fe221f7b3c7a"
rowState := #["String", "String.dropSuffix", Declaration.def {
name := `String.dropSuffix
renderedStatement := "String.dropSuffix {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.BackwardPattern pat] :\n String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropSuffix", Declaration.def {
name := `String.Slice.dropSuffix
renderedStatement := "String.Slice.dropSuffix {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.BackwardPattern pat] : String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .bad
comment := "Missing position transformations"
}
def «4feda3e0-903b-4d52-b34e-0af70f7866e0» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "4feda3e0-903b-4d52-b34e-0af70f7866e0"
rowId := "4feda3e0-903b-4d52-b34e-0af70f7866e0"
rowState := #["String", "String.dropPrefix?", Declaration.def {
name := `String.dropPrefix?
renderedStatement := "String.dropPrefix? {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.ForwardPattern pat] :\n Option String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropPrefix?", Declaration.def {
name := `String.Slice.dropPrefix?
renderedStatement := "String.Slice.dropPrefix? {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.ForwardPattern pat] : Option String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .postponed
comment := "Missing position transformations"
}
def «45ca44c8-fbd5-4400-8297-a60778f302b0» : AssociationTable.Fact .declaration where
widgetId := "slice-producing"
factId := "45ca44c8-fbd5-4400-8297-a60778f302b0"
rowId := "45ca44c8-fbd5-4400-8297-a60778f302b0"
rowState := #["String", "String.dropSuffix?", Declaration.def {
name := `String.dropSuffix?
renderedStatement := "String.dropSuffix? {ρ : Type} (s : String) (pat : ρ) [String.Slice.Pattern.BackwardPattern pat] :\n Option String.Slice"
isDeprecated := false
}
,"String.Slice", "String.Slice.dropSuffix?", Declaration.def {
name := `String.Slice.dropSuffix?
renderedStatement := "String.Slice.dropSuffix? {ρ : Type} (s : String.Slice) (pat : ρ)\n [String.Slice.Pattern.BackwardPattern pat] : Option String.Slice"
isDeprecated := false
}
,]
metadata := {
status := .postponed
comment := "Missing position transformations"
}
def table : AssociationTable.Data .declaration where
widgetId := "slice-producing"
rows := #[
"c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7", "slice", #["String", "String.slice","String.Slice", "String.Slice.slice","string-pos-forwards", "String.Pos.slice","string-pos-backwards", "String.Pos.ofSlice","string-slice-pos-forwards", "String.Slice.Pos.slice","string-slice-pos-backwards", "String.Slice.Pos.ofSlice","string-pos-noproof", "String.Pos.sliceOrPanic","string-slice-pos-noproof", "String.Slice.Pos.sliceOrPanic",],
"21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819", "slice?", #["String", "String.slice?","String.Slice", "String.Slice.slice?",],
"6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0", "slice!", #["String", "String.slice!","String.Slice", "String.Slice.slice!","string-pos-forwards", "String.Pos.slice!","string-pos-backwards", "String.Pos.ofSlice!","string-slice-pos-forwards", "String.Slice.Pos.slice!","string-slice-pos-backwards", "String.Slice.Pos.ofSlice!",],
"a3bdf66d-bc11-4019-aee9-2f1c1701de52", "trimAsciiStart", #["String", "String.trimAsciiStart","String.Slice", "String.Slice.trimAsciiStart",],
"f12b2730-7a4d-465c-8a6d-9d051c300fd5", "trimAsciiEnd", #["String", "String.trimAsciiEnd","String.Slice", "String.Slice.trimAsciiEnd",],
"32307b55-d6d1-4756-a947-dbe4dfde573c", "trimAscii", #["String", "String.trimAscii","String.Slice", "String.Slice.trimAscii",],
"dce95a38-f55a-4d6a-ae79-078ffe4b5c15", "toSlice", #["String", "String.toSlice","string-pos-forwards", "String.Pos.toSlice","string-pos-backwards", "String.Pos.ofToSlice",],
"005a3f30-5dab-493f-b168-32c36a2bdf7c", "str", #["String.Slice", "String.Slice.str","string-slice-pos-forwards", "String.Slice.Pos.str","string-slice-pos-backwards", "String.Slice.Pos.ofStr",],
"5f1a154c-ae2f-43a1-9409-2ce95b163ef3", "drop", #["String", "String.drop","String.Slice", "String.Slice.drop",],
"179518d1-ad07-4b2b-8ffe-3b7616e4c4ab", "take", #["String", "String.take","String.Slice", "String.Slice.take",],
"55c587fd-a7a8-4633-a4ae-e2c4e768ad28", "dropWhile", #["String", "String.dropWhile","String.Slice", "String.Slice.dropWhile",],
"d4444684-4279-4400-9be2-561a7cdb32c1", "takeWhile", #["String", "String.takeWhile","String.Slice", "String.Slice.takeWhile",],
"1c9e6689-65a0-4d4b-b001-256e83917d98", "dropEndWhile", #["String", "String.dropEndWhile","String.Slice", "String.Slice.dropEndWhile",],
"b836052b-3470-4a8e-8989-6951c898de37", "takeEndWhile", #["String", "String.takeEndWhile","String.Slice", "String.Slice.takeEndWhile",],
"5aa777d8-9642-43d8-9e20-30400fb8bb9d", "dropPrefix", #["String", "String.dropPrefix","String.Slice", "String.Slice.dropPrefix",],
"80e3869d-fcfe-459d-8433-fe221f7b3c7a", "dropSuffix", #["String", "String.dropSuffix","String.Slice", "String.Slice.dropSuffix",],
"4feda3e0-903b-4d52-b34e-0af70f7866e0", "dropPrefix?", #["String", "String.dropPrefix?","String.Slice", "String.Slice.dropPrefix?",],
"45ca44c8-fbd5-4400-8297-a60778f302b0", "dropSuffix?", #["String", "String.dropSuffix?","String.Slice", "String.Slice.dropSuffix?",],
]
facts := #[
«c8a13d6d-7ed6-4cd1-a386-23e2d55ce6f7»,
«21b4fdfd-f8b3-44f5-a59e-57f1dc1d6819»,
«6f2b6ecb-2f0c-4e45-9da3-eb7f2e15eff0»,
«a3bdf66d-bc11-4019-aee9-2f1c1701de52»,
«f12b2730-7a4d-465c-8a6d-9d051c300fd5»,
«32307b55-d6d1-4756-a947-dbe4dfde573c»,
«dce95a38-f55a-4d6a-ae79-078ffe4b5c15»,
«005a3f30-5dab-493f-b168-32c36a2bdf7c»,
«5f1a154c-ae2f-43a1-9409-2ce95b163ef3»,
«179518d1-ad07-4b2b-8ffe-3b7616e4c4ab»,
«55c587fd-a7a8-4633-a4ae-e2c4e768ad28»,
«d4444684-4279-4400-9be2-561a7cdb32c1»,
«1c9e6689-65a0-4d4b-b001-256e83917d98»,
«b836052b-3470-4a8e-8989-6951c898de37»,
«5aa777d8-9642-43d8-9e20-30400fb8bb9d»,
«80e3869d-fcfe-459d-8433-fe221f7b3c7a»,
«4feda3e0-903b-4d52-b34e-0af70f7866e0»,
«45ca44c8-fbd5-4400-8297-a60778f302b0»,
]
def restoreState : RestoreStateM Unit := do
addAssociationTable table

View File

@@ -15,7 +15,7 @@ namespace GroveStdlib
namespace Std
def introduction : Node :=
.text "introduction", "Welcome to the interactive Lean standard library outline!"
.text "Welcome to the interactive Lean standard library outline!"
end Std

View File

@@ -11,87 +11,9 @@ namespace GroveStdlib.Std.CoreTypesAndOperations
namespace StringsAndFormatting
open Lean Meta
def introduction : Text where
id := "string-introduction"
content := Grove.Markdown.render [
.h1 "The Lean string library",
.text "The Lean standard library contains a fully-featured string library, centered around the types `String` and `String.Slice`.",
.text "`String` is defined as the subtype of `ByteArray` of valid UTF-8 strings. A `String.Slice` is a `String` together with a start and end position.",
.text "`String` is equivalent to `List Char`, but it has a more efficient runtime representation. While the logical model based on `ByteArray` is overwritten in the runtime, the runtime implementation is very similar to the logical model, with the main difference being that the length of a string in Unicode code points is cached in the runtime implementation.",
.text "We are considering removing this feature in the future (i.e., deprecating `String.length`), as the number of UTF-8 codepoints in a string is not particularly useful, and if needed it can be computed in linear time using `s.positions.count`."
]
def highLevelStringTypes : List Lean.Name :=
[`String, `String.Slice, `String.Pos, `String.Slice.Pos]
def creatingStringsAndSlices : Text where
id := "transforming-strings-and-slices"
content := Grove.Markdown.render [
.h2 "Transforming strings and slices",
.text "The Lean standard library contains a number of functions that take one or more strings and slices and return a string or a slice.",
.text "If possible, these functions should avoid allocating a new string, and return a slice of their input(s) instead.",
.text "Usually, for every operation `f`, there will be functions `String.f` and `String.Slice.f`, where `String.f s` is defined as `String.Slice.f s.toSlice`.",
.text "In particular, functions that transform strings and slices should live in the `String` and `String.Slice` namespaces even if they involve a `String.Pos`/`String.Slice.Pos` (like `String.sliceTo`), for reasons that will become clear shortly.",
.h3 "Transforming positions",
.text "Since positions on strings and slices are dependent on the string or slice, whenever users transform a string/slice, they will be interested in interpreting positions on the original string/slice as positions on the result, or vice versa.",
.text "Consequently, every operation that transforms a string or slice should come with a corresponding set of transformations between positions, usually in both directions, possibly with one of the directions being conditional.",
.text "For example, given a string `s` and a position `p` on `s`, we have the slice `s.sliceFrom p`, which is the slice from `p` to the end of `s`. A position on `s.sliceFrom p` can always be interpreted as a position on `s`. This is the \"backwards\" transformation. Conversely, a position `q` on `s` can be interpreted as a position on `s.sliceFrom p` as long as `p ≤ q`. This is the conditional forwards direction.",
.text "The convention for naming these transformations is that the forwards transformation should have the same name as the transformation on strings/slices, but it should be located in the `String.Pos` or `String.Slice.Pos` namespace, depending on the type of the starting position (so that dot notation is possible for the forward direction). The backwards transformation should have the same name as the operation on strings/slices, but with an `of` prefix, and live in the same namespace as the forwards transformation (so in general dot notation will not be available).",
.text "So, in the `sliceFrom` example, the forward direction would be called `String.Pos.sliceFrom`, while the backwards direction should be called `String.Pos.ofSliceFrom` (not `String.Slice.Pos.ofSliceFrom`).",
.text "If one of the directions is conditional, it should have a corresponding panicking operation that does not require a proof; in our example this would be `String.Pos.sliceFrom!`.",
.text "Sometimes there is a name clash for the panicking operations if the operation on strings is already panicking. For example, there are both `String.slice` and `String.slice!`. If the original operation is already panicking, we only provide panicking transformation operations. But now `String.Pos.slice!` could refer both to the panicking forwards transformation associated with `String.slice`, and also to the (only) forwards transformation associated with `String.slice!`. In this situation, we use an `orPanic` suffix to disambiguate. So the panicking forwards operation associated with `String.slice` is called `String.Pos.sliceOrPanic`, and the forwards operation associated with `String.slice!` is called `String.Pos.slice!`."
]
-- TODO: also include the `HAppend` instance(s)
def sliceProducing : AssociationTable (β := Alias Lean.Name) .declaration
[`String, `String.Slice,
Alias.mk `String.Pos "string-pos-forwards" "String.Pos (forwards)",
Alias.mk `String.Pos "string-pos-backwards" "String.Pos (backwards)",
Alias.mk `String.Pos "string-pos-noproof" "String.Pos (no proof)",
Alias.mk `String.Slice.Pos "string-slice-pos-forwards" "String.Slice.Pos (forwards)",
Alias.mk `String.Slice.Pos "string-slice-pos-backwards" "String.Slice.Pos (backwards)",
Alias.mk `String.Slice.Pos "string-slice-pos-noproof" "String.Slice.Pos (no proof)"] where
id := "slice-producing"
title := "String functions returning strings or slices"
description := "Operations on strings and string slices that themselves return a new string slice."
dataSources n := DataSource.definitionsInNamespace n.inner
def sliceProducingComplete : Assertion where
widgetId := "slice-producing-complete"
title := "Slice-producing table is complete"
description := "All functions in the `String.**` namespace that return a string or a slice are covered in the table"
check := do
let mut ans := #[]
let covered := Std.HashSet.ofArray ( valuesInAssociationTable sliceProducing)
let pred : DataSource.DeclarationPredicate :=
DataSource.DeclarationPredicate.all [.isDefinition, .not .isDeprecated,
.notInNamespace `String.Pos.Raw, .notInNamespace `String.Legacy,
.not .isInstance]
let env getEnv
for name in declarationsMatching `String pred do
let some c := env.find? name | continue
if c.type.getForallBody.getUsedConstants.any (fun n => n == ``String || n == ``String.Slice) then
let success : Bool := name.toString covered
ans := ans.push {
assertionId := name.toString
description := s!"`{name}` should appear in the table."
passed := success
message := s!"`{name}` was{if success then "" else " not"} found in the table."
}
return ans
end StringsAndFormatting
open StringsAndFormatting
def stringsAndFormatting : Node :=
.section "strings-and-formatting" "Strings and formatting"
#[.text introduction,
.text creatingStringsAndSlices,
.associationTable sliceProducing,
.assertion sliceProducingComplete]
.section "strings-and-formatting" "Strings and formatting" #[]
end GroveStdlib.Std.CoreTypesAndOperations
end GroveStdlib.Std.CoreTypesAndOperations

View File

@@ -5,7 +5,7 @@
"type": "git",
"subDir": "backend",
"scope": "",
"rev": "c580a425c9b7fa2aebaec2a1d8de16b2e2283c40",
"rev": "3e8aabdea58c11813c5d3b7eeb187ded44ee9a34",
"name": "grove",
"manifestFile": "lake-manifest.json",
"inputRev": "master",
@@ -15,10 +15,10 @@
"type": "git",
"subDir": null,
"scope": "leanprover",
"rev": "d9fc8ae23024be37424a189982c92356e37935c8",
"rev": "1604206fcd0462da9a241beeac0e2df471647435",
"name": "Cli",
"manifestFile": "lake-manifest.json",
"inputRev": "nightly-testing",
"inputRev": "main",
"inherited": true,
"configFile": "lakefile.toml"}],
"name": "grovestdlib",

View File

@@ -810,7 +810,7 @@ Docstrings for constants should have the following structure:
The **short summary** should be 13 sentences (ideally 1) and provide
enough information for most readers to quickly decide whether the
constant is relevant to their task. The first (or only) sentence of
docstring is relevant to their task. The first (or only) sentence of
the short summary should be a *sentence fragment* in which the subject
is implied to be the documented item, written in present tense
indicative, or a *noun phrase* that characterizes the documented
@@ -1123,110 +1123,6 @@ infix:50 " ⇔ " => Bijection
recommended_spelling "bij" for "⇔" in [Bijection, «term_⇔_»]
```
#### Tactics
Docstrings for tactics should have the following structure:
* Short summary
* Details
* Variants
* Examples
Sometimes more than one declaration is needed to implement what the user
sees as a single tactic. In that case, only one declaration should have
the associated docstring, and the others should have the `tactic_alt`
attribute to mark them as an implementation detail.
The **short summary** should be 13 sentences (ideally 1) and provide
enough information for most readers to quickly decide whether the
tactic is relevant to their task. The first (or only) sentence of
the short summary should be a full sentence in which the subject
is an example invocation of the tactic, written in present tense
indicative. If the example tactic invocation names parameters, then the
short summary may refer to them. For the example invocation, prefer the
simplest or most typical example. Explain more complicated forms in the
variants section. If needed, abbreviate the invocation by naming part of
the syntax and expanding it in the next sentence. The summary should be
written as a single paragraph.
**Details**, if needed, may be 1-3 paragraphs that describe further
relevant information. They may insert links as needed. This section
should fully explain the scope of the tactic: its syntax format,
on which goals it works and what the resulting goal(s) look like. It
should be clear whether the tactic fails if it does not close the main
goal and whether it creates any side goals. The details may include
explanatory examples that cant necessarily be machine checked and
dont fit the format.
If the tactic is extensible using `macro_rules`, mention this in the
details, with a link to `lean-manual://section/tactic-macro-extension`
and give a one-line example. If the tactic provides an attribute or a
command that allows the user to extend its behavior, the documentation
on how to extend the tactic belongs to that attribute or command. In the
tactic docstring, use a single sentence to refer the reader to this
further documentation.
**Variants**, if needed, should be a bulleted list describing different
options and forms of the same tactic. The reader should be able to parse
and understand the parts of a tactic invocation they are hovering over,
using this list. Each list item should describe an individual variant
and take one of two formats: the **short summary** as above, or a
**named list item**. A named list item consists of a title in bold
followed by an indented short paragraph.
Variants should be explained from the perspective of the tactic's users, not
their implementers. A tactic that is implemented as a single Lean parser may
have multiple variants from the perspective of users, while a tactic that is
implemented as multiple parsers may have no variants, but merely an optional
part of the syntax.
**Examples** should start with the line `Examples:` (or `Example:` if
theres exactly one). The section should consist of a sequence of code
blocks, each showing a Lean declaration (usually with the `example`
keyword) that invokes the tactic. When the effect of the tactic is not
clear from the code, you can use code comments to describe this. Do
not include text between examples, because it can be unclear whether
the text refers to the code before or after the example.
##### Example
````
`rw [e]` uses the expression `e` as a rewrite rule on the main goal,
then tries to close the goal by "cheap" (reducible) `rfl`.
If `e` is a defined constant, then the equational theorems associated with `e`
are used. This provides a convenient way to unfold `e`. If `e` has parameters,
the tactic will try to fill these in by unification with the matching part of
the target. Parameters are only filled in once per rule, restricting which
later rewrites can be found. Parameters that are not filled in after
unification will create side goals. If the `rfl` fails to close the main goal,
no error is raised.
`rw` may fail to rewrite terms "under binders", such as `∀ x, ...` or `∃ x,
...`. `rw` can also fail with a "motive is type incorrect" error in the context
of dependent types. In these cases, consider using `simp only`.
* `rw [e₁, ... eₙ]` applies the given rules sequentially.
* `rw [← e]` or `rw [<- e]` applies the rewrite in the reverse direction.
* `rw [e] at l` rewrites with `e` at location(s) `l`.
* `rw (occs := .pos L) [e]`, where `L` is a literal list of natural numbers,
only rewrites the given occurrences in the target. Occurrences count from 1.
* `rw (occs := .neg L) [e]`, where `L` is a literal list of natural numbers,
skips rewriting the given occurrences in the target. Occurrences count from 1.
Examples:
```lean
example {a b : Nat} (h : a + a = b) : (a + a) + (a + a) = b + b := by rw [h]
```
```lean
example {f : Nat -> Nat} (h : ∀ x, f x = 1) (a b : Nat) : f a = f b := by
rw [h] -- `rw` instantiates `h` only once, so this is equivalent to: `rw [h a]`
-- goal: ⊢ 1 = f b
rw [h] -- equivalent to: `rw [h b]`
```
````
## Dictionary

View File

@@ -25,7 +25,6 @@
} ({
buildInputs = with pkgs; [
cmake gmp libuv ccache pkg-config
llvmPackages.bintools # wrapped lld
llvmPackages.llvm # llvm-symbolizer for asan/lsan
gdb
tree # for CI

View File

@@ -8,15 +8,9 @@
},
{
"path": "tests"
},
{
"path": "script"
}
],
"settings": {
// Open terminal at root, not current workspace folder
// (there is not way to directly refer to the root folder included as `.` above)
"terminal.integrated.cwd": "${workspaceFolder:src}/..",
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"cmake.buildDirectory": "${workspaceFolder}/build/release",
@@ -43,15 +37,6 @@
"isDefault": true
}
},
{
"label": "build-old",
"type": "shell",
"command": "make -C build/release -j$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4) LAKE_EXTRA_ARGS=--old",
"problemMatcher": [],
"group": {
"kind": "build"
}
},
{
"label": "test",
"type": "shell",

View File

@@ -1,54 +0,0 @@
This release introduces the Lean module system, which allows files to
control the visibility of their contents for other files. In previous
releases, this feature was available as a preview when the option
`experimental.module` was set to `true`; it is now a fully supported
feature of Lean.
# Benefits
Because modules reduce the amount of information exposed to other
code, they speed up rebuilds because irrelevant changes can be
ignored, they make it possible to be deliberate about API evolution by
hiding details that may change from clients, they help proofs be
checked faster by avoiding accidentally unfolding definitions, and
they lead to smaller executable files through improved dead code
elimination.
# Visibility
A source file is a module if it begins with the `module` keyword. By
default, declarations in a module are private; the `public` modifier
exports them. Proofs of theorems and bodies of definitions are private
by default even when their signatures are public; the bodies of
definitions can be made public by adding the `@[expose]`
attribute. Theorems and opaque constants never expose their bodies.
`public section` and `@[expose] section` change the default visibility
of declarations in the section.
# Imports
Modules may only import other modules. By default, `import` adds the
public information of the imported module to the private scope of the
current module. Adding the `public` modifier to an import places the
imported modules's public information in the public scope of the
current module, exposing it in turn to the current module's clients.
Within a package, `import all` can be used to import another module's
private scope into the current module; this can be used to separate
lemmas or tests from definition modules without exposing details to
downstream clients.
# Meta Code
Code used in metaprograms must be marked `meta`. This ensures that the
code is compiled and available for execution when it is needed during
elaboration. Meta code may only reference other meta code. A whole
module can be made available in the meta phase using `meta import`;
this allows code to be shared across phases by importing the module in
each phase. Code that is reachable from public metaprograms must be
imported via `public meta import`, while local metaprograms can use
plain `meta import` for their dependencies.
The module system is described in detail in [the Lean language reference](https://lean-reference-manual-review.netlify.app/find/?domain=Verso.Genre.Manual.section&name=files).

View File

@@ -0,0 +1,101 @@
/-
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean
namespace Lean.Meta.Grind.Analyzer
/-!
A simple E-matching annotation analyzer.
For each theorem annotated as an E-matching candidate, it creates an artificial goal, executes `grind` and shows the
number of instances created.
For a theorem of the form `params -> type`, the artificial goal is of the form `params -> type -> False`.
-/
/--
`grind` configuration for the analyzer. We disable case-splits and lookahead,
increase the number of generations, and limit the number of instances generated.
-/
def config : Grind.Config := {
splits := 0
lookahead := false
mbtc := false
ematch := 20
instances := 100
gen := 10
}
structure Config where
/-- Minimum number of instantiations to trigger summary report -/
min : Nat := 10
/-- Minimum number of instantiations to trigger detailed report -/
detailed : Nat := 50
def mkParams : MetaM Params := do
let params Grind.mkParams config
let ematch getEMatchTheorems
let casesTypes Grind.getCasesTypes
return { params with ematch, casesTypes }
/-- Returns the total number of generated instances. -/
private def sum (cs : PHashMap Origin Nat) : Nat := Id.run do
let mut r := 0
for (_, c) in cs do
r := r + c
return r
private def thmsToMessageData (thms : PHashMap Origin Nat) : MetaM MessageData := do
let data := thms.toArray.filterMap fun (origin, c) =>
match origin with
| .decl declName => some (declName, c)
| _ => none
let data := data.qsort fun (d₁, c₁) (d₂, c₂) => if c₁ == c₂ then Name.lt d₁ d₂ else c₁ > c₂
let data data.mapM fun (declName, counter) =>
return .trace { cls := `thm } m!"{.ofConst (← mkConstWithLevelParams declName)} ↦ {counter}" #[]
return .trace { cls := `thm } "instances" data
/--
Analyzes theorem `declName`. That is, creates the artificial goal based on `declName` type,
and invokes `grind` on it.
-/
def analyzeEMatchTheorem (declName : Name) (c : Config) : MetaM Unit := do
let info getConstInfo declName
let mvarId forallTelescope info.type fun _ type => do
withLocalDeclD `h type fun _ => do
return ( mkFreshExprMVar (mkConst ``False)).mvarId!
let result Grind.main mvarId ( mkParams) (pure ())
let thms := result.counters.thm
let s := sum thms
if s > c.min then
IO.println s!"{declName} : {s}"
if s > c.detailed then
logInfo m!"{declName}\n{← thmsToMessageData thms}"
-- Not sure why this is failing: `down_pure` perhaps has an unnecessary universe parameter?
run_meta analyzeEMatchTheorem ``Std.Do.SPred.down_pure {}
/-- Analyzes all theorems in the standard library marked as E-matching theorems. -/
def analyzeEMatchTheorems (c : Config := {}) : MetaM Unit := do
let origins := ( getEMatchTheorems).getOrigins
let decls := origins.filterMap fun | .decl declName => some declName | _ => none
for declName in decls.mergeSort Name.lt do
try
analyzeEMatchTheorem declName c
catch e =>
logError m!"{declName} failed with {e.toMessageData}"
logInfo m!"Finished analyzing {decls.length} theorems"
/-- Macro for analyzing E-match theorems with unlimited heartbeats -/
macro "#analyzeEMatchTheorems" : command => `(
set_option maxHeartbeats 0 in
run_meta analyzeEMatchTheorems
)
#analyzeEMatchTheorems
-- -- We can analyze specific theorems using commands such as
set_option trace.grind.ematch.instance true in
run_meta analyzeEMatchTheorem ``List.filterMap_some {}

View File

@@ -1,77 +0,0 @@
import Lake.CLI.Main
/-!
Usage: `lean --run script/Modulize.lean [--meta] file1.lean file2.lean ...`
A simple script that inserts `module` and `public section` into un-modulized files and
bumps their imports to `public`.
When `--meta` is passed, `public meta section` and `public meta import` is used instead.
-/
open Lean Parser.Module
def main (args : List String) : IO Unit := do
let mut args := args
let mut doMeta := false
while !args.isEmpty && args[0]!.startsWith "-" do
match args[0]! with
| "--meta" => doMeta := true
| arg => throw <| .userError s!"unknown flag '{arg}'"
args := args.tail
for path in args do
-- Parse the input file
let mut text IO.FS.readFile path
let inputCtx := Parser.mkInputContext text path
let (header, parserState, msgs) Parser.parseHeader inputCtx
if !msgs.toList.isEmpty then -- skip this file if there are parse errors
msgs.forM fun msg => msg.toString >>= IO.println
throw <| .userError "parse errors in file"
let `(header| $[module%$moduleTk?]? $[prelude%$preludeTk?]? $imps:import*) := header
| throw <| .userError s!"unexpected header syntax of {path}"
if moduleTk?.isSome then
continue
-- initial whitespace if empty header
let startPos := header.raw.getPos? |>.getD parserState.pos
let dummyEnv mkEmptyEnvironment
let (initCmd, parserState', msgs') :=
Parser.parseCommand inputCtx { env := dummyEnv, options := {} } parserState msgs
-- insert section if any trailing command (or error, which could be from an unknown command)
if !initCmd.isOfKind ``Parser.Command.eoi || msgs'.hasErrors then
let insertPos? :=
-- put below initial module docstring if any
guard (initCmd.isOfKind ``Parser.Command.moduleDoc) *> initCmd.getTailPos? <|>
-- else below header
header.raw.getTailPos?
let insertPos := insertPos?.getD startPos -- empty header
let mut sec := if doMeta then
"public meta section"
else
"@[expose] public section"
if !imps.isEmpty then
sec := "\n\n" ++ sec
if insertPos?.isNone then
sec := sec ++ "\n\n"
let insertPos := text.pos! insertPos
text := text.extract text.startPos insertPos ++ sec ++ text.extract insertPos text.endPos
-- prepend each import with `public `
for imp in imps.reverse do
let insertPos := imp.raw.getPos?.get!
let prfx := if doMeta then "public meta " else "public "
let insertPos := text.pos! insertPos
text := text.extract text.startPos insertPos ++ prfx ++ text.extract insertPos text.endPos
-- insert `module` header
let mut initText := text.extract text.startPos (text.pos! startPos)
if !initText.trimAscii.isEmpty then
-- If there is a header comment, preserve it and put `module` in the line after
initText := initText.trimAsciiEnd.toString ++ "\n"
text := initText ++ "module\n\n" ++ text.extract (text.pos! startPos) text.endPos
IO.FS.writeFile path text

View File

@@ -60,7 +60,7 @@ if (arity == fixed + {n}) \{
for j in [n:max + 1] do
let fs := mkFsArgs (j - n)
let sep := if j = n then "" else ", "
emit s!" case {j}: \{ obj* r = FN{j}(f)({fs}{sep}{args}); lean_free_object(f); return r; }\n"
emit s!" case {j}: \{ obj* r = FN{j}(f)({fs}{sep}{args}); lean_free_small_object(f); return r; }\n"
emit " }
}
switch (arity) {\n"
@@ -162,7 +162,7 @@ static obj* fix_args(obj* f, unsigned n, obj*const* as) {
for (unsigned i = 0; i < fixed; i++, source++, target++) {
*target = *source;
}
lean_free_object(f);
lean_free_small_object(f);
}
for (unsigned i = 0; i < n; i++, as++, target++) {
*target = *as;

96
script/bench.sh Executable file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
set -euxo pipefail
cmake --preset release 1>&2
# We benchmark against stage2/bin to test new optimizations.
timeout -s KILL 1h time make -C build/release -j$(nproc) stage3 1>&2
export PATH=$PWD/build/release/stage2/bin:$PATH
# The extra opts used to be passed to the Makefile during benchmarking only but with Lake it is
# easier to configure them statically.
cmake -B build/release/stage3 -S src -DLEAN_EXTRA_LAKEFILE_TOML='weakLeanArgs=["-Dprofiler=true", "-Dprofiler.threshold=9999999", "--stats"]' 1>&2
(
cd tests/bench
timeout -s KILL 1h time temci exec --config speedcenter.yaml --in speedcenter.exec.velcom.yaml 1>&2
temci report run_output.yaml --reporter codespeed2
)
if [ -d .git ]; then
DIR="$(git rev-parse @)"
BASE_URL="https://speed.lean-lang.org/lean4-out/$DIR"
{
cat <<'EOF'
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lakeprof Report</title>
</head>
<h1>Lakeprof Report</h1>
<button type="button" id="btn_fetch">View build trace in Perfetto</button>
<script type="text/javascript">
const ORIGIN = 'https://ui.perfetto.dev';
const btnFetch = document.getElementById('btn_fetch');
async function fetchAndOpen(traceUrl) {
const resp = await fetch(traceUrl);
// Error checking is left as an exercise to the reader.
const blob = await resp.blob();
const arrayBuffer = await blob.arrayBuffer();
openTrace(arrayBuffer, traceUrl);
}
function openTrace(arrayBuffer, traceUrl) {
const win = window.open(ORIGIN);
if (!win) {
btnFetch.style.background = '#f3ca63';
btnFetch.onclick = () => openTrace(arrayBuffer);
btnFetch.innerText = 'Popups blocked, click here to open the trace file';
return;
}
const timer = setInterval(() => win.postMessage('PING', ORIGIN), 50);
const onMessageHandler = (evt) => {
if (evt.data !== 'PONG') return;
// We got a PONG, the UI is ready.
window.clearInterval(timer);
window.removeEventListener('message', onMessageHandler);
const reopenUrl = new URL(location.href);
reopenUrl.hash = `#reopen=${traceUrl}`;
win.postMessage({
perfetto: {
buffer: arrayBuffer,
title: 'Lake Build Trace',
url: reopenUrl.toString(),
}}, ORIGIN);
};
window.addEventListener('message', onMessageHandler);
}
// This is triggered when following the link from the Perfetto UI's sidebar.
if (location.hash.startsWith('#reopen=')) {
const traceUrl = location.hash.substr(8);
fetchAndOpen(traceUrl);
}
EOF
cat <<EOF
btnFetch.onclick = () => fetchAndOpen("$BASE_URL/lakeprof.trace_event");
</script>
EOF
echo "<pre><code>"
(cd src; lakeprof report -prc)
echo "</code></pre>"
echo "</body></html>"
} | tee index.html
curl -T index.html $BASE_URL/index.html
curl -T src/lakeprof.log $BASE_URL/lakeprof.log
curl -T src/lakeprof.trace_event $BASE_URL/lakeprof.trace_event
fi

View File

@@ -10,16 +10,6 @@ Tests language server memory use by repeatedly re-elaborate a given file.
NOTE: only works on Linux for now.
-/
def determineRSS (pid : UInt32) : IO Nat := do
let status IO.FS.readFile s!"/proc/{pid}/smaps_rollup"
let some rssLine := status.splitOn "\n" |>.find? (·.startsWith "Rss:")
| throw <| IO.userError "No RSS in proc status"
let rssLine := rssLine.dropPrefix "Rss:"
let rssLine := rssLine.dropWhile Char.isWhitespace
let some rssInKB := rssLine.takeWhile Char.isDigit |>.toNat?
| throw <| IO.userError "Cannot parse RSS"
return rssInKB
def main (args : List String) : IO Unit := do
let leanCmd :: file :: iters :: args := args | panic! "usage: script <lean> <file> <#iterations> <server-args>..."
let file IO.FS.realPath file
@@ -44,14 +34,11 @@ def main (args : List String) : IO Unit := do
let text IO.FS.readFile file
let (_, headerEndPos, _) Elab.parseImports text
let headerEndPos := FileMap.ofString text |>.leanPosToLspPos headerEndPos
let n := iters.toNat!
let mut lastRSS? : Option Nat := none
let mut totalRSSDelta : Int := 0
let mut requestNo : Nat := 1
let mut versionNo : Nat := 1
Ipc.writeNotification "textDocument/didOpen", {
textDocument := { uri := uri, languageId := "lean", version := 1, text := text } : DidOpenTextDocumentParams }
for i in [0:n] do
for i in [0:iters.toNat!] do
if i > 0 then
versionNo := versionNo + 1
let params : DidChangeTextDocumentParams := {
@@ -74,16 +61,9 @@ def main (args : List String) : IO Unit := do
IO.eprintln diag.message
requestNo := requestNo + 1
let rss determineRSS ( read).pid
-- The first `didChange` usually results in a significantly higher RSS increase than
-- the others, so we ignore it.
if i > 1 then
if let some lastRSS := lastRSS? then
totalRSSDelta := totalRSSDelta + ((rss : Int) - (lastRSS : Int))
lastRSS? := some rss
let avgRSSDelta := totalRSSDelta / (n - 2)
IO.println s!"avg-reelab-rss-delta: {avgRSSDelta}"
let status IO.FS.readFile s!"/proc/{(← read).pid}/status"
for line in status.splitOn "\n" |>.filter (·.startsWith "RssAnon") do
IO.eprintln line
let _ Ipc.collectDiagnostics requestNo uri versionNo
( Ipc.stdin).writeLspMessage (Message.notification "exit" none)

View File

@@ -1,89 +0,0 @@
import Lean.Data.Lsp
import Lean.Elab.Import
open Lean
open Lean.Lsp
open Lean.JsonRpc
/-!
Tests watchdog memory use by repeatedly re-elaborate a given file.
NOTE: only works on Linux for now.
-/
def determineRSS (pid : UInt32) : IO Nat := do
let status IO.FS.readFile s!"/proc/{pid}/smaps_rollup"
let some rssLine := status.splitOn "\n" |>.find? (·.startsWith "Rss:")
| throw <| IO.userError "No RSS in proc status"
let rssLine := rssLine.dropPrefix "Rss:"
let rssLine := rssLine.dropWhile Char.isWhitespace
let some rssInKB := rssLine.takeWhile Char.isDigit |>.toNat?
| throw <| IO.userError "Cannot parse RSS"
return rssInKB
def main (args : List String) : IO Unit := do
let leanCmd :: file :: iters :: args := args | panic! "usage: script <lean> <file> <#iterations> <server-args>..."
let file IO.FS.realPath file
let uri := s!"file://{file}"
Ipc.runWith leanCmd (#["--server", "-DstderrAsMessages=false"] ++ args ++ #[uri]) do
let capabilities := {
textDocument? := some {
completion? := some {
completionItem? := some {
insertReplaceSupport? := true
}
}
}
}
Ipc.writeRequest 0, "initialize", { capabilities : InitializeParams }
discard <| Ipc.readResponseAs 0 InitializeResult
Ipc.writeNotification "initialized", InitializedParams.mk
let text IO.FS.readFile file
let (_, headerEndPos, _) Elab.parseImports text
let headerEndPos := FileMap.ofString text |>.leanPosToLspPos headerEndPos
let n := iters.toNat!
let mut lastRSS? : Option Nat := none
let mut totalRSSDelta : Int := 0
let mut requestNo : Nat := 1
let mut versionNo : Nat := 1
Ipc.writeNotification "textDocument/didOpen", {
textDocument := { uri := uri, languageId := "lean", version := 1, text := text } : DidOpenTextDocumentParams }
for i in [0:iters.toNat!] do
if i > 0 then
versionNo := versionNo + 1
let params : DidChangeTextDocumentParams := {
textDocument := {
uri := uri
version? := versionNo
}
contentChanges := #[TextDocumentContentChangeEvent.rangeChange {
start := headerEndPos
«end» := headerEndPos
} " "]
}
let params := toJson params
Ipc.writeNotification "textDocument/didChange", params
requestNo := requestNo + 1
let diags Ipc.collectDiagnostics requestNo uri versionNo
if let some diags := diags then
for diag in diags.param.diagnostics do
IO.eprintln diag.message
requestNo := requestNo + 1
Ipc.waitForILeans requestNo uri versionNo
let rss determineRSS ( read).pid
-- The first `didChange` usually results in a significantly higher RSS increase than
-- the others, so we ignore it.
if i > 1 then
if let some lastRSS := lastRSS? then
totalRSSDelta := totalRSSDelta + ((rss : Int) - (lastRSS : Int))
lastRSS? := some rss
let avgRSSDelta := totalRSSDelta / (n - 2)
IO.println s!"avg-reelab-rss-delta: {avgRSSDelta}"
let _ Ipc.collectDiagnostics requestNo uri versionNo
Ipc.shutdown requestNo
discard <| Ipc.waitForExit

View File

@@ -1,441 +0,0 @@
#!/usr/bin/env python3
"""
build_artifact.py: Download pre-built CI artifacts for a Lean commit.
Usage:
build_artifact.py # Download artifact for current HEAD
build_artifact.py --sha abc1234 # Download artifact for specific commit
build_artifact.py --clear-cache # Clear artifact cache
This script downloads pre-built binaries from GitHub Actions CI runs,
which is much faster than building from source (~30s vs 2-5min).
Artifacts are cached in ~/.cache/lean_build_artifact/ for reuse.
"""
import argparse
import json
import os
import platform
import shutil
import subprocess
import sys
import urllib.request
import urllib.error
from pathlib import Path
from typing import Optional
# Constants
GITHUB_API_BASE = "https://api.github.com"
LEAN4_REPO = "leanprover/lean4"
# CI artifact cache
CACHE_DIR = Path.home() / '.cache' / 'lean_build_artifact'
ARTIFACT_CACHE = CACHE_DIR
# Sentinel value indicating CI failed (don't bother building locally)
CI_FAILED = object()
# ANSI colors for terminal output
class Colors:
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
BOLD = '\033[1m'
RESET = '\033[0m'
def color(text: str, c: str) -> str:
"""Apply color to text if stdout is a tty."""
if sys.stdout.isatty():
return f"{c}{text}{Colors.RESET}"
return text
def error(msg: str) -> None:
"""Print error message and exit."""
print(color(f"Error: {msg}", Colors.RED), file=sys.stderr)
sys.exit(1)
def warn(msg: str) -> None:
"""Print warning message."""
print(color(f"Warning: {msg}", Colors.YELLOW), file=sys.stderr)
def info(msg: str) -> None:
"""Print info message."""
print(color(msg, Colors.BLUE), file=sys.stderr)
def success(msg: str) -> None:
"""Print success message."""
print(color(msg, Colors.GREEN), file=sys.stderr)
# -----------------------------------------------------------------------------
# Platform detection
# -----------------------------------------------------------------------------
def get_artifact_name() -> Optional[str]:
"""Get CI artifact name for current platform."""
system = platform.system()
machine = platform.machine()
if system == 'Darwin':
if machine == 'arm64':
return 'build-macOS aarch64'
return 'build-macOS' # Intel
elif system == 'Linux':
if machine == 'aarch64':
return 'build-Linux aarch64'
return 'build-Linux release'
# Windows not supported for CI artifact download
return None
# -----------------------------------------------------------------------------
# GitHub API helpers
# -----------------------------------------------------------------------------
_github_token_warning_shown = False
def get_github_token() -> Optional[str]:
"""Get GitHub token from environment or gh CLI."""
global _github_token_warning_shown
# Check environment variable first
token = os.environ.get('GITHUB_TOKEN')
if token:
return token
# Try to get token from gh CLI
try:
result = subprocess.run(
['gh', 'auth', 'token'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip()
except (FileNotFoundError, subprocess.TimeoutExpired):
pass
# Warn once if no token available
if not _github_token_warning_shown:
_github_token_warning_shown = True
warn("No GitHub authentication found. API rate limits may apply.")
warn("Run 'gh auth login' or set GITHUB_TOKEN to avoid rate limiting.")
return None
def github_api_request(url: str) -> dict:
"""Make a GitHub API request and return JSON response."""
headers = {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'build-artifact'
}
token = get_github_token()
if token:
headers['Authorization'] = f'token {token}'
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=30) as response:
return json.loads(response.read().decode())
except urllib.error.HTTPError as e:
if e.code == 403:
error(f"GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable to increase limit.")
elif e.code == 404:
error(f"GitHub resource not found: {url}")
else:
error(f"GitHub API error: {e.code} {e.reason}")
except urllib.error.URLError as e:
error(f"Network error accessing GitHub API: {e.reason}")
# -----------------------------------------------------------------------------
# CI artifact cache functions
# -----------------------------------------------------------------------------
def get_cache_path(sha: str) -> Path:
"""Get cache directory for a commit's artifact."""
return ARTIFACT_CACHE / sha[:12]
def is_cached(sha: str) -> bool:
"""Check if artifact for this commit is already cached and valid."""
cache_path = get_cache_path(sha)
return cache_path.exists() and (cache_path / 'bin' / 'lean').exists()
def check_zstd_support() -> bool:
"""Check if tar supports zstd compression."""
try:
result = subprocess.run(
['tar', '--zstd', '--version'],
capture_output=True,
timeout=5
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
def check_gh_available() -> bool:
"""Check if gh CLI is available and authenticated."""
try:
result = subprocess.run(
['gh', 'auth', 'status'],
capture_output=True,
timeout=10
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
def download_ci_artifact(sha: str, quiet: bool = False):
"""
Try to download CI artifact for a commit.
Returns:
- Path to extracted toolchain directory if available
- CI_FAILED sentinel if CI run failed (don't bother building locally)
- None if no artifact available but local build might work
"""
# Check cache first
if is_cached(sha):
return get_cache_path(sha)
artifact_name = get_artifact_name()
if artifact_name is None:
return None # Unsupported platform
cache_path = get_cache_path(sha)
try:
# Query for CI workflow run for this commit, including status
# Note: Query parameters must be in the URL for GET requests
result = subprocess.run(
['gh', 'api', f'repos/{LEAN4_REPO}/actions/runs?head_sha={sha}&per_page=100',
'--jq', r'.workflow_runs[] | select(.name == "CI") | "\(.id) \(.conclusion // "null")"'],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0 or not result.stdout.strip():
return None # No CI run found (old commit?)
# Parse "run_id conclusion" format
line = result.stdout.strip().split('\n')[0]
parts = line.split(' ', 1)
run_id = parts[0]
conclusion = parts[1] if len(parts) > 1 else "null"
# Check if the desired artifact exists for this run
result = subprocess.run(
['gh', 'api', f'repos/{LEAN4_REPO}/actions/runs/{run_id}/artifacts',
'--jq', f'.artifacts[] | select(.name == "{artifact_name}") | .id'],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0 or not result.stdout.strip():
# No artifact available
# If CI failed and no artifact, the build itself likely failed - skip
if conclusion == "failure":
return CI_FAILED
# Otherwise (in progress, expired, etc.) - fall back to local build
return None
# Download artifact
cache_path.mkdir(parents=True, exist_ok=True)
if not quiet:
print("downloading CI artifact... ", end='', flush=True)
result = subprocess.run(
['gh', 'run', 'download', run_id,
'-n', artifact_name,
'-R', LEAN4_REPO,
'-D', str(cache_path)],
capture_output=True,
text=True,
timeout=600 # 10 minutes for large downloads
)
if result.returncode != 0:
shutil.rmtree(cache_path, ignore_errors=True)
return None
# Extract tar.zst - find the file (name varies by platform/version)
tar_files = list(cache_path.glob('*.tar.zst'))
if not tar_files:
shutil.rmtree(cache_path, ignore_errors=True)
return None
tar_file = tar_files[0]
if not quiet:
print("extracting... ", end='', flush=True)
result = subprocess.run(
['tar', '--zstd', '-xf', tar_file.name],
cwd=cache_path,
capture_output=True,
timeout=300
)
if result.returncode != 0:
shutil.rmtree(cache_path, ignore_errors=True)
return None
# Move contents up from lean-VERSION-PLATFORM/ to cache_path/
# The extracted directory name varies (e.g., lean-4.15.0-linux, lean-4.15.0-darwin_aarch64)
extracted_dirs = [d for d in cache_path.iterdir() if d.is_dir() and d.name.startswith('lean-')]
if extracted_dirs:
extracted = extracted_dirs[0]
for item in extracted.iterdir():
dest = cache_path / item.name
if dest.exists():
if dest.is_dir():
shutil.rmtree(dest)
else:
dest.unlink()
shutil.move(str(item), str(cache_path / item.name))
extracted.rmdir()
# Clean up tar file
tar_file.unlink()
# Verify the extraction worked
if not (cache_path / 'bin' / 'lean').exists():
shutil.rmtree(cache_path, ignore_errors=True)
return None
return cache_path
except (subprocess.TimeoutExpired, FileNotFoundError):
shutil.rmtree(cache_path, ignore_errors=True)
return None
# -----------------------------------------------------------------------------
# Git helpers
# -----------------------------------------------------------------------------
def get_current_commit() -> str:
"""Get the current git HEAD commit SHA."""
try:
result = subprocess.run(
['git', 'rev-parse', 'HEAD'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
return result.stdout.strip()
error(f"Failed to get current commit: {result.stderr.strip()}")
except subprocess.TimeoutExpired:
error("Timeout getting current commit")
except FileNotFoundError:
error("git not found")
def resolve_sha(short_sha: str) -> str:
"""Resolve a (possibly short) SHA to full 40-character SHA using git rev-parse."""
if len(short_sha) == 40:
return short_sha
try:
result = subprocess.run(
['git', 'rev-parse', short_sha],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
full_sha = result.stdout.strip()
if len(full_sha) == 40:
return full_sha
error(f"Cannot resolve SHA '{short_sha}': {result.stderr.strip() or 'not found in repository'}")
except subprocess.TimeoutExpired:
error(f"Timeout resolving SHA '{short_sha}'")
except FileNotFoundError:
error("git not found - required for SHA resolution")
# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description='Download pre-built CI artifacts for a Lean commit.',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
This script downloads pre-built binaries from GitHub Actions CI runs,
which is much faster than building from source (~30s vs 2-5min).
Artifacts are cached in ~/.cache/lean_build_artifact/ for reuse.
Examples:
build_artifact.py # Download for current HEAD
build_artifact.py --sha abc1234 # Download for specific commit
build_artifact.py --clear-cache # Clear cache to free disk space
"""
)
parser.add_argument('--sha', metavar='SHA',
help='Commit SHA to download artifact for (default: current HEAD)')
parser.add_argument('--clear-cache', action='store_true',
help='Clear artifact cache and exit')
parser.add_argument('--quiet', '-q', action='store_true',
help='Suppress progress messages (still prints result path)')
args = parser.parse_args()
# Handle cache clearing
if args.clear_cache:
if ARTIFACT_CACHE.exists():
size = sum(f.stat().st_size for f in ARTIFACT_CACHE.rglob('*') if f.is_file())
shutil.rmtree(ARTIFACT_CACHE)
info(f"Cleared cache at {ARTIFACT_CACHE} ({size / 1024 / 1024:.1f} MB)")
else:
info(f"Cache directory does not exist: {ARTIFACT_CACHE}")
return
# Get commit SHA
if args.sha:
sha = resolve_sha(args.sha)
else:
sha = get_current_commit()
if not args.quiet:
info(f"Commit: {sha[:12]}")
# Check prerequisites
if not check_gh_available():
error("gh CLI not available or not authenticated. Run 'gh auth login' first.")
if not check_zstd_support():
error("tar does not support zstd compression. Install zstd or a newer tar.")
artifact_name = get_artifact_name()
if artifact_name is None:
error(f"No CI artifacts available for this platform ({platform.system()} {platform.machine()})")
if not args.quiet:
info(f"Platform: {artifact_name}")
# Check cache
if is_cached(sha):
path = get_cache_path(sha)
if not args.quiet:
success("Using cached artifact")
print(path)
return
# Download artifact
result = download_ci_artifact(sha, quiet=args.quiet)
if result is CI_FAILED:
if not args.quiet:
print() # End the "downloading..." line
error(f"CI build failed for commit {sha[:12]}")
elif result is None:
if not args.quiet:
print() # End the "downloading..." line
error(f"No CI artifact available for commit {sha[:12]}")
else:
if not args.quiet:
print(color("done", Colors.GREEN))
print(result)
if __name__ == '__main__':
main()

View File

@@ -1,5 +0,0 @@
name = "scripts"
[[lean_exe]]
name = "modulize"
root = "Modulize"

File diff suppressed because it is too large Load Diff

View File

@@ -1,307 +0,0 @@
/-
Copyright Strata Contributors
SPDX-License-Identifier: Apache-2.0 OR MIT
-/
namespace Strata
namespace Python
/-
Parser and translator for some basic regular expression patterns supported by
Python's `re` library
Ref.: https://docs.python.org/3/library/re.html
Also see
https://github.com/python/cpython/blob/759a048d4bea522fda2fe929be0fba1650c62b0e/Lib/re/_parser.py
for a reference implementation.
-/
-------------------------------------------------------------------------------
inductive ParseError where
/--
`patternError` is raised when Python's `re.patternError` exception is
raised.
[Reference: Python's re exceptions](https://docs.python.org/3/library/re.html#exceptions):
"Exception raised when a string passed to one of the functions here is not a
valid regular expression (for example, it might contain unmatched
parentheses) or when some other error occurs during compilation or matching.
It is never an error if a string contains no match for a pattern."
-/
| patternError (message : String) (pattern : String) (pos : String.Pos.Raw)
/--
`unimplemented` is raised whenever we don't support some regex operations
(e.g., lookahead assertions).
-/
| unimplemented (message : String) (pattern : String) (pos : String.Pos.Raw)
deriving Repr
def ParseError.toString : ParseError String
| .patternError msg pat pos => s!"Pattern error at position {pos.byteIdx}: {msg} in pattern '{pat}'"
| .unimplemented msg pat pos => s!"Unimplemented at position {pos.byteIdx}: {msg} in pattern '{pat}'"
instance : ToString ParseError where
toString := ParseError.toString
-------------------------------------------------------------------------------
/--
Regular Expression Nodes
-/
inductive RegexAST where
/-- Single literal character: `a` -/
| char : Char RegexAST
/-- Character range: `[a-z]` -/
| range : Char Char RegexAST
/-- Alternation: `a|b` -/
| union : RegexAST RegexAST RegexAST
/-- Concatenation: `ab` -/
| concat : RegexAST RegexAST RegexAST
/-- Any character: `.` -/
| anychar : RegexAST
/-- Zero or more: `a*` -/
| star : RegexAST RegexAST
/-- One or more: `a+` -/
| plus : RegexAST RegexAST
/-- Zero or one: `a?` -/
| optional : RegexAST RegexAST
/-- Bounded repetition: `a{n,m}` -/
| loop : RegexAST Nat Nat RegexAST
/-- Start of string: `^` -/
| anchor_start : RegexAST
/-- End of string: `$` -/
| anchor_end : RegexAST
/-- Grouping: `(abc)` -/
| group : RegexAST RegexAST
/-- Empty string: `()` or `""` -/
| empty : RegexAST
/-- Complement: `[^a-z]` -/
| complement : RegexAST RegexAST
deriving Inhabited, Repr
-------------------------------------------------------------------------------
/-- Parse character class like [a-z], [0-9], etc. into union of ranges and
chars. Note that this parses `|` as a character. -/
def parseCharClass (s : String) (pos : String.Pos.Raw) : Except ParseError (RegexAST × String.Pos.Raw) := do
if pos.get? s != some '[' then throw (.patternError "Expected '[' at start of character class" s pos)
let mut i := pos.next s
-- Check for complement (negation) with leading ^
let isComplement := !i.atEnd s && i.get? s == some '^'
if isComplement then
i := i.next s
let mut result : Option RegexAST := none
-- Process each element in the character class.
while !i.atEnd s && i.get? s != some ']' do
-- Uncommenting this makes the code stop
--dbg_trace "Working" (pure ())
let some c1 := i.get? s | throw (.patternError "Invalid character in class" s i)
let i1 := i.next s
-- Check for range pattern: c1-c2.
if !i1.atEnd s && i1.get? s == some '-' then
let i2 := i1.next s
if !i2.atEnd s && i2.get? s != some ']' then
let some c2 := i2.get? s | throw (.patternError "Invalid character in range" s i2)
if c1 > c2 then
throw (.patternError s!"Invalid character range [{c1}-{c2}]: \
start character '{c1}' is greater than end character '{c2}'" s i)
let r := RegexAST.range c1 c2
-- Union with previous elements.
result := some (match result with | none => r | some prev => RegexAST.union prev r)
i := i2.next s
continue
-- Single character.
let r := RegexAST.char c1
result := some (match result with | none => r | some prev => RegexAST.union prev r)
i := i.next s
let some ast := result | throw (.patternError "Unterminated character set" s pos)
let finalAst := if isComplement then RegexAST.complement ast else ast
pure (finalAst, i.next s)
-------------------------------------------------------------------------------
/-- Parse numeric repeats like `{10}` or `{1,10}` into min and max bounds. -/
def parseBounds (s : String) (pos : String.Pos.Raw) : Except ParseError (Nat × Nat × String.Pos.Raw) := do
if pos.get? s != some '{' then throw (.patternError "Expected '{' at start of bounds" s pos)
let mut i := pos.next s
let mut numStr := ""
-- Parse first number.
while !i.atEnd s && (i.get? s).any Char.isDigit do
numStr := numStr.push ((i.get? s).get!)
i := i.next s
let some n := numStr.toNat? | throw (.patternError "Invalid minimum bound" s pos)
-- Check for comma (range) or closing brace (exact count).
match i.get? s with
| some '}' => pure (n, n, i.next s) -- {n} means exactly n times.
| some ',' =>
i := i.next s
-- Parse maximum bound
numStr := ""
while !i.atEnd s && (i.get? s).any Char.isDigit do
numStr := numStr.push ((i.get? s).get!)
i := i.next s
let some max := numStr.toNat? | throw (.patternError "Invalid maximum bound" s i)
if i.get? s != some '}' then throw (.patternError "Expected '}' at end of bounds" s i)
-- Validate bounds order
if max < n then
throw (.patternError s!"Invalid repeat bounds \{{n},{max}}: \
maximum {max} is less than minimum {n}" s pos)
pure (n, max, i.next s)
| _ => throw (.patternError "Invalid bounds syntax" s i)
-------------------------------------------------------------------------------
mutual
/--
Parse atom: single element (char, class, anchor, group) with optional
quantifier. Stops at the first `|`.
-/
partial def parseAtom (s : String) (pos : String.Pos.Raw) : Except ParseError (RegexAST × String.Pos.Raw) := do
if pos.atEnd s then throw (.patternError "Unexpected end of regex" s pos)
let some c := pos.get? s | throw (.patternError "Invalid position" s pos)
-- Detect invalid quantifier at start
if c == '*' || c == '+' || c == '{' || c == '?' then
throw (.patternError s!"Quantifier '{c}' at position {pos} has nothing to quantify" s pos)
-- Detect unbalanced closing parenthesis
if c == ')' then
throw (.patternError "Unbalanced parenthesis" s pos)
-- Parse base element (anchor, char class, group, anychar, escape, or single char).
let (base, nextPos) match c with
| '^' => pure (RegexAST.anchor_start, pos.next s)
| '$' => pure (RegexAST.anchor_end, pos.next s)
| '[' => parseCharClass s pos
| '(' => parseExplicitGroup s pos
| '.' => pure (RegexAST.anychar, pos.next s)
| '\\' =>
-- Handle escape sequence.
-- Note: Python uses a single backslash as an escape character, but Lean
-- strings need to escape that. After DDMification, we will see two
-- backslashes in Strata for every Python backslash.
let nextPos := pos.next s
if nextPos.atEnd s then throw (.patternError "Incomplete escape sequence at end of regex" s pos)
let some escapedChar := nextPos.get? s | throw (.patternError "Invalid escape position" s nextPos)
-- Check for special sequences (unsupported right now).
match escapedChar with
| 'A' | 'b' | 'B' | 'd' | 'D' | 's' | 'S' | 'w' | 'W' | 'z' | 'Z' =>
throw (.unimplemented s!"Special sequence \\{escapedChar} is not supported" s pos)
| 'a' | 'f' | 'n' | 'N' | 'r' | 't' | 'u' | 'U' | 'v' | 'x' =>
throw (.unimplemented s!"Escape sequence \\{escapedChar} is not supported" s pos)
| c =>
if c.isDigit then
throw (.unimplemented s!"Backreference \\{c} is not supported" s pos)
else
pure (RegexAST.char escapedChar, nextPos.next s)
| _ => pure (RegexAST.char c, pos.next s)
-- Check for numeric repeat suffix on base element (but not on anchors)
match base with
| .anchor_start | .anchor_end => pure (base, nextPos)
| _ =>
if !nextPos.atEnd s then
match nextPos.get? s with
| some '{' =>
let (min, max, finalPos) parseBounds s nextPos
pure (RegexAST.loop base min max, finalPos)
| some '*' =>
let afterStar := nextPos.next s
if !afterStar.atEnd s then
match afterStar.get? s with
| some '?' => throw (.unimplemented "Non-greedy quantifier *? is not supported" s nextPos)
| some '+' => throw (.unimplemented "Possessive quantifier *+ is not supported" s nextPos)
| _ => pure (RegexAST.star base, afterStar)
else pure (RegexAST.star base, afterStar)
| some '+' =>
let afterPlus := nextPos.next s
if !afterPlus.atEnd s then
match afterPlus.get? s with
| some '?' => throw (.unimplemented "Non-greedy quantifier +? is not supported" s nextPos)
| some '+' => throw (.unimplemented "Possessive quantifier ++ is not supported" s nextPos)
| _ => pure (RegexAST.plus base, afterPlus)
else pure (RegexAST.plus base, afterPlus)
| some '?' =>
let afterQuestion := nextPos.next s
if !afterQuestion.atEnd s then
match afterQuestion.get? s with
| some '?' => throw (.unimplemented "Non-greedy quantifier ?? is not supported" s nextPos)
| some '+' => throw (.unimplemented "Possessive quantifier ?+ is not supported" s nextPos)
| _ => pure (RegexAST.optional base, afterQuestion)
else pure (RegexAST.optional base, afterQuestion)
| _ => pure (base, nextPos)
else
pure (base, nextPos)
/-- Parse explicit group with parentheses. -/
partial def parseExplicitGroup (s : String) (pos : String.Pos.Raw) : Except ParseError (RegexAST × String.Pos.Raw) := do
if pos.get? s != some '(' then throw (.patternError "Expected '(' at start of group" s pos)
let mut i := pos.next s
-- Check for extension notation (?...
if !i.atEnd s && i.get? s == some '?' then
let i1 := i.next s
if !i1.atEnd s then
match i1.get? s with
| some '=' => throw (.unimplemented "Positive lookahead (?=...) is not supported" s pos)
| some '!' => throw (.unimplemented "Negative lookahead (?!...) is not supported" s pos)
| _ => throw (.unimplemented "Extension notation (?...) is not supported" s pos)
let (inner, finalPos) parseGroup s i (some ')')
pure (.group inner, finalPos)
/-- Parse group: handles alternation and concatenation at current scope. -/
partial def parseGroup (s : String) (pos : String.Pos.Raw) (endChar : Option Char) :
Except ParseError (RegexAST × String.Pos.Raw) := do
let mut alternatives : List (List RegexAST) := [[]]
let mut i := pos
-- Parse until end of string or `endChar`.
while !i.atEnd s && (endChar.isNone || i.get? s != endChar) do
if i.get? s == some '|' then
-- Push a new scope to `alternatives`.
alternatives := [] :: alternatives
i := i.next s
else
let (ast, nextPos) parseAtom s i
alternatives := match alternatives with
| [] => [[ast]]
| head :: tail => (ast :: head) :: tail
i := nextPos
-- Check for expected end character.
if let some ec := endChar then
if i.get? s != some ec then
throw (.patternError s!"Expected '{ec}'" s i)
i := i.next s
-- Build result: concatenate each alternative, then union them.
let concatAlts := alternatives.reverse.filterMap fun alt =>
match alt.reverse with
| [] => -- Empty regex.
some (.empty)
| [single] => some single
| head :: tail => some (tail.foldl RegexAST.concat head)
match concatAlts with
| [] => pure (.empty, i)
| [single] => pure (single, i)
| head :: tail => pure (tail.foldl RegexAST.union head, i)
end
/-- info: Except.ok (Strata.Python.RegexAST.range 'A' 'z', { byteIdx := 5 }) -/
#guard_msgs in
#eval parseCharClass "[A-z]" 0
-- Test code: Print done
#print "Done!"

View File

@@ -1 +0,0 @@
lean4

View File

@@ -5,7 +5,6 @@ Merge a tag into a branch on a GitHub repository.
This script checks if a specified tag can be merged cleanly into a branch and performs
the merge if possible. If the merge cannot be done cleanly, it prints a helpful message.
Merge conflicts in the lean-toolchain file are automatically resolved by accepting the incoming changes.
Usage:
python3 merge_remote.py <org/repo> <branch> <tag>
@@ -59,32 +58,6 @@ def clone_repo(repo, temp_dir):
return True
def get_conflicted_files():
"""Get list of files with merge conflicts."""
result = run_command("git diff --name-only --diff-filter=U", check=False)
if result.returncode == 0:
return result.stdout.strip().split('\n') if result.stdout.strip() else []
return []
def resolve_lean_toolchain_conflict(tag):
"""Resolve lean-toolchain conflict by accepting incoming (tag) changes."""
print("Resolving lean-toolchain conflict by accepting incoming changes...")
# Accept theirs (incoming) version for lean-toolchain
result = run_command(f"git checkout --theirs lean-toolchain", check=False)
if result.returncode != 0:
print("Failed to resolve lean-toolchain conflict")
return False
# Add the resolved file
add_result = run_command("git add lean-toolchain", check=False)
if add_result.returncode != 0:
print("Failed to stage resolved lean-toolchain")
return False
return True
def check_and_merge(repo, branch, tag, temp_dir):
"""Check if tag can be merged into branch and perform the merge if possible."""
# Change to the temporary directory
@@ -125,37 +98,12 @@ def check_and_merge(repo, branch, tag, temp_dir):
# Try merging the tag directly
print(f"Merging {tag} into {branch}...")
merge_result = run_command(f"git merge {tag} --no-edit", check=False)
if merge_result.returncode != 0:
# Check which files have conflicts
conflicted_files = get_conflicted_files()
if conflicted_files == ['lean-toolchain']:
# Only lean-toolchain has conflicts, resolve it
print("Merge conflict detected only in lean-toolchain.")
if resolve_lean_toolchain_conflict(tag):
# Continue the merge with the resolved conflict
print("Continuing merge with resolved lean-toolchain...")
continue_result = run_command(f"git commit --no-edit", check=False)
if continue_result.returncode != 0:
print("Failed to complete merge after resolving lean-toolchain")
run_command("git merge --abort")
return False
else:
print("Failed to resolve lean-toolchain conflict")
run_command("git merge --abort")
return False
else:
# Other files have conflicts, or unable to determine
if conflicted_files:
print(f"Cannot merge {tag} cleanly into {branch}.")
print(f"Merge conflicts in: {', '.join(conflicted_files)}")
else:
print(f"Cannot merge {tag} cleanly into {branch}.")
print("Merge conflicts would occur.")
print("Aborting merge.")
run_command("git merge --abort")
return False
print(f"Cannot merge {tag} cleanly into {branch}.")
print("Merge conflicts would occur. Aborting merge.")
run_command("git merge --abort")
return False
print(f"Pushing changes to remote...")
push_result = run_command(f"git push origin {branch}")

View File

@@ -58,11 +58,7 @@ OPTIONS=()
# We build cadical using the custom toolchain on Linux to avoid glibc versioning issues
echo -n " -DLEAN_STANDALONE=ON -DCADICAL_USE_CUSTOM_CXX=ON"
echo -n " -DCMAKE_CXX_COMPILER=$PWD/llvm-host/bin/clang++ -DLEAN_CXX_STDLIB='-Wl,-Bstatic -lc++ -lc++abi -Wl,-Bdynamic'"
# these should also be used for cadical, so do not use `LEAN_EXTRA_CXX_FLAGS` here
echo -n " -DCMAKE_CXX_FLAGS='--sysroot $PWD/llvm -idirafter $GLIBC_DEV/include ${EXTRA_FLAGS:-}'"
# the above does not include linker flags which will be added below based on context, so skip the
# generic check by cmake
echo -n " -DCMAKE_C_COMPILER_WORKS=1 -DCMAKE_CXX_COMPILER_WORKS=1"
echo -n " -DLEAN_EXTRA_CXX_FLAGS='--sysroot $PWD/llvm -idirafter $GLIBC_DEV/include ${EXTRA_FLAGS:-}'"
# use target compiler directly when not cross-compiling
if [[ -L llvm-host ]]; then
echo -n " -DCMAKE_C_COMPILER=$PWD/stage1/bin/clang"

View File

@@ -1,60 +1,5 @@
#!/usr/bin/env python3
"""
Release Checklist for Lean4 and Downstream Repositories
This script validates the status of a Lean4 release across all dependent repositories.
It checks whether repositories are ready for release and identifies missing steps.
IMPORTANT: Keep this documentation up-to-date when modifying the script's behavior!
What this script does:
1. Validates preliminary Lean4 release infrastructure:
- Checks that the release branch (releases/vX.Y.0) exists
- Verifies CMake version settings are correct
- Confirms the release tag exists
- Validates the release page exists on GitHub (created automatically by CI after tag push)
- Checks the release notes page on lean-lang.org (updated while bumping the `reference-manual` repository)
**IMPORTANT: If the release page doesn't exist, the script will skip checking
downstream repositories and the master branch configuration. The preliminary
infrastructure must be in place before the release process can proceed.**
**NOTE: The GitHub release page is created AUTOMATICALLY by CI after the tag is pushed.
DO NOT create it manually. Wait for CI to complete after pushing the tag.**
2. For each downstream repository (batteries, mathlib4, etc.):
- Checks if dependencies are ready (e.g., mathlib4 depends on batteries)
- Verifies the main branch is on the target toolchain (or newer)
- Checks if a PR exists to bump the toolchain (if not yet updated)
- Validates tags exist for the release version
- Ensures tags are merged into stable branches (for non-RC releases)
- Verifies bump branches exist and are configured correctly
- Special handling for ProofWidgets4 release tags
- For mathlib4: runs verify_version_tags.py to validate the release tag
(checks git/GitHub consistency, toolchain, elan, cache, and build)
3. Optionally automates missing steps (when not in --dry-run mode):
- Creates missing release tags using push_repo_release_tag.py
- Merges tags into stable branches using merge_remote.py
Usage:
./release_checklist.py v4.24.0 # Check release status
./release_checklist.py v4.24.0 --verbose # Show detailed debug info
./release_checklist.py v4.24.0 --dry-run # Check only, don't execute fixes
For automated release management with Claude Code:
/release v4.24.0 # Run full release process with Claude
The script reads repository configurations from release_repos.yml and reports:
- ✅ for completed requirements
- ❌ for missing requirements (with instructions to fix)
- 🟡 for repositories waiting on dependencies
- ⮕ for automated actions being taken
This script is idempotent and safe to rerun multiple times.
"""
import argparse
import yaml
import requests
@@ -131,39 +76,6 @@ def release_page_exists(repo_url, tag_name, github_token):
response = requests.get(api_url, headers=headers)
return response.status_code == 200
def get_tag_workflow_status(repo_url, tag_name, github_token):
"""Get the status of CI workflows running for a specific tag."""
api_base = repo_url.replace("https://github.com/", "https://api.github.com/repos/")
headers = {'Authorization': f'token {github_token}'} if github_token else {}
# Get workflow runs for the tag
# GitHub's workflow runs API uses the branch/tag name in the 'head_branch' field
api_url = f"{api_base}/actions/runs?event=push&head_branch={tag_name}"
response = requests.get(api_url, headers=headers)
if response.status_code != 200:
return None
data = response.json()
workflow_runs = data.get('workflow_runs', [])
if not workflow_runs:
return None
# Get the most recent workflow run for this tag
run = workflow_runs[0]
status = run.get('status')
conclusion = run.get('conclusion')
workflow_name = run.get('name', 'CI')
run_id = run.get('id')
return {
'status': status,
'conclusion': conclusion,
'workflow_name': workflow_name,
'run_id': run_id
}
def get_release_notes(tag_name):
"""Fetch release notes page title from lean-lang.org."""
# Strip -rcX suffix if present for the URL
@@ -172,17 +84,20 @@ def get_release_notes(tag_name):
try:
response = requests.get(reference_url)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
# Extract title using regex
match = re.search(r"<title>(.*?)</title>", response.text, re.IGNORECASE | re.DOTALL)
if match:
return match.group(1).strip()
else:
print(f" ⚠️ Could not find <title> tag in {reference_url}")
return None
except requests.exceptions.RequestException:
except requests.exceptions.RequestException as e:
print(f" ❌ Error fetching release notes from {reference_url}: {e}")
return None
except Exception:
except Exception as e:
print(f" ❌ An unexpected error occurred while processing release notes: {e}")
return None
def get_branch_content(repo_url, branch, file_path, github_token):
@@ -371,68 +286,6 @@ def check_bump_branch_toolchain(url, bump_branch, github_token):
print(f" ✅ Bump branch correctly uses toolchain: {content}")
return True
def get_pr_ci_status(repo_url, pr_number, github_token):
"""Get the CI status for a pull request."""
api_base = repo_url.replace("https://github.com/", "https://api.github.com/repos/")
headers = {'Authorization': f'token {github_token}'} if github_token else {}
# Get PR details to find the head SHA
pr_response = requests.get(f"{api_base}/pulls/{pr_number}", headers=headers)
if pr_response.status_code != 200:
return "unknown", "Could not fetch PR details"
pr_data = pr_response.json()
head_sha = pr_data['head']['sha']
# Get check runs for the commit
check_runs_response = requests.get(
f"{api_base}/commits/{head_sha}/check-runs",
headers=headers
)
if check_runs_response.status_code != 200:
return "unknown", "Could not fetch check runs"
check_runs_data = check_runs_response.json()
check_runs = check_runs_data.get('check_runs', [])
if not check_runs:
# No check runs, check for status checks (legacy)
status_response = requests.get(
f"{api_base}/commits/{head_sha}/status",
headers=headers
)
if status_response.status_code == 200:
status_data = status_response.json()
state = status_data.get('state', 'unknown')
if state == 'success':
return "success", "All status checks passed"
elif state == 'failure':
return "failure", "Some status checks failed"
elif state == 'pending':
return "pending", "Status checks in progress"
return "unknown", "No CI checks found"
# Analyze check runs
conclusions = [run['conclusion'] for run in check_runs if run.get('status') == 'completed']
in_progress = [run for run in check_runs if run.get('status') in ['queued', 'in_progress']]
if in_progress:
return "pending", f"{len(in_progress)} check(s) in progress"
if not conclusions:
return "pending", "Checks queued"
if all(c == 'success' for c in conclusions):
return "success", f"All {len(conclusions)} checks passed"
failed = sum(1 for c in conclusions if c in ['failure', 'timed_out', 'action_required'])
if failed > 0:
return "failure", f"{failed} check(s) failed"
# Some checks are cancelled, skipped, or neutral
return "warning", f"Some checks did not complete normally"
def pr_exists_with_title(repo_url, title, github_token):
api_url = repo_url.replace("https://github.com/", "https://api.github.com/repos/") + "/pulls"
headers = {'Authorization': f'token {github_token}'} if github_token else {}
@@ -501,127 +354,6 @@ def check_proofwidgets4_release(repo_url, target_toolchain, github_token):
print(f" You will need to create and push a tag v0.0.{next_version}")
return False
def check_reference_manual_release_title(repo_url, toolchain, pr_branch, github_token):
"""Check if the reference-manual release notes title matches the release type.
For RC releases (e.g., v4.27.0-rc1), the title should contain the exact RC suffix.
For final releases (e.g., v4.27.0), the title should NOT contain any "-rc".
Returns True if check passes or is not applicable, False if title needs updating.
"""
is_rc = is_release_candidate(toolchain)
# For RC releases, get the base version and RC suffix
# e.g., "v4.27.0-rc1" -> version="4.27.0", rc_suffix="-rc1"
if is_rc:
parts = toolchain.lstrip('v').split('-', 1)
version = parts[0]
rc_suffix = '-' + parts[1] if len(parts) > 1 else ''
else:
version = toolchain.lstrip('v')
rc_suffix = ''
# Construct the release notes file path (e.g., Manual/Releases/v4_27_0.lean for v4.27.0)
file_name = f"v{version.replace('.', '_')}.lean" # "v4_27_0.lean"
file_path = f"Manual/Releases/{file_name}"
# Try to get the file from the PR branch first, then fall back to main branch
content = get_branch_content(repo_url, pr_branch, file_path, github_token)
if content is None:
# Try the default branch
content = get_branch_content(repo_url, "main", file_path, github_token)
if content is None:
print(f" ⚠️ Could not check release notes file: {file_path}")
return True # Don't block on this
# Look for the #doc line with the title
for line in content.splitlines():
if line.strip().startswith('#doc') and 'Manual' in line:
has_rc_in_title = '-rc' in line.lower()
if is_rc:
# For RC releases, title should contain the exact RC suffix (e.g., "-rc1")
# Use regex to match exact suffix followed by non-digit (to avoid -rc1 matching -rc10)
# Pattern matches the RC suffix followed by a non-digit or end-of-string context
# e.g., "-rc1" followed by space, quote, paren, or similar
exact_match = re.search(rf'{re.escape(rc_suffix)}(?![0-9])', line, re.IGNORECASE)
if exact_match:
print(f" ✅ Release notes title correctly shows {rc_suffix}")
return True
elif has_rc_in_title:
print(f" ❌ Release notes title shows wrong RC version (expected {rc_suffix})")
print(f" Update {file_path} to use '{rc_suffix}' in the title")
return False
else:
print(f" ❌ Release notes title missing RC suffix")
print(f" Update {file_path} to include '{rc_suffix}' in the title")
return False
else:
# For final releases, title should NOT contain -rc
if has_rc_in_title:
print(f" ❌ Release notes title still shows RC version")
print(f" Update {file_path} to remove '-rcN' from the title")
return False
else:
print(f" ✅ Release notes title is updated for final release")
return True
# If we didn't find the #doc line, don't block
print(f" ⚠️ Could not find release notes title in {file_path}")
return True
def run_mathlib_verify_version_tags(toolchain, verbose=False):
"""Run mathlib4's verify_version_tags.py script to validate the release tag.
This clones mathlib4 to a temp directory and runs the verification script.
Returns True if verification passes, False otherwise.
"""
import tempfile
print(f" ... Running mathlib4 verify_version_tags.py {toolchain}")
with tempfile.TemporaryDirectory() as tmpdir:
# Clone mathlib4 (shallow clone is sufficient for running the script)
clone_result = subprocess.run(
['git', 'clone', '--depth', '1', 'https://github.com/leanprover-community/mathlib4.git', tmpdir],
capture_output=True,
text=True
)
if clone_result.returncode != 0:
print(f" ❌ Failed to clone mathlib4: {clone_result.stderr.strip()[:200]}")
return False
# Run the verification script
script_path = os.path.join(tmpdir, 'scripts', 'verify_version_tags.py')
if not os.path.exists(script_path):
print(f" ❌ verify_version_tags.py not found in mathlib4 (expected at scripts/verify_version_tags.py)")
return False
# Run from the mathlib4 directory so git operations work
result = subprocess.run(
['python3', script_path, toolchain],
cwd=tmpdir,
capture_output=True,
text=True,
timeout=900 # 15 minutes timeout for cache download etc.
)
# Print output with indentation
if result.stdout:
for line in result.stdout.strip().split('\n'):
print(f" {line}")
if result.stderr:
for line in result.stderr.strip().split('\n'):
print(f" {line}")
if result.returncode != 0:
print(f" ❌ mathlib4 verify_version_tags.py failed")
return False
print(f" ✅ mathlib4 verify_version_tags.py passed")
return True
def main():
parser = argparse.ArgumentParser(description="Check release status of Lean4 repositories")
parser.add_argument("toolchain", help="The toolchain version to check (e.g., v4.6.0)")
@@ -672,57 +404,30 @@ def main():
print(f" ❌ Short commit hash {commit_hash[:SHORT_HASH_LENGTH]} is numeric and starts with 0, causing issues for version parsing. Try regenerating the last commit to get a new hash.")
lean4_success = False
release_page_ready = release_page_exists(lean_repo_url, toolchain, github_token)
if not release_page_ready:
print(f" ❌ Release page for {toolchain} does not exist (This will be created by CI.)")
# Check CI workflow status
workflow_status = get_tag_workflow_status(lean_repo_url, toolchain, github_token)
if workflow_status:
status = workflow_status['status']
conclusion = workflow_status['conclusion']
workflow_name = workflow_status['workflow_name']
run_id = workflow_status['run_id']
workflow_url = f"{lean_repo_url}/actions/runs/{run_id}"
if status == 'in_progress' or status == 'queued':
print(f" 🔄 {workflow_name} workflow is {status}: {workflow_url}")
elif status == 'completed':
if conclusion == 'success':
print(f"{workflow_name} workflow completed successfully: {workflow_url}")
elif conclusion == 'failure':
print(f"{workflow_name} workflow failed: {workflow_url}")
else:
print(f" ⚠️ {workflow_name} workflow completed with status: {conclusion}: {workflow_url}")
else:
print(f" {workflow_name} workflow status: {status}: {workflow_url}")
if not release_page_exists(lean_repo_url, toolchain, github_token):
print(f" ❌ Release page for {toolchain} does not exist")
lean4_success = False
else:
print(f" ✅ Release page for {toolchain} exists")
# Check the actual release notes page title (informational only - does not block)
# Check the actual release notes page title
actual_title = get_release_notes(toolchain)
expected_title_prefix = f"Lean {toolchain.lstrip('v')}" # e.g., "Lean 4.19.0" or "Lean 4.19.0-rc1"
base_tag = toolchain.split('-')[0]
release_notes_url = f"https://lean-lang.org/doc/reference/latest/releases/{base_tag}/"
if actual_title is None:
print(f" ⚠️ Release notes not found at {release_notes_url} (this will be fixed while updating the reference-manual repository)")
# Error already printed by get_release_notes
lean4_success = False
elif not actual_title.startswith(expected_title_prefix):
print(f" ⚠️ Release notes page title mismatch. Expected prefix '{expected_title_prefix}', got '{actual_title}'. Check {release_notes_url}")
# Construct URL for the error message (using the base tag)
base_tag = toolchain.split('-')[0]
check_url = f"https://lean-lang.org/doc/reference/latest/releases/{base_tag}/"
print(f" ❌ Release notes page title mismatch. Expected prefix '{expected_title_prefix}', got '{actual_title}'. Check {check_url}")
lean4_success = False
else:
print(f" ✅ Release notes page title looks good ('{actual_title}').")
repo_status["lean4"] = lean4_success
# If the release page doesn't exist, skip repository checks and master branch checks
# The preliminary infrastructure must be in place first
if not release_page_exists(lean_repo_url, toolchain, github_token):
print("\n⚠️ Release process blocked: preliminary Lean4 infrastructure incomplete.")
print(" Complete the steps above, then rerun this script to proceed with downstream repositories.")
return
# Load repositories and perform further checks
print("\nChecking repositories...")
@@ -766,24 +471,6 @@ def main():
if pr_info:
pr_number, pr_url = pr_info
print(f" ✅ PR with title '{pr_title}' exists: #{pr_number} ({pr_url})")
# Check CI status
ci_status, ci_message = get_pr_ci_status(url, pr_number, github_token)
if ci_status == "success":
print(f" ✅ CI: {ci_message}")
elif ci_status == "failure":
print(f" ❌ CI: {ci_message}")
elif ci_status == "pending":
print(f" 🔄 CI: {ci_message}")
elif ci_status == "warning":
print(f" ⚠️ CI: {ci_message}")
else:
print(f" ❓ CI: {ci_message}")
# For reference-manual, check that the release notes title has been updated
if name == "reference-manual":
pr_branch = f"bump_to_{toolchain}"
check_reference_manual_release_title(url, toolchain, pr_branch, github_token)
else:
print(f" ❌ PR with title '{pr_title}' does not exist")
print(f" Run `script/release_steps.py {toolchain} {name}` to create it")
@@ -891,12 +578,6 @@ def main():
repo_status[name] = False
continue
# For mathlib4, run verify_version_tags.py to validate the release tag
if name == "mathlib4":
if not run_mathlib_verify_version_tags(toolchain, verbose):
repo_status[name] = False
continue
repo_status[name] = success
# Final check for lean4 master branch

View File

@@ -52,7 +52,6 @@ def sort_sections_order():
return [
"Language",
"Library",
"Tactics",
"Compiler",
"Pretty Printing",
"Documentation",

View File

@@ -1,11 +1,4 @@
repositories:
- name: lean4-cli
url: https://github.com/leanprover/lean4-cli
toolchain-tag: true
stable-branch: false
branch: main
dependencies: []
- name: batteries
url: https://github.com/leanprover-community/batteries
toolchain-tag: true
@@ -14,13 +7,6 @@ repositories:
bump-branch: true
dependencies: []
- name: verso
url: https://github.com/leanprover/verso
toolchain-tag: true
stable-branch: false
branch: main
dependencies: []
- name: lean4checker
url: https://github.com/leanprover/lean4checker
toolchain-tag: true
@@ -35,6 +21,20 @@ repositories:
branch: master
dependencies: []
- name: lean4-cli
url: https://github.com/leanprover/lean4-cli
toolchain-tag: true
stable-branch: false
branch: main
dependencies: []
- name: verso
url: https://github.com/leanprover/verso
toolchain-tag: true
stable-branch: false
branch: main
dependencies: []
- name: plausible
url: https://github.com/leanprover-community/plausible
toolchain-tag: true
@@ -50,26 +50,12 @@ repositories:
dependencies:
- lean4-cli
- name: lean4-unicode-basic
url: https://github.com/fgdorais/lean4-unicode-basic
toolchain-tag: true
stable-branch: false
branch: main
dependencies: []
- name: BibtexQuery
url: https://github.com/dupuisf/BibtexQuery
toolchain-tag: true
stable-branch: false
branch: master
dependencies: [lean4-unicode-basic]
- name: doc-gen4
url: https://github.com/leanprover/doc-gen4
toolchain-tag: true
stable-branch: false
branch: main
dependencies: [lean4-cli, BibtexQuery]
dependencies: [lean4-cli]
- name: reference-manual
url: https://github.com/leanprover/reference-manual
@@ -110,15 +96,6 @@ repositories:
- import-graph
- plausible
- name: cslib
url: https://github.com/leanprover/cslib
toolchain-tag: true
stable-branch: true
branch: main
bump-branch: true
dependencies:
- mathlib4
- name: repl
url: https://github.com/leanprover-community/repl
toolchain-tag: true
@@ -126,31 +103,3 @@ repositories:
branch: master
dependencies:
- mathlib4
- name: verso-web-components
url: https://github.com/leanprover/verso-web-components
toolchain-tag: true
stable-branch: false
branch: main
dependencies:
- verso
- name: lean-fro.org
url: https://github.com/leanprover/lean-fro.org
toolchain-tag: false
stable-branch: false
branch: master
dependencies:
- verso-web-components
- name: comparator
url: https://github.com/leanprover/comparator
toolchain-tag: true
stable-branch: false
branch: master
- name: lean4export
url: https://github.com/leanprover/lean4export
toolchain-tag: true
stable-branch: false
branch: master

View File

@@ -1,54 +1,30 @@
#!/usr/bin/env python3
"""
Execute Release Steps for Lean4 Downstream Repositories
Execute release steps for Lean4 repositories.
This script automates the process of updating a downstream repository to a new Lean4 release.
It handles creating branches, updating toolchains, merging changes, building, testing, and
creating pull requests.
IMPORTANT: Keep this documentation up-to-date when modifying the script's behavior!
What this script does:
1. Sets up the downstream_releases/ directory for cloning repositories
2. Clones or updates the target repository
3. Creates a branch named bump_to_{version} for the changes
4. Updates the lean-toolchain file to the target version
5. Handles repository-specific variations:
- Different dependency update mechanisms
- Special merging strategies for repositories with nightly-testing branches
- Safety checks for repositories using bump branches
- Custom build and test procedures
- lean-fro.org: runs scripts/update.sh to regenerate site content
6. Commits the changes with message "chore: bump toolchain to {version}"
7. Builds the project (with a clean .lake cache)
8. Runs tests if available
9. Pushes the branch to GitHub
10. Creates a pull request (or reports if one already exists)
This script helps automate the release process for Lean4 and its dependent repositories
by actually executing the step-by-step instructions for updating toolchains, creating tags,
and managing branches.
Usage:
./release_steps.py v4.24.0 batteries # Update batteries to v4.24.0
./release_steps.py v4.24.0-rc1 mathlib4 # Update mathlib4 to v4.24.0-rc1
python3 release_steps.py <version> <repo>
The script reads repository configurations from release_repos.yml.
Each repository has specific handling for merging, dependencies, and testing.
Arguments:
version: The version to set in the lean-toolchain file (e.g., v4.6.0)
repo: The repository name as specified in release_repos.yml
This script is idempotent - it's safe to rerun if it fails partway through.
Existing branches, commits, and PRs will be reused rather than duplicated.
Example:
python3 release_steps.py v4.6.0 mathlib4
python3 release_steps.py v4.6.0 batteries
Error handling:
- If build or tests fail, the script continues to create the PR anyway
- Manual conflicts must be resolved by the user
- Network issues during push/PR creation are reported with manual instructions
The script reads repository configurations from release_repos.yml in the same directory.
Each repository may have specific requirements for:
- Branch management
- Toolchain updates
- Dependency updates
- Tagging conventions
- Stable branch handling
"""
import argparse
@@ -401,27 +377,6 @@ def execute_release_steps(repo, version, config):
except subprocess.CalledProcessError as e:
print(red("Tests failed, but continuing with PR creation..."))
print(red(f"Test error: {e}"))
elif repo_name == "lean-fro.org":
# Update lean-toolchain in examples/hero
print(blue("Updating examples/hero/lean-toolchain..."))
docs_toolchain = repo_path / "examples" / "hero" / "lean-toolchain"
with open(docs_toolchain, "w") as f:
f.write(f"leanprover/lean4:{version}\n")
print(green(f"Updated examples/hero/lean-toolchain to leanprover/lean4:{version}"))
print(blue("Running `lake update`..."))
run_command("lake update", cwd=repo_path, stream_output=True)
print(blue("Running `lake update` in examples/hero..."))
run_command("lake update", cwd=repo_path / "examples" / "hero", stream_output=True)
# Run scripts/update.sh to regenerate content
print(blue("Running `scripts/update.sh` to regenerate content..."))
run_command("scripts/update.sh", cwd=repo_path, stream_output=True)
print(green("Content regenerated successfully"))
elif repo_name == "cslib":
print(blue("Updating lakefile.toml..."))
run_command(f'perl -pi -e \'s/"v4\\.[0-9]+(\\.[0-9]+)?(-rc[0-9]+)?"/"' + version + '"/g\' lakefile.*', cwd=repo_path)
run_command("lake update", cwd=repo_path, stream_output=True)
elif dependencies:
run_command(f'perl -pi -e \'s/"v4\\.[0-9]+(\\.[0-9]+)?(-rc[0-9]+)?"/"' + version + '"/g\' lakefile.*', cwd=repo_path)
run_command("lake update", cwd=repo_path, stream_output=True)
@@ -584,19 +539,8 @@ def execute_release_steps(repo, version, config):
# Clean lake cache for a fresh build
print(blue("Cleaning lake cache..."))
run_command("lake clean", cwd=repo_path)
# Check if downstream of Mathlib and get cache if so
mathlib_package_dir = repo_path / ".lake" / "packages" / "mathlib"
if mathlib_package_dir.exists():
print(blue("Project is downstream of Mathlib, fetching cache..."))
try:
run_command("lake exe cache get", cwd=repo_path, stream_output=True)
print(green("Cache fetched successfully"))
except subprocess.CalledProcessError as e:
print(yellow("Failed to fetch cache, continuing anyway..."))
print(yellow(f"Cache fetch error: {e}"))
run_command("rm -rf .lake", cwd=repo_path)
try:
run_command("lake build", cwd=repo_path, stream_output=True)
print(green("Build completed successfully"))

View File

@@ -10,7 +10,7 @@ endif()
include(ExternalProject)
project(LEAN CXX C)
set(LEAN_VERSION_MAJOR 4)
set(LEAN_VERSION_MINOR 28)
set(LEAN_VERSION_MINOR 24)
set(LEAN_VERSION_PATCH 0)
set(LEAN_VERSION_IS_RELEASE 0) # This number is 1 in the release revision, and 0 otherwise.
set(LEAN_SPECIAL_VERSION_DESC "" CACHE STRING "Additional version description like 'nightly-2018-03-11'")
@@ -34,19 +34,8 @@ if (NOT LEAN_PLATFORM_TARGET)
OUTPUT_VARIABLE LEAN_PLATFORM_TARGET OUTPUT_STRIP_TRAILING_WHITESPACE)
endif()
set(LEAN_EXTRA_LINKER_FLAGS_DEFAULT "")
# Use lld by default, if available
find_program(LLD_PATH lld)
if(LLD_PATH)
string(APPEND LEAN_EXTRA_LINKER_FLAGS_DEFAULT " -fuse-ld=lld")
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
# Create space in install names so they can be patched later in Nix.
string(APPEND LEAN_EXTRA_LINKER_FLAGS_DEFAULT " -headerpad_max_install_names")
endif()
set(LEAN_EXTRA_LINKER_FLAGS ${LEAN_EXTRA_LINKER_FLAGS_DEFAULT} CACHE STRING "Additional flags used by the linker")
set(LEAN_EXTRA_CXX_FLAGS "" CACHE STRING "Additional flags used by the C++ compiler. Unlike `CMAKE_CXX_FLAGS`, these will not be used to build e.g. cadical.")
set(LEAN_EXTRA_LINKER_FLAGS "" CACHE STRING "Additional flags used by the linker")
set(LEAN_EXTRA_CXX_FLAGS "" CACHE STRING "Additional flags used by the C++ compiler")
set(LEAN_TEST_VARS "LEAN_CC=${CMAKE_C_COMPILER}" CACHE STRING "Additional environment variables used when running tests")
if (NOT CMAKE_BUILD_TYPE)
@@ -93,7 +82,6 @@ option(USE_MIMALLOC "use mimalloc" ON)
# development-specific options
option(CHECK_OLEAN_VERSION "Only load .olean files compiled with the current version of Lean" OFF)
option(USE_LAKE "Use Lake instead of lean.mk for building core libs from language server" ON)
option(USE_LAKE_CACHE "Use the Lake artifact cache for stage 1 builds (requires USE_LAKE)" OFF)
set(LEAN_EXTRA_MAKE_OPTS "" CACHE STRING "extra options to lean --make")
set(LEANC_CC ${CMAKE_C_COMPILER} CACHE STRING "C compiler to use in `leanc`")
@@ -195,7 +183,7 @@ endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules")
# Initialize CXXFLAGS.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LEAN_EXTRA_CXX_FLAGS} -DLEAN_BUILD_TYPE=\"${CMAKE_BUILD_TYPE}\" -DLEAN_EXPORTING")
set(CMAKE_CXX_FLAGS "${LEAN_EXTRA_CXX_FLAGS} -DLEAN_BUILD_TYPE=\"${CMAKE_BUILD_TYPE}\" -DLEAN_EXPORTING")
set(CMAKE_CXX_FLAGS_DEBUG "-DLEAN_DEBUG")
set(CMAKE_CXX_FLAGS_MINSIZEREL "-DNDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG")
@@ -452,18 +440,15 @@ if(LLVM AND ${STAGE} GREATER 0)
# - In particular, `host/bin/llvm-config` produces flags like `-Lllvm-host/lib/libLLVM`, while
# we need the path to be `-Lllvm/lib/libLLVM`. Thus, we perform this replacement here.
string(REPLACE "llvm-host" "llvm" LEANSHARED_LINKER_FLAGS ${LEANSHARED_LINKER_FLAGS})
string(REPLACE "llvm-host" "llvm" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
message(VERBOSE "leanshared linker flags: '${LEANSHARED_LINKER_FLAGS}' | lean extra cxx flags '${CMAKE_CXX_FLAGS}'")
string(REPLACE "llvm-host" "llvm" LEAN_EXTRA_CXX_FLAGS ${LEAN_EXTRA_CXX_FLAGS})
message(VERBOSE "leanshared linker flags: '${LEANSHARED_LINKER_FLAGS}' | lean extra cxx flags '${LEAN_EXTR_CXX_FLAGS}'")
endif()
# We always strip away unused declarations to reduce binary sizes as the time cost is small and the
# potential benefit can be huge, especially when stripping `meta import`s.
# get rid of unused parts of C++ stdlib
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
string(APPEND LEANC_EXTRA_CC_FLAGS " -fdata-sections -ffunction-sections")
string(APPEND LEAN_EXTRA_LINKER_FLAGS " -Wl,-dead_strip")
string(APPEND TOOLCHAIN_SHARED_LINKER_FLAGS " -Wl,-dead_strip")
elseif(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
string(APPEND LEANC_EXTRA_CC_FLAGS " -fdata-sections -ffunction-sections")
string(APPEND LEAN_EXTRA_LINKER_FLAGS " -Wl,--gc-sections")
string(APPEND TOOLCHAIN_SHARED_LINKER_FLAGS " -Wl,--gc-sections")
endif()
if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
@@ -484,7 +469,6 @@ elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
string(APPEND CMAKE_CXX_FLAGS " -ftls-model=initial-exec")
string(APPEND INIT_SHARED_LINKER_FLAGS " -install_name @rpath/libInit_shared.dylib")
string(APPEND LEANSHARED_1_LINKER_FLAGS " -install_name @rpath/libleanshared_1.dylib")
string(APPEND LEANSHARED_2_LINKER_FLAGS " -install_name @rpath/libleanshared_2.dylib")
string(APPEND LEANSHARED_LINKER_FLAGS " -install_name @rpath/libleanshared.dylib")
string(APPEND LAKESHARED_LINKER_FLAGS " -Wl,-force_load,${CMAKE_BINARY_DIR}/lib/lean/libLake.a.export -install_name @rpath/libLake_shared.dylib")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,-rpath,@executable_path/../lib -Wl,-rpath,@executable_path/../lib/lean")
@@ -518,7 +502,7 @@ endif()
# are already loaded) and probably fail unless we set up LD_LIBRARY_PATH.
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
# import libraries created by the stdlib.make targets
string(APPEND LEANC_SHARED_LINKER_FLAGS " -lInit_shared -lleanshared_2 -lleanshared_1 -lleanshared")
string(APPEND LEANC_SHARED_LINKER_FLAGS " -lInit_shared -lleanshared_1 -lleanshared")
elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
# The second flag is necessary to even *load* dylibs without resolved symbols, as can happen
# if a Lake `extern_lib` depends on a symbols defined by the Lean library but is loaded even
@@ -605,7 +589,7 @@ endif()
add_subdirectory(initialize)
add_subdirectory(shell)
# to be included in `leanshared` but not the smaller `leanshared_*` (as it would pull
# to be included in `leanshared` but not the smaller `leanshared_1` (as it would pull
# in the world)
add_library(leaninitialize STATIC $<TARGET_OBJECTS:initialize>)
set_target_properties(leaninitialize PROPERTIES
@@ -638,9 +622,6 @@ if(${STAGE} GREATER 1)
COMMAND cmake -E copy_if_different "${PREV_STAGE}/lib/lean/libleanrt.a" "${CMAKE_BINARY_DIR}/lib/lean/libleanrt.a"
COMMAND cmake -E copy_if_different "${PREV_STAGE}/lib/lean/libleancpp.a" "${CMAKE_BINARY_DIR}/lib/lean/libleancpp.a"
COMMAND cmake -E copy_if_different "${PREV_STAGE}/lib/temp/libleancpp_1.a" "${CMAKE_BINARY_DIR}/lib/temp/libleancpp_1.a")
add_dependencies(leanrt_initial-exec copy-leancpp)
add_dependencies(leanrt copy-leancpp)
add_dependencies(leancpp_1 copy-leancpp)
add_dependencies(leancpp copy-leancpp)
if(LLVM)
add_custom_target(copy-lean-h-bc
@@ -705,7 +686,7 @@ endif()
set(STDLIBS Init Std Lean Leanc)
if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
list(APPEND STDLIBS Lake LeanChecker)
list(APPEND STDLIBS Lake)
endif()
add_custom_target(make_stdlib ALL
@@ -733,7 +714,6 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
)
add_custom_target(leanshared ALL
DEPENDS Init_shared leancpp
COMMAND touch ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libleanshared_2${CMAKE_SHARED_LIBRARY_SUFFIX}
COMMAND touch ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libleanshared_1${CMAKE_SHARED_LIBRARY_SUFFIX}
COMMAND touch ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libleanshared${CMAKE_SHARED_LIBRARY_SUFFIX}
)
@@ -754,7 +734,7 @@ else()
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make leanshared
VERBATIM)
string(APPEND CMAKE_EXE_LINKER_FLAGS " -lInit_shared -lleanshared_2 -lleanshared_1 -lleanshared")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -lInit_shared -lleanshared_1 -lleanshared")
endif()
if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
@@ -768,12 +748,6 @@ if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
DEPENDS lake_shared
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make lake
VERBATIM)
add_custom_target(leanchecker ALL
WORKING_DIRECTORY ${LEAN_SOURCE_DIR}
DEPENDS lake_shared
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make leanchecker
VERBATIM)
endif()
if(PREV_STAGE)
@@ -821,9 +795,6 @@ install(DIRECTORY "${CMAKE_BINARY_DIR}/lib/" DESTINATION lib
# symlink source into expected installation location for go-to-definition, if file system allows it
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/src)
# get rid of all files in `src/lean` that may have been loaded from the cache
# (at the time of writing this, this is the case for some lake test .c files)
file(REMOVE_RECURSE ${CMAKE_BINARY_DIR}/src/lean)
if(${STAGE} EQUAL 0)
file(CREATE_LINK ${CMAKE_SOURCE_DIR}/../../src ${CMAKE_BINARY_DIR}/src/lean RESULT _IGNORE_RES SYMBOLIC)
else()
@@ -850,13 +821,10 @@ if(LEAN_INSTALL_PREFIX)
set(CMAKE_INSTALL_PREFIX "${LEAN_INSTALL_PREFIX}/lean-${LEAN_VERSION_STRING}${LEAN_INSTALL_SUFFIX}")
endif()
if (USE_LAKE_CACHE AND STAGE EQUAL 1)
set(LAKE_ARTIFACT_CACHE_TOML "true")
else()
if (STAGE GREATER 1)
# The build of stage2+ may depend on local changes made to src/ that are not reflected by the
# commit hash in stage1/bin/lean, so we make sure to disable the global cache
set(LAKE_ARTIFACT_CACHE_TOML "false")
string(APPEND LEAN_EXTRA_LAKEFILE_TOML "\n\nenableArtifactCache = false")
endif()
# Escape for `make`. Yes, twice.
@@ -874,13 +842,15 @@ endfunction()
string(REPLACE "ROOT" "${CMAKE_BINARY_DIR}" LEANC_CC "${LEANC_CC}")
string(REPLACE "ROOT" "${CMAKE_BINARY_DIR}" LEANC_INTERNAL_FLAGS "${LEANC_INTERNAL_FLAGS}")
string(REPLACE "ROOT" "${CMAKE_BINARY_DIR}" LEANC_INTERNAL_LINKER_FLAGS "${LEANC_INTERNAL_LINKER_FLAGS}")
set(LEANC_OPTS_TOML "${LEANC_OPTS} ${LEANC_EXTRA_CC_FLAGS} ${LEANC_INTERNAL_FLAGS}")
set(LINK_OPTS_TOML "${LEANC_INTERNAL_LINKER_FLAGS} -L${CMAKE_BINARY_DIR}/lib/lean ${LEAN_EXTRA_LINKER_FLAGS}")
toml_escape("${LEAN_EXTRA_MAKE_OPTS}" LEAN_EXTRA_OPTS_TOML)
toml_escape("${LEANC_OPTS_TOML}" LEANC_OPTS_TOML)
toml_escape("${LINK_OPTS_TOML}" LINK_OPTS_TOML)
if(${CMAKE_BUILD_TYPE} MATCHES "Debug|Release|RelWithDebInfo|MinSizeRel")
set(CMAKE_BUILD_TYPE_TOML "${CMAKE_BUILD_TYPE}")
else()
set(CMAKE_BUILD_TYPE_TOML "Release")
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
set(LAKE_LIB_PREFIX "lib")
endif()
if(USE_LAKE)

View File

@@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Init.Prelude
public import Init.Notation
@@ -13,8 +14,8 @@ public import Init.ByCases
public import Init.RCases
public import Init.Core
public import Init.Control
public import Init.Data.Basic
public import Init.WF
public import Init.WFComputable
public import Init.WFTactics
public import Init.Data
public import Init.System
@@ -37,13 +38,9 @@ public import Init.Omega
public import Init.MacroTrace
public import Init.Grind
public import Init.GrindInstances
public import Init.Sym
public import Init.While
public import Init.Syntax
public import Init.Internal
public import Init.Try
public meta import Init.Try -- make sure `Try.Config` can be evaluated anywhere
public import Init.BinderNameHint
public import Init.Task
public import Init.MethodSpecsSimp
public import Init.LawfulBEqTactics

View File

@@ -7,6 +7,7 @@ Authors: Joachim Breitner
module
prelude
public import Init.Prelude
public import Init.Tactics
public section

View File

@@ -44,10 +44,3 @@ theorem apply_ite (f : α → β) (P : Prop) [Decidable P] (x y : α) :
/-- A `dite` whose results do not actually depend on the condition may be reduced to an `ite`. -/
@[simp] theorem dite_eq_ite [Decidable P] :
(dite P (fun _ => a) (fun _ => b)) = ite P a b := rfl
-- Remark: dite and ite are "defally equal" when we ignore the proofs.
@[deprecated dite_eq_ite (since := "2025-10-29")]
theorem dif_eq_if (c : Prop) {h : Decidable c} {α : Sort u} (t : α) (e : α) : dite c (fun _ => t) (fun _ => e) = ite c t e :=
match h with
| isTrue _ => rfl
| isFalse _ => rfl

View File

@@ -8,7 +8,7 @@ module
prelude
public import Init.PropLemmas
@[expose] public section
public section
universe u v
@@ -102,7 +102,7 @@ noncomputable def strongIndefiniteDescription {α : Sort u} (p : α → Prop) (h
xp.val, fun _ => xp.property)
(fun hp => choice h, fun h => absurd h hp)
/-- The Hilbert epsilon function. -/
/-- the Hilbert epsilon Function -/
noncomputable def epsilon {α : Sort u} [h : Nonempty α] (p : α Prop) : α :=
(strongIndefiniteDescription p h).val
@@ -181,6 +181,9 @@ theorem not_imp_iff_and_not : ¬(a → b) ↔ a ∧ ¬b := Decidable.not_imp_iff
theorem not_and_iff_not_or_not : ¬(a b) ¬a ¬b := Decidable.not_and_iff_not_or_not
@[deprecated not_and_iff_not_or_not (since := "2025-03-18")]
abbrev not_and_iff_or_not_not := @not_and_iff_not_or_not
theorem not_iff : ¬(a b) (¬a b) := Decidable.not_iff
@[simp] theorem imp_iff_left_iff : (b a b) a b := Decidable.imp_iff_left_iff
@@ -205,5 +208,3 @@ export Classical (imp_iff_right_iff imp_and_neg_imp_iff and_or_imp not_imp)
/-- Show that an element extracted from `P : ∃ a, p a` using `P.choose` satisfies `p`. -/
theorem Exists.choose_spec {p : α Prop} (P : a, p a) : p P.choose := Classical.choose_spec P
grind_pattern Exists.choose_spec => P.choose

View File

@@ -116,7 +116,7 @@ On top of these instances this file defines several auxiliary type classes:
* `CoeOTC := CoeOut* Coe*`
* `CoeHTC := CoeHead? CoeOut* Coe*`
* `CoeHTCT := CoeHead? CoeOut* Coe* CoeTail?`
* `CoeT := CoeHead? CoeOut* Coe* CoeTail? | CoeDep`
* `CoeDep := CoeHead? CoeOut* Coe* CoeTail? | CoeDep`
-/

View File

@@ -16,4 +16,5 @@ public import Init.Control.Option
public import Init.Control.Lawful
public import Init.Control.StateCps
public import Init.Control.ExceptCps
public import Init.Control.MonadAttach
public section

View File

@@ -25,7 +25,7 @@ instances are provided for the same type.
instance (priority := 500) instForInOfForIn' [ForIn' m ρ α d] : ForIn m ρ α where
forIn x b f := forIn' x b fun a _ => f a
@[simp] theorem forIn'_eq_forIn [d : Membership α ρ] [ForIn' m ρ α d] {β} (x : ρ) (b : β)
@[simp] theorem forIn'_eq_forIn [d : Membership α ρ] [ForIn' m ρ α d] {β} [Monad m] (x : ρ) (b : β)
(f : (a : α) a x β m (ForInStep β)) (g : (a : α) β m (ForInStep β))
(h : a m b, f a m b = g a b) :
forIn' x b f = forIn x b g := by
@@ -40,11 +40,14 @@ instance (priority := 500) instForInOfForIn' [ForIn' m ρ α d] : ForIn m ρ α
simp [h]
rfl
@[wf_preprocess] theorem forIn_eq_forIn' [d : Membership α ρ] [ForIn' m ρ α d] {β}
@[wf_preprocess] theorem forIn_eq_forIn' [d : Membership α ρ] [ForIn' m ρ α d] {β} [Monad m]
(x : ρ) (b : β) (f : (a : α) β m (ForInStep β)) :
forIn x b f = forIn' x b (fun x h => binderNameHint x f <| binderNameHint h () <| f x) := by
rfl
@[deprecated forIn_eq_forIn' (since := "2025-04-04")]
abbrev forIn_eq_forin' := @forIn_eq_forIn'
/--
Extracts the value from a `ForInStep`, ignoring whether it is `ForInStep.done` or `ForInStep.yield`.
-/
@@ -144,7 +147,7 @@ instance : ToBool Bool where
Converts the result of the monadic action `x` to a `Bool`. If it is `true`, returns it and ignores
`y`; otherwise, runs `y` and returns its result.
This is a monadic counterpart to the short-circuiting `||` operator, usually accessed via the `<||>`
This a monadic counterpart to the short-circuiting `||` operator, usually accessed via the `<||>`
operator.
-/
@[macro_inline] def orM {m : Type u Type v} {β : Type u} [Monad m] [ToBool β] (x y : m β) : m β := do
@@ -161,7 +164,7 @@ recommended_spelling "orM" for "<||>" in [orM, «term_<||>_»]
Converts the result of the monadic action `x` to a `Bool`. If it is `true`, returns `y`; otherwise,
returns the original result of `x`.
This is a monadic counterpart to the short-circuiting `&&` operator, usually accessed via the `<&&>`
This a monadic counterpart to the short-circuiting `&&` operator, usually accessed via the `<&&>`
operator.
-/
@[macro_inline] def andM {m : Type u Type v} {β : Type u} [Monad m] [ToBool β] (x y : m β) : m β := do
@@ -403,7 +406,7 @@ class ForM (m : Type u → Type v) (γ : Type w₁) (α : outParam (Type w₂))
/--
Runs the monadic action `f` on each element of the collection `coll`.
-/
forM (coll : γ) (f : α m PUnit) : m PUnit
forM [Monad m] (coll : γ) (f : α m PUnit) : m PUnit
export ForM (forM)

View File

@@ -6,6 +6,8 @@ Authors: Leonardo de Moura
module
prelude
public import Init.Control.State
public import Init.Control.Except
public import Init.Data.ToString.Basic
public section
@@ -17,20 +19,14 @@ variable {ε σ α : Type u}
instance [ToString ε] [ToString α] : ToString (Result ε σ α) where
toString
| Result.ok a _ => String.Internal.append "ok: " (toString a)
| Result.error e _ => String.Internal.append "error: " (toString e)
| Result.ok a _ => "ok: " ++ toString a
| Result.error e _ => "error: " ++ toString e
instance [Repr ε] [Repr α] : Repr (Result ε σ α) where
reprPrec
| Result.error e _, prec => Repr.addAppParen ("EStateM.Result.error " ++ reprArg e) prec
| Result.ok a _, prec => Repr.addAppParen ("EStateM.Result.ok " ++ reprArg a) prec
instance : MonadAttach (EStateM ε σ) where
CanReturn x a := Exists fun s => Exists fun s' => x.run s = .ok a s'
attach x s := match h : x s with
| .ok a s' => .ok a, s, s', h s'
| .error e s' => .error e s'
end EStateM
namespace EStateM

View File

@@ -10,6 +10,7 @@ module
prelude
public import Init.Control.Basic
public import Init.Control.Id
public import Init.Coe
@[expose] public section
@@ -148,23 +149,6 @@ This is the inverse of `ExceptT.mk`.
@[always_inline, inline, expose]
def ExceptT.run {ε : Type u} {m : Type u Type v} {α : Type u} (x : ExceptT ε m α) : m (Except ε α) := x
/--
Use a monadic action that may throw an exception by providing explicit success and failure
continuations.
-/
@[always_inline, inline, expose]
def ExceptT.runK [Monad m] (x : ExceptT ε m α) (ok : α m β) (error : ε m β) : m β :=
x.run >>= (·.casesOn error ok)
/--
Returns the value of a computation, forgetting whether it was an exception or a success.
This corresponds to early return.
-/
@[always_inline, inline, expose]
def ExceptT.runCatch [Monad m] (x : ExceptT α m α) : m α :=
x.runK pure pure
namespace ExceptT
variable {ε : Type u} {m : Type u Type v} [Monad m]
@@ -329,8 +313,3 @@ instance ExceptT.finally {m : Type u → Type v} {ε : Type u} [MonadFinally m]
| (.ok a, .ok b) => pure (.ok (a, b))
| (_, .error e) => pure (.error e) -- second error has precedence
| (.error e, _) => pure (.error e)
instance [Monad m] [MonadAttach m] : MonadAttach (ExceptT ε m) where
CanReturn x a := MonadAttach.CanReturn (m := m) x (.ok a)
attach x := show m (Except ε _) from
(fun a, h => match a with | .ok a => .ok a, h | .error e => .error e) <$> MonadAttach.attach (m := m) x

View File

@@ -75,13 +75,6 @@ instance [Monad m] : MonadLift m (ExceptCpsT σ m) where
instance [Inhabited ε] : Inhabited (ExceptCpsT ε m α) where
default := fun _ _ k₂ => k₂ default
/--
For continuation monads, it is not possible to provide a computable `MonadAttach` instance that
actually adds information about the return value. Therefore, this instance always attaches a proof
of `True`.
-/
instance : MonadAttach (ExceptCpsT ε m) := .trivial
@[simp] theorem run_pure [Monad m] : run (pure x : ExceptCpsT ε m α) = pure (Except.ok x) := rfl
@[simp] theorem run_lift {α ε : Type u} [Monad m] (x : m α) : run (ExceptCpsT.lift x : ExceptCpsT ε m α) = (x >>= fun a => pure (Except.ok a) : m (Except ε α)) := rfl

View File

@@ -9,7 +9,6 @@ module
prelude
public import Init.Core
public import Init.Control.MonadAttach
public section
@@ -68,15 +67,4 @@ instance [OfNat α n] : OfNat (Id α) n :=
instance {m : Type u Type v} [Pure m] : MonadLiftT Id m where
monadLift x := pure x.run
instance : MonadAttach Id where
CanReturn x a := x.run = a
attach x := pure x.run, rfl
instance : LawfulMonadAttach Id where
map_attach := rfl
canReturn_map_imp := by
intro _ _ x _ h
cases h
exact x.run.2
end Id

View File

@@ -10,4 +10,5 @@ public import Init.Control.Lawful.Basic
public import Init.Control.Lawful.Instances
public import Init.Control.Lawful.Lemmas
public import Init.Control.Lawful.MonadLift
public import Init.Control.Lawful.MonadAttach
public section

View File

@@ -7,6 +7,8 @@ module
prelude
public import Init.Ext
public import Init.SimpLemmas
public import Init.Meta
public section
@@ -145,7 +147,7 @@ class LawfulMonad (m : Type u → Type v) [Monad m] : Prop extends LawfulApplica
export LawfulMonad (bind_pure_comp bind_map pure_bind bind_assoc)
attribute [simp] pure_bind bind_assoc bind_pure_comp
attribute [grind <=] pure_bind
attribute [grind] pure_bind
@[simp] theorem bind_pure [Monad m] [LawfulMonad m] (x : m α) : x >>= pure = x := by
change x >>= (fun a => pure (id a)) = x
@@ -170,7 +172,6 @@ theorem bind_pure_unit [Monad m] [LawfulMonad m] {x : m PUnit} : (x >>= fun _ =>
theorem map_congr [Functor m] {x : m α} {f g : α β} (h : a, f a = g a) : (f <$> x : m β) = g <$> x := by
simp [funext h]
@[deprecated seq_eq_bind_map (since := "2025-10-26")]
theorem seq_eq_bind {α β : Type u} [Monad m] [LawfulMonad m] (mf : m (α β)) (x : m α) : mf <*> x = mf >>= fun f => f <$> x := by
rw [bind_map]
@@ -248,12 +249,27 @@ namespace Id
instance : LawfulMonad Id := by
refine LawfulMonad.mk' _ ?_ ?_ ?_ <;> intros <;> rfl
@[simp, grind =] theorem run_map (x : Id α) (f : α β) : (f <$> x).run = f x.run := rfl
@[simp, grind =] theorem run_bind (x : Id α) (f : α Id β) : (x >>= f).run = (f x.run).run := rfl
@[simp, grind =] theorem run_pure (a : α) : (pure a : Id α).run = a := rfl
@[simp, grind =] theorem pure_run (a : Id α) : pure a.run = a := rfl
@[simp] theorem run_map (x : Id α) (f : α β) : (f <$> x).run = f x.run := rfl
@[simp] theorem run_bind (x : Id α) (f : α Id β) : (x >>= f).run = (f x.run).run := rfl
@[simp] theorem run_pure (a : α) : (pure a : Id α).run = a := rfl
@[simp] theorem run_seqRight (x y : Id α) : (x *> y).run = y.run := rfl
@[simp] theorem run_seqLeft (x y : Id α) : (x <* y).run = x.run := rfl
@[simp] theorem run_seq (f : Id (α β)) (x : Id α) : (f <*> x).run = f.run x.run := rfl
-- These lemmas are bad as they abuse the defeq of `Id α` and `α`
@[deprecated run_map (since := "2025-03-05")] theorem map_eq (x : Id α) (f : α β) : f <$> x = f x := rfl
@[deprecated run_bind (since := "2025-03-05")] theorem bind_eq (x : Id α) (f : α id β) : x >>= f = f x := rfl
@[deprecated run_pure (since := "2025-03-05")] theorem pure_eq (a : α) : (pure a : Id α) = a := rfl
end Id
/-! # Option -/
instance : LawfulMonad Option := LawfulMonad.mk'
(id_map := fun x => by cases x <;> rfl)
(pure_bind := fun _ _ => rfl)
(bind_assoc := fun x _ _ => by cases x <;> rfl)
(bind_pure_comp := fun _ x => by cases x <;> rfl)
instance : LawfulApplicative Option := inferInstance
instance : LawfulFunctor Option := inferInstance

View File

@@ -7,43 +7,38 @@ module
prelude
public import Init.Control.Lawful.Basic
public import Init.Control.Except
import all Init.Control.Except
public import Init.Control.Option
import all Init.Control.Option
public import Init.Control.State
import all Init.Control.State
public import Init.Control.StateRef
public import Init.Ext
public section
open Function
@[simp, grind =] theorem monadMap_refl {m : Type _ Type _} {α} (f : {α}, m α m α) :
monadMap @f = @f α := rfl
/-! # ExceptT -/
namespace ExceptT
@[ext, grind ext] theorem ext {x y : ExceptT ε m α} (h : x.run = y.run) : x = y := by
@[ext] theorem ext {x y : ExceptT ε m α} (h : x.run = y.run) : x = y := by
simp [run] at h
assumption
@[simp, grind =] theorem run_mk (x : m (Except ε α)) : run (mk x : ExceptT ε m α) = x := rfl
@[simp] theorem run_pure [Monad m] (x : α) : run (pure x : ExceptT ε m α) = pure (Except.ok x) := rfl
@[simp, grind =] theorem run_pure [Monad m] (x : α) : run (pure x : ExceptT ε m α) = pure (Except.ok x) := rfl
@[simp] theorem run_lift [Monad.{u, v} m] (x : m α) : run (ExceptT.lift x : ExceptT ε m α) = (Except.ok <$> x : m (Except ε α)) := rfl
@[simp, grind =] theorem run_lift [Monad.{u, v} m] (x : m α) : run (ExceptT.lift x : ExceptT ε m α) = (Except.ok <$> x : m (Except ε α)) := rfl
@[simp] theorem run_throw [Monad m] : run (throw e : ExceptT ε m β) = pure (Except.error e) := rfl
@[simp, grind =] theorem run_throw [Monad m] : run (throw e : ExceptT ε m β) = pure (Except.error e) := rfl
@[simp, grind =] theorem run_bind_lift [Monad m] [LawfulMonad m] (x : m α) (f : α ExceptT ε m β) : run (ExceptT.lift x >>= f : ExceptT ε m β) = x >>= fun a => run (f a) := by
@[simp] theorem run_bind_lift [Monad m] [LawfulMonad m] (x : m α) (f : α ExceptT ε m β) : run (ExceptT.lift x >>= f : ExceptT ε m β) = x >>= fun a => run (f a) := by
simp [ExceptT.run, ExceptT.lift, bind, ExceptT.bind, ExceptT.mk, ExceptT.bindCont]
@[simp, grind =] theorem bind_throw [Monad m] [LawfulMonad m] (f : α ExceptT ε m β) : (throw e >>= f) = throw e := by
@[simp] theorem bind_throw [Monad m] [LawfulMonad m] (f : α ExceptT ε m β) : (throw e >>= f) = throw e := by
simp [throw, throwThe, MonadExceptOf.throw, bind, ExceptT.bind, ExceptT.bindCont, ExceptT.mk]
@[grind =]
theorem run_bind [Monad m] (x : ExceptT ε m α) (f : α ExceptT ε m β)
theorem run_bind [Monad m] (x : ExceptT ε m α)
: run (x >>= f : ExceptT ε m β)
=
run x >>= fun
@@ -51,18 +46,15 @@ theorem run_bind [Monad m] (x : ExceptT ε m α) (f : α → ExceptT ε m β)
| Except.error e => pure (Except.error e) :=
rfl
@[simp, grind =] theorem lift_pure [Monad m] [LawfulMonad m] (a : α) : ExceptT.lift (pure a) = (pure a : ExceptT ε m α) := by
@[simp] theorem lift_pure [Monad m] [LawfulMonad m] (a : α) : ExceptT.lift (pure a) = (pure a : ExceptT ε m α) := by
simp [ExceptT.lift, pure, ExceptT.pure]
@[simp, grind =] theorem run_map [Monad m] [LawfulMonad m] (f : α β) (x : ExceptT ε m α)
@[simp] theorem run_map [Monad m] [LawfulMonad m] (f : α β) (x : ExceptT ε m α)
: (f <$> x).run = Except.map f <$> x.run := by
simp [Functor.map, ExceptT.map, bind_pure_comp]
apply bind_congr
intro a; cases a <;> simp [Except.map]
@[simp, grind =] theorem run_monadMap [MonadFunctorT n m] (f : {β : Type u} n β n β) (x : ExceptT ε m α)
: (monadMap @f x : ExceptT ε m α).run = monadMap @f (x.run) := rfl
protected theorem seq_eq {α β ε : Type u} [Monad m] (mf : ExceptT ε m (α β)) (x : ExceptT ε m α) : mf <*> x = mf >>= fun f => f <$> x :=
rfl
@@ -105,22 +97,6 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (ExceptT ε m) where
simp only [ExceptT.instMonad, ExceptT.map, ExceptT.mk, throw, throwThe, MonadExceptOf.throw,
pure_bind]
/-! Note that the `MonadControl` instance for `ExceptT` is not monad-generic. -/
@[simp] theorem run_restoreM [Monad m] (x : stM m (ExceptT ε m) α) :
ExceptT.run (restoreM x) = pure x := rfl
@[simp] theorem run_liftWith [Monad m] (f : ({β : Type u} ExceptT ε m β m (stM m (ExceptT ε m) β)) m α) :
ExceptT.run (liftWith f) = Except.ok <$> (f fun x => x.run) :=
rfl
@[simp] theorem run_controlAt [Monad m] [LawfulMonad m] (f : ({β : Type u} ExceptT ε m β m (stM m (ExceptT ε m) β)) m (stM m (ExceptT ε m) α)) :
ExceptT.run (controlAt m f) = f fun x => x.run := by
simp [controlAt, run_bind, bind_map_left]
@[simp] theorem run_control [Monad m] [LawfulMonad m] (f : ({β : Type u} ExceptT ε m β m (stM m (ExceptT ε m) β)) m (stM m (ExceptT ε m) α)) :
ExceptT.run (control f) = f fun x => x.run := run_controlAt f
end ExceptT
/-! # Except -/
@@ -133,171 +109,32 @@ instance : LawfulMonad (Except ε) := LawfulMonad.mk'
instance : LawfulApplicative (Except ε) := inferInstance
instance : LawfulFunctor (Except ε) := inferInstance
/-! # OptionT -/
namespace OptionT
@[ext] theorem ext {x y : OptionT m α} (h : x.run = y.run) : x = y := by
simp [run] at h
assumption
@[simp, grind =] theorem run_mk {m : Type u Type v} (x : m (Option α)) :
OptionT.run (OptionT.mk x) = x := by rfl
@[simp, grind =] theorem run_pure [Monad m] (x : α) : run (pure x : OptionT m α) = pure (some x) := by
simp [run, pure, OptionT.pure, OptionT.mk]
@[simp, grind =] theorem run_lift [Monad.{u, v} m] (x : m α) : run (OptionT.lift x : OptionT m α) = (return some ( x) : m (Option α)) := by
simp [run, OptionT.lift, OptionT.mk]
@[simp, grind =] theorem run_throw [Monad m] : run (throw e : OptionT m β) = pure none := by
simp [run, throw, throwThe, MonadExceptOf.throw, OptionT.fail, OptionT.mk]
@[simp, grind =] theorem run_bind_lift [Monad m] [LawfulMonad m] (x : m α) (f : α OptionT m β) : run (OptionT.lift x >>= f : OptionT m β) = x >>= fun a => run (f a) := by
simp [OptionT.run, OptionT.lift, bind, OptionT.bind, OptionT.mk]
@[simp, grind =] theorem bind_throw [Monad m] [LawfulMonad m] (f : α OptionT m β) : (throw e >>= f) = throw e := by
simp [throw, throwThe, MonadExceptOf.throw, bind, OptionT.bind, OptionT.mk, OptionT.fail]
@[simp, grind =] theorem run_bind (f : α OptionT m β) [Monad m] :
(x >>= f).run = Option.elimM x.run (pure none) (fun x => (f x).run) := by
change x.run >>= _ = _
simp [Option.elimM]
exact bind_congr fun |some _ => rfl | none => rfl
@[simp, grind =] theorem lift_pure [Monad m] [LawfulMonad m] {α : Type u} (a : α) : OptionT.lift (pure a : m α) = pure a := by
simp only [OptionT.lift, OptionT.mk, bind_pure_comp, map_pure, pure, OptionT.pure]
@[simp, grind =] theorem run_map [Monad m] [LawfulMonad m] (f : α β) (x : OptionT m α)
: (f <$> x).run = Option.map f <$> x.run := by
simp [Functor.map, Option.map, bind_pure_comp]
apply bind_congr
intro a; cases a <;> simp [OptionT.pure, OptionT.mk]
@[simp, grind =] theorem run_monadMap [MonadFunctorT n m] (f : {β : Type u} n β n β) (x : OptionT m α)
: (monadMap @f x : OptionT m α).run = monadMap @f (x.run) := rfl
protected theorem seq_eq {α β : Type u} [Monad m] (mf : OptionT m (α β)) (x : OptionT m α) : mf <*> x = mf >>= fun f => f <$> x :=
rfl
protected theorem bind_pure_comp [Monad m] (f : α β) (x : OptionT m α) : x >>= pure f = f <$> x := by
intros; rfl
protected theorem seqLeft_eq {α β : Type u} {m : Type u Type v} [Monad m] [LawfulMonad m] (x : OptionT m α) (y : OptionT m β) : x <* y = const β <$> x <*> y := by
change (x >>= fun a => y >>= fun _ => pure a) = (const (α := α) β <$> x) >>= fun f => f <$> y
rw [ OptionT.bind_pure_comp]
apply ext
simp [Option.elimM, Option.elim]
apply bind_congr
intro
| none => simp
| some _ =>
simp [bind_pure_comp]; apply bind_congr; intro b;
cases b <;> simp [const]
protected theorem seqRight_eq [Monad m] [LawfulMonad m] (x : OptionT m α) (y : OptionT m β) : x *> y = const α id <$> x <*> y := by
change (x >>= fun _ => y) = (const α id <$> x) >>= fun f => f <$> y
rw [ OptionT.bind_pure_comp]
apply ext
simp [Option.elimM, Option.elim]
apply bind_congr
intro a; cases a <;> simp
instance [Monad m] [LawfulMonad m] : LawfulMonad (OptionT m) where
id_map := by intros; apply ext; simp
map_const := by intros; rfl
seqLeft_eq := OptionT.seqLeft_eq
seqRight_eq := OptionT.seqRight_eq
pure_seq := by intros; apply ext; simp [OptionT.seq_eq, Option.elimM, Option.elim]
bind_pure_comp := OptionT.bind_pure_comp
bind_map := by intros; rfl
pure_bind := by intros; apply ext; simp [Option.elimM, Option.elim]
bind_assoc := by intros; apply ext; simp [Option.elimM, Option.elim]; apply bind_congr; intro a; cases a <;> simp
@[simp] theorem run_seq [Monad m] [LawfulMonad m] (f : OptionT m (α β)) (x : OptionT m α) :
(f <*> x).run = Option.elimM f.run (pure none) (fun f => Option.map f <$> x.run) := by
simp [seq_eq_bind_map, Option.elimM, Option.elim]
@[simp] theorem run_seqLeft [Monad m] [LawfulMonad m] (x : OptionT m α) (y : OptionT m β) :
(x <* y).run = Option.elimM x.run (pure none)
(fun x => Option.map (Function.const β x) <$> y.run) := by
simp [seqLeft_eq, seq_eq_bind_map, Option.elimM, OptionT.run_bind]
@[simp] theorem run_seqRight [Monad m] [LawfulMonad m] (x : OptionT m α) (y : OptionT m β) :
(x *> y).run = Option.elimM x.run (pure none) (Function.const α y.run) := by
simp only [seqRight_eq, run_seq, Option.elimM, run_map, Option.elim, bind_map_left]
refine bind_congr (fun | some _ => by simp | none => by simp)
@[simp, grind =] theorem run_failure [Monad m] : (failure : OptionT m α).run = pure none := by rfl
@[simp] theorem map_failure [Monad m] [LawfulMonad m] {α β : Type _} (f : α β) :
f <$> (failure : OptionT m α) = (failure : OptionT m β) := by
simp [OptionT.mk, Functor.map, Alternative.failure, OptionT.fail, OptionT.bind]
@[simp] theorem run_orElse [Monad m] (x : OptionT m α) (y : OptionT m α) :
(x <|> y).run = Option.elimM x.run y.run (fun x => pure (some x)) :=
bind_congr fun | some _ => by rfl | none => by rfl
/-! Note that the `MonadControl` instance for `OptionT` is not monad-generic. -/
@[simp] theorem run_restoreM [Monad m] (x : stM m (OptionT m) α) :
OptionT.run (restoreM x) = pure x := rfl
@[simp] theorem run_liftWith [Monad m] [LawfulMonad m] (f : ({β : Type u} OptionT m β m (stM m (OptionT m) β)) m α) :
OptionT.run (liftWith f) = Option.some <$> (f fun x => x.run) := by
dsimp [liftWith]
rw [ bind_pure_comp]
rfl
@[simp] theorem run_controlAt [Monad m] [LawfulMonad m] (f : ({β : Type u} OptionT m β m (stM m (OptionT m) β)) m (stM m (OptionT m) α)) :
OptionT.run (controlAt m f) = f fun x => x.run := by
simp [controlAt, Option.elimM, Option.elim]
@[simp] theorem run_control [Monad m] [LawfulMonad m] (f : ({β : Type u} OptionT m β m (stM m (OptionT m) β)) m (stM m (OptionT m) α)) :
OptionT.run (control f) = f fun x => x.run := run_controlAt f
end OptionT
/-! # Option -/
instance : LawfulMonad Option := LawfulMonad.mk'
(id_map := fun x => by cases x <;> rfl)
(pure_bind := fun _ _ => by rfl)
(bind_assoc := fun a _ _ => by cases a <;> rfl)
(bind_pure_comp := fun _ x => by cases x <;> rfl)
instance : LawfulApplicative Option := inferInstance
instance : LawfulFunctor Option := inferInstance
/-! # ReaderT -/
namespace ReaderT
@[ext, grind ext] theorem ext {x y : ReaderT ρ m α} (h : ctx, x.run ctx = y.run ctx) : x = y := by
@[ext] theorem ext {x y : ReaderT ρ m α} (h : ctx, x.run ctx = y.run ctx) : x = y := by
simp [run] at h
exact funext h
@[simp, grind =] theorem run_mk (x : ρ m α) (ctx : ρ) : run (.mk x : ReaderT ρ m α) ctx = x ctx :=
rfl
@[simp] theorem run_pure [Monad m] (a : α) (ctx : ρ) : (pure a : ReaderT ρ m α).run ctx = pure a := rfl
@[simp, grind =] theorem run_pure [Monad m] (a : α) (ctx : ρ) : (pure a : ReaderT ρ m α).run ctx = pure a := rfl
@[simp, grind =] theorem run_bind [Monad m] (x : ReaderT ρ m α) (f : α ReaderT ρ m β) (ctx : ρ)
@[simp] theorem run_bind [Monad m] (x : ReaderT ρ m α) (f : α ReaderT ρ m β) (ctx : ρ)
: (x >>= f).run ctx = x.run ctx >>= λ a => (f a).run ctx := rfl
@[simp, grind =] theorem run_mapConst [Monad m] (a : α) (x : ReaderT ρ m β) (ctx : ρ)
@[simp] theorem run_mapConst [Monad m] (a : α) (x : ReaderT ρ m β) (ctx : ρ)
: (Functor.mapConst a x).run ctx = Functor.mapConst a (x.run ctx) := rfl
@[simp, grind =] theorem run_map [Monad m] (f : α β) (x : ReaderT ρ m α) (ctx : ρ)
@[simp] theorem run_map [Monad m] (f : α β) (x : ReaderT ρ m α) (ctx : ρ)
: (f <$> x).run ctx = f <$> x.run ctx := rfl
@[simp, grind =] theorem run_monadLift [MonadLiftT n m] (x : n α) (ctx : ρ)
@[simp] theorem run_monadLift [MonadLiftT n m] (x : n α) (ctx : ρ)
: (monadLift x : ReaderT ρ m α).run ctx = (monadLift x : m α) := rfl
@[simp, grind =] theorem run_monadMap [MonadFunctorT n m] (f : {β : Type u} n β n β) (x : ReaderT ρ m α) (ctx : ρ)
@[simp] theorem run_monadMap [MonadFunctorT n m] (f : {β : Type u} n β n β) (x : ReaderT ρ m α) (ctx : ρ)
: (monadMap @f x : ReaderT ρ m α).run ctx = monadMap @f (x.run ctx) := rfl
@[simp, grind =] theorem run_read [Monad m] (ctx : ρ) : (ReaderT.read : ReaderT ρ m ρ).run ctx = pure ctx := rfl
@[simp] theorem run_read [Monad m] (ctx : ρ) : (ReaderT.read : ReaderT ρ m ρ).run ctx = pure ctx := rfl
@[simp] theorem run_seq {α β : Type u} [Monad m] (f : ReaderT ρ m (α β)) (x : ReaderT ρ m α) (ctx : ρ)
: (f <*> x).run ctx = (f.run ctx <*> x.run ctx) := rfl
@@ -327,22 +164,6 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (ReaderT ρ m) where
pure_bind := by intros; apply ext; intros; simp
bind_assoc := by intros; apply ext; intros; simp
/-! Note that the `MonadControl` instance for `ReaderT` is not monad-generic. -/
@[simp] theorem run_restoreM [Monad m] (x : stM m (ReaderT ρ m) α) (ctx : ρ) :
ReaderT.run (restoreM x) ctx = pure x := rfl
@[simp] theorem run_liftWith [Monad m] (f : ({β : Type u} ReaderT ρ m β m (stM m (ReaderT ρ m) β)) m α) (ctx : ρ) :
ReaderT.run (liftWith f) ctx = (f fun x => x.run ctx) :=
rfl
@[simp] theorem run_controlAt [Monad m] [LawfulMonad m] (f : ({β : Type u} ReaderT ρ m β m (stM m (ReaderT ρ m) β)) m (stM m (ReaderT ρ m) α)) (ctx : ρ) :
ReaderT.run (controlAt m f) ctx = f fun x => x.run ctx := by
simp [controlAt]
@[simp] theorem run_control [Monad m] [LawfulMonad m] (f : ({β : Type u} ReaderT ρ m β m (stM m (ReaderT ρ m) β)) m (stM m (ReaderT ρ m) α)) (ctx : ρ) :
ReaderT.run (control f) ctx = f fun x => x.run ctx := run_controlAt f ctx
end ReaderT
/-! # StateRefT -/
@@ -354,42 +175,38 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (StateRefT' ω σ m) :=
namespace StateT
@[ext, grind ext] theorem ext {x y : StateT σ m α} (h : s, x.run s = y.run s) : x = y :=
@[ext] theorem ext {x y : StateT σ m α} (h : s, x.run s = y.run s) : x = y :=
funext h
@[simp, grind =] theorem run_mk [Monad m] (x : σ m (α × σ)) (s : σ) : run (.mk x) s = x s :=
@[simp] theorem run'_eq [Monad m] (x : StateT σ m α) (s : σ) : run' x s = (·.1) <$> run x s :=
rfl
@[simp, grind =] theorem run'_eq [Monad m] (x : StateT σ m α) (s : σ) : run' x s = (·.1) <$> run x s :=
rfl
@[simp] theorem run_pure [Monad m] (a : α) (s : σ) : (pure a : StateT σ m α).run s = pure (a, s) := rfl
@[simp, grind =] theorem run_pure [Monad m] (a : α) (s : σ) : (pure a : StateT σ m α).run s = pure (a, s) := rfl
@[simp] theorem run_bind [Monad m] (x : StateT σ m α) (f : α StateT σ m β) (s : σ)
: (x >>= f).run s = x.run s >>= λ p => (f p.1).run p.2 := by
simp [bind, StateT.bind, run]
@[simp, grind =] theorem run_bind [Monad m] (x : StateT σ m α) (f : α StateT σ m β) (s : σ)
: (x >>= f).run s = x.run s >>= λ p => (f p.1).run p.2 := rfl
@[simp] theorem run_map {α β σ : Type u} [Monad m] [LawfulMonad m] (f : α β) (x : StateT σ m α) (s : σ) : (f <$> x).run s = (fun (p : α × σ) => (f p.1, p.2)) <$> x.run s := by
simp [Functor.map, StateT.map, run, bind_pure_comp]
@[simp, grind =] theorem run_map {α β σ : Type u} [Monad m] [LawfulMonad m] (f : α β) (x : StateT σ m α) (s : σ) : (f <$> x).run s = (fun (p : α × σ) => (f p.1, p.2)) <$> x.run s := by
rw [ bind_pure_comp (m := m)]
rfl
@[simp] theorem run_get [Monad m] (s : σ) : (get : StateT σ m σ).run s = pure (s, s) := rfl
@[simp, grind =] theorem run_get [Monad m] (s : σ) : (get : StateT σ m σ).run s = pure (s, s) := rfl
@[simp] theorem run_set [Monad m] (s s' : σ) : (set s' : StateT σ m PUnit).run s = pure (, s') := rfl
@[simp, grind =] theorem run_set [Monad m] (s s' : σ) : (set s' : StateT σ m PUnit).run s = pure (, s') := rfl
@[simp] theorem run_modify [Monad m] (f : σ σ) (s : σ) : (modify f : StateT σ m PUnit).run s = pure (, f s) := rfl
@[simp, grind =] theorem run_modify [Monad m] (f : σ σ) (s : σ) : (modify f : StateT σ m PUnit).run s = pure (, f s) := rfl
@[simp] theorem run_modifyGet [Monad m] (f : σ α × σ) (s : σ) : (modifyGet f : StateT σ m α).run s = pure ((f s).1, (f s).2) := by
simp [modifyGet, MonadStateOf.modifyGet, StateT.modifyGet, run]
@[simp, grind =] theorem run_modifyGet [Monad m] (f : σ α × σ) (s : σ) : (modifyGet f : StateT σ m α).run s = pure ((f s).1, (f s).2) := by
rfl
@[simp] theorem run_lift {α σ : Type u} [Monad m] (x : m α) (s : σ) : (StateT.lift x : StateT σ m α).run s = x >>= fun a => pure (a, s) := rfl
@[simp, grind =] theorem run_lift {α σ : Type u} [Monad m] (x : m α) (s : σ) : (StateT.lift x : StateT σ m α).run s = x >>= fun a => pure (a, s) := rfl
@[grind =]
theorem run_bind_lift {α σ : Type u} [Monad m] [LawfulMonad m] (x : m α) (f : α StateT σ m β) (s : σ) : (StateT.lift x >>= f).run s = x >>= fun a => (f a).run s := by
simp
simp [StateT.lift, StateT.run, bind, StateT.bind]
@[simp, grind =] theorem run_monadLift {α σ : Type u} [Monad m] [MonadLiftT n m] (x : n α) (s : σ) : (monadLift x : StateT σ m α).run s = (monadLift x : m α) >>= fun a => pure (a, s) := rfl
@[simp] theorem run_monadLift {α σ : Type u} [Monad m] [MonadLiftT n m] (x : n α) (s : σ) : (monadLift x : StateT σ m α).run s = (monadLift x : m α) >>= fun a => pure (a, s) := rfl
@[simp, grind =] theorem run_monadMap [MonadFunctorT n m] (f : {β : Type u} n β n β) (x : StateT σ m α) (s : σ) :
@[simp] theorem run_monadMap [MonadFunctorT n m] (f : {β : Type u} n β n β) (x : StateT σ m α) (s : σ) :
(monadMap @f x : StateT σ m α).run s = monadMap @f (x.run s) := rfl
@[simp] theorem run_seq {α β σ : Type u} [Monad m] [LawfulMonad m] (f : StateT σ m (α β)) (x : StateT σ m α) (s : σ) : (f <*> x).run s = (f.run s >>= fun fs => (fun (p : α × σ) => (fs.1 p.1, p.2)) <$> x.run fs.2) := by
@@ -425,48 +242,10 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (StateT σ m) where
pure_bind := by intros; apply ext; intros; simp
bind_assoc := by intros; apply ext; intros; simp
/-! Note that the `MonadControl` instance for `StateT` is not monad-generic. -/
@[simp] theorem run_restoreM [Monad m] [LawfulMonad m] (x : stM m (StateT σ m) α) (s : σ) :
StateT.run (restoreM x) s = pure x := by
simp [restoreM, MonadControl.restoreM]
rfl
@[simp] theorem run_liftWith [Monad m] [LawfulMonad m] (f : ({β : Type u} StateT σ m β m (stM m (StateT σ m) β)) m α) (s : σ) :
StateT.run (liftWith f) s = ((·, s) <$> f fun x => x.run s) := by
simp [liftWith, MonadControl.liftWith, Function.comp_def]
@[simp] theorem run_controlAt [Monad m] [LawfulMonad m] (f : ({β : Type u} StateT σ m β m (stM m (StateT σ m) β)) m (stM m (StateT σ m) α)) (s : σ) :
StateT.run (controlAt m f) s = f fun x => x.run s := by
simp [controlAt]
@[simp] theorem run_control [Monad m] [LawfulMonad m] (f : ({β : Type u} StateT σ m β m (stM m (StateT σ m) β)) m (stM m (StateT σ m) α)) (s : σ) :
StateT.run (control f) s = f fun x => x.run s := run_controlAt f s
end StateT
/-! # EStateM -/
namespace EStateM
@[simp, grind =] theorem run_pure (a : α) (s : σ) :
EStateM.run (pure a : EStateM ε σ α) s = .ok a s := rfl
@[simp, grind =] theorem run_get (s : σ) :
EStateM.run (get : EStateM ε σ σ) s = .ok s s := rfl
@[simp, grind =] theorem run_set (s₁ s₂ : σ) :
EStateM.run (set s₁ : EStateM ε σ PUnit) s₂ = .ok .unit s₁ := rfl
@[simp, grind =] theorem run_modify (f : σ σ) (s : σ) :
EStateM.run (modify f : EStateM ε σ PUnit) s = .ok .unit (f s) := rfl
@[simp, grind =] theorem run_modifyGet (f : σ α × σ) (s : σ) :
EStateM.run (modifyGet f : EStateM ε σ α) s = .ok (f s).1 (f s).2 := rfl
@[simp, grind =] theorem run_throw (e : ε) (s : σ):
EStateM.run (throw e : EStateM ε σ PUnit) s = .error e s := rfl
instance : LawfulMonad (EStateM ε σ) := .mk'
(id_map := fun x => funext <| fun s => by
dsimp only [EStateM.instMonad, EStateM.map]
@@ -480,5 +259,3 @@ instance : LawfulMonad (EStateM ε σ) := .mk'
| .ok _ _ => rfl
| .error _ _ => rfl)
(map_const := fun _ _ => rfl)
end EStateM

View File

@@ -7,6 +7,7 @@ module
prelude
public import Init.Control.Lawful.Basic
public import Init.RCases
public import Init.ByCases
public section

View File

@@ -1,10 +0,0 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
module
prelude
public import Init.Control.Lawful.MonadAttach.Lemmas
public import Init.Control.Lawful.MonadAttach.Instances

View File

@@ -1,86 +0,0 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
module
prelude
public import Init.Control.Reader
public import Init.Control.Lawful.Instances
import Init.Control.Lawful.MonadAttach.Lemmas
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (ReaderT ρ m) where
map_attach := by
simp only [Functor.map, MonadAttach.attach, Functor.map_map, WeaklyLawfulMonadAttach.map_attach]
intros; rfl
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m] :
LawfulMonadAttach (ReaderT ρ m) where
canReturn_map_imp := by
simp only [Functor.map, MonadAttach.CanReturn, ReaderT.run]
rintro _ _ x a r, h
apply LawfulMonadAttach.canReturn_map_imp h
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (StateT σ m) where
map_attach := by
intro α x
simp only [Functor.map, StateT, funext_iff, StateT.map, bind_pure_comp, MonadAttach.attach,
Functor.map_map]
exact fun s => WeaklyLawfulMonadAttach.map_attach
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m] :
LawfulMonadAttach (StateT σ m) where
canReturn_map_imp := by
simp only [Functor.map, MonadAttach.CanReturn, StateT.run, StateT.map, bind_pure_comp]
rintro _ _ x a s, s', h
obtain a, h, h' := LawfulMonadAttach.canReturn_map_imp' h
cases h'
exact a.1.2
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (ExceptT ε m) where
map_attach {α} x := by
simp only [Functor.map, MonadAttach.attach, ExceptT.map]
simp
conv => rhs; rw [ WeaklyLawfulMonadAttach.map_attach (m := m) (x := x)]
simp only [map_eq_pure_bind]
apply bind_congr; intro a
match a with
| .ok _, _ => simp
| .error _, _ => simp
public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m] :
LawfulMonadAttach (ExceptT ε m) where
canReturn_map_imp {α P x a} := by
simp only [Functor.map, MonadAttach.CanReturn, ExceptT.map, ExceptT.mk]
let x' := (fun a => show Subtype (fun a : Except _ _ => match a with | .ok a => P a | .error e => True) from match a with | .ok a => .ok a.1 | .error e => .error e, by cases a <;> simp [Subtype.property]) <$> show m _ from x
have := LawfulMonadAttach.canReturn_map_imp (m := m) (x := x') (a := .ok a)
simp only at this
intro h
apply this
simp only [x', map_eq_pure_bind, bind_assoc]
refine cast ?_ h
congr 1
apply bind_congr; intro a
split <;> simp
public instance [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (StateRefT' ω σ m) :=
inferInstanceAs (WeaklyLawfulMonadAttach (ReaderT _ _))
public instance [Monad m] [MonadAttach m] [LawfulMonad m] [LawfulMonadAttach m] :
LawfulMonadAttach (StateRefT' ω σ m) :=
inferInstanceAs (LawfulMonadAttach (ReaderT _ _))
section
attribute [local instance] MonadAttach.trivial
public instance [Monad m] [LawfulMonad m] :
WeaklyLawfulMonadAttach m where
map_attach := by simp [MonadAttach.attach]
end

View File

@@ -1,90 +0,0 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
module
prelude
public import Init.Control.MonadAttach
import all Init.Control.MonadAttach
public import Init.Control.Lawful.Lemmas
public import Init.Control.Lawful.MonadLift.Lemmas
public theorem LawfulMonadAttach.canReturn_bind_imp' [Monad m] [LawfulMonad m]
[MonadAttach m] [LawfulMonadAttach m]
{x : m α} {f : α m β} :
MonadAttach.CanReturn (x >>= f) b Exists fun a => MonadAttach.CanReturn x a MonadAttach.CanReturn (f a) b := by
intro h
let P (b : β) := Exists fun a => MonadAttach.CanReturn x a MonadAttach.CanReturn (f a) b
have h' : (x >>= f) = Subtype.val <$> (MonadAttach.attach x >>= (fun a => (do
let b MonadAttach.attach (f a)
return b.1, a.1, a.2, b.2 : m (Subtype P)))) := by
simp only [map_bind, map_pure]
simp only [bind_pure_comp, WeaklyLawfulMonadAttach.map_attach]
rw (occs := [1]) [ WeaklyLawfulMonadAttach.map_attach (x := x)]
simp
rw [h'] at h
have := LawfulMonadAttach.canReturn_map_imp h
exact this
public theorem LawfulMonadAttach.eq_of_canReturn_pure [Monad m] [MonadAttach m]
[LawfulMonad m] [LawfulMonadAttach m] {a b : α}
(h : MonadAttach.CanReturn (m := m) (pure a) b) :
a = b := by
let x : m (Subtype (a = ·)) := pure a, rfl
have : pure a = Subtype.val <$> x := by simp [x]
rw [this] at h
exact LawfulMonadAttach.canReturn_map_imp h
public theorem LawfulMonadAttach.canReturn_map_imp' [Monad m] [LawfulMonad m]
[MonadAttach m] [LawfulMonadAttach m]
{x : m α} {f : α β} :
MonadAttach.CanReturn (f <$> x) b Exists fun a => MonadAttach.CanReturn x a f a = b := by
rw [map_eq_pure_bind]
intro h
obtain a, h, h' := canReturn_bind_imp' h
exact a, h, eq_of_canReturn_pure h'
public theorem LawfulMonadAttach.canReturn_liftM_imp'
[Monad m] [MonadAttach m] [LawfulMonad m] [LawfulMonadAttach m]
[Monad n] [MonadAttach n] [LawfulMonad n] [LawfulMonadAttach n]
[MonadLiftT m n] [LawfulMonadLiftT m n] {x : m α} {a : α} :
MonadAttach.CanReturn (liftM (n := n) x) a MonadAttach.CanReturn x a := by
intro h
simp only [ WeaklyLawfulMonadAttach.map_attach (x := x), liftM_map] at h
exact canReturn_map_imp h
public theorem WeaklyLawfulMonadAttach.attach_bind_val
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{x : m α} {f : α m β} :
MonadAttach.attach x >>= (fun a => f a.val) = x >>= f := by
conv => rhs; simp only [ map_attach (x := x), bind_map_left]
public theorem WeaklyLawfulMonadAttach.bind_attach_of_nonempty
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] [Nonempty (m β)]
{x : m α} {f : Subtype (MonadAttach.CanReturn x) m β} :
open scoped Classical in
MonadAttach.attach x >>= f = x >>= (fun a => if ha : MonadAttach.CanReturn x a then f a, ha else Classical.ofNonempty) := by
conv => rhs; simp +singlePass only [ map_attach (x := x)]
simp [Subtype.property]
public theorem MonadAttach.attach_bind_eq_pbind
[Monad m] [MonadAttach m]
{x : m α} {f : Subtype (MonadAttach.CanReturn x) m β} :
MonadAttach.attach x >>= f = MonadAttach.pbind x (fun a ha => f a, ha) := by
simp [MonadAttach.pbind]
public theorem WeaklyLawfulMonadAttach.pbind_eq_bind
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{x : m α} {f : α m β} :
MonadAttach.pbind x (fun a _ => f a) = x >>= f := by
conv => rhs; rw [ map_attach (x := x)]
simp [MonadAttach.pbind]
public theorem WeaklyLawfulMonadAttach.pbind_eq_bind'
[Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m]
{x : m α} {f : α m β} :
MonadAttach.pbind x (fun a _ => f a) = x >>= f := by
conv => rhs; rw [ map_attach (x := x)]
simp [MonadAttach.pbind]

View File

@@ -9,3 +9,5 @@ prelude
public import Init.Control.Lawful.MonadLift.Basic
public import Init.Control.Lawful.MonadLift.Lemmas
public import Init.Control.Lawful.MonadLift.Instances
public section

View File

@@ -6,13 +6,17 @@ Authors: Quang Dao, Paul Reichert
module
prelude
public import Init.Control.Option
import all Init.Control.Option
public import Init.Control.Except
import all Init.Control.Except
public import Init.Control.ExceptCps
import all Init.Control.ExceptCps
public import Init.Control.StateRef
import all Init.Control.StateRef
public import Init.Control.StateCps
import all Init.Control.StateCps
public import Init.Control.Id
import all Init.Control.Id
public import Init.Control.Lawful.MonadLift.Lemmas
public import Init.Control.Lawful.Instances
@@ -60,6 +64,10 @@ namespace OptionT
variable [Monad m] [LawfulMonad m]
@[simp]
theorem lift_pure {α : Type u} (a : α) : OptionT.lift (pure a : m α) = pure a := by
simp only [OptionT.lift, OptionT.mk, bind_pure_comp, map_pure, pure, OptionT.pure]
@[simp]
theorem lift_bind {α β : Type u} (ma : m α) (f : α m β) :
OptionT.lift (ma >>= f) = OptionT.lift ma >>= (fun a => OptionT.lift (f a)) := by

View File

@@ -6,7 +6,6 @@ Authors: Quang Dao
module
prelude
public import Init.Control.Id
public import Init.Control.Lawful.Basic
public import Init.Control.Lawful.MonadLift.Basic
@@ -14,14 +13,6 @@ public section
universe u v w
theorem instMonadLiftTOfMonadLift_instMonadLiftTOfPure [Monad m] [Monad n] {_ : MonadLift m n}
[LawfulMonadLift m n] : instMonadLiftTOfMonadLift Id m n = Id.instMonadLiftTOfPure := by
have hext {a b : MonadLiftT Id n} (h : @a.monadLift = @b.monadLift) : a = b := by
cases a <;> cases b <;> simp_all
apply hext
ext α x
simp [monadLift, LawfulMonadLift.monadLift_pure]
variable {m : Type u Type v} {n : Type u Type w} [Monad m] [Monad n] [MonadLiftT m n]
[LawfulMonadLiftT m n] {α β : Type u}
@@ -32,7 +23,7 @@ theorem monadLift_map [LawfulMonad m] [LawfulMonad n] (f : α → β) (ma : m α
theorem monadLift_seq [LawfulMonad m] [LawfulMonad n] (mf : m (α β)) (ma : m α) :
monadLift (mf <*> ma) = monadLift mf <*> (monadLift ma : n α) := by
simp only [seq_eq_bind_map, monadLift_map, monadLift_bind]
simp only [seq_eq_bind, monadLift_map, monadLift_bind]
theorem monadLift_seqLeft [LawfulMonad m] [LawfulMonad n] (x : m α) (y : m β) :
monadLift (x <* y) = (monadLift x : n α) <* (monadLift y : n β) := by

View File

@@ -1,126 +0,0 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
module
prelude
public import Init.Control.Basic
set_option linter.all true
set_option doc.verso true
/-!
# {name (scope := "Init.Control.MonadAttach")}`MonadAttach`
This module provides a mechanism for attaching proofs to the return values of monadic computations,
producing a new monadic computation returning a {name}`Subtype`.
This function is primarily used to allow definitions by [well-founded
recursion](lean-manual://section/well-founded-recursion) that sequence computations using
{name}`Bind.bind` (`>>=`) to prove properties about the return values of prior computations when
a recursive call happens.
This allows the well-founded recursion mechanism to prove that the function terminates.
-/
-- verso docstring is added below
set_option linter.missingDocs false in
public class MonadAttach (m : Type u Type v) where
/--
A predicate that can be assumed to be true for all return values {name}`a` of actions {name}`x`
in {name}`m`, in all situations.
-/
CanReturn {α : Type u} : (x : m α) (a : α) Prop
/--
Attaches a proof of {name}`MonadAttach.CanReturn` to the return value of {name}`x`. This proof
can be used to prove the termination of well-founded recursive functions.
-/
attach {α : Type u} (x : m α) : m (Subtype (CanReturn x))
-- verso docstring is added below
set_option linter.missingDocs false in
public class WeaklyLawfulMonadAttach (m : Type u Type v) [Monad m] [MonadAttach m] where
map_attach {α : Type u} {x : m α} : Subtype.val <$> MonadAttach.attach x = x
/--
This type class ensures that {name}`MonadAttach.CanReturn` is the unique strongest possible
postcondition.
-/
public class LawfulMonadAttach (m : Type u Type v) [Monad m] [MonadAttach m] extends
WeaklyLawfulMonadAttach m where
canReturn_map_imp {α : Type u} {P : α Prop} {x : m (Subtype P)} {a : α} :
MonadAttach.CanReturn (Subtype.val <$> x) a P a
/--
Like {name}`Bind.bind`, {name}`pbind` sequences two computations {lean}`x : m α` and {lean}`f`,
allowing the second to depend on the value computed by the first.
But other than with {name}`Bind.bind`, the second computation can also depend on a proof that
the return value {given}`a` of {name}`x` satisfies {lean}`MonadAttach.CanReturn x a`.
-/
public def MonadAttach.pbind [Monad m] [MonadAttach m]
(x : m α) (f : (a : α) MonadAttach.CanReturn x a m β) : m β :=
MonadAttach.attach x >>= (fun a, ha => f a ha)
/--
A {lean}`MonadAttach` instance where all return values are possible and {name}`attach` adds no
information to the return value, except a trivial proof of {name}`True`.
This instance is used whenever no more useful {name}`MonadAttach` instance can be implemented.
It always has a {name}`WeaklyLawfulMonadAttach`, but usually no {name}`LawfulMonadAttach` instance.
-/
@[expose]
public protected def MonadAttach.trivial {m : Type u Type v} [Monad m] : MonadAttach m where
CanReturn _ _ := True
attach x := (·, .intro) <$> x
section
variable (α : Type u) [ m, Monad m] [ m, MonadAttach m]
set_option doc.verso true
/--
For every {given}`x : m α`, this type class provides a predicate {lean}`MonadAttach.CanReturn x`
and a way to attach a proof of this predicate to the return values of {name}`x` by providing
an element {lean}`MonadAttach.attach x` of {lean}`m { a : α // MonadAttach.CanReturn x a }`.
Instances should abide the law {lean}`Subtype.val <$> MonadAttach.attach x = x`, which is encoded by
the {name}`WeaklyLawfulMonadAttach` type class. The stronger type class {name}`LawfulMonadAttach`
ensures that {lean}`MonadAttach.CanReturn x` is the _unique_ strongest possible predicate.
Similarly to {name (scope := "Init.Data.List.Attach")}`List.attach`, the purpose of
{name}`MonadAttach` is to attach proof terms necessary for well-founded termination proofs.
The iterator library relies on {name}`MonadAttach` for combinators such as
{name (scope := "Init.Data.Iterators")}`Std.Iter.filterM` in order to automatically attach
information about the monadic predicate's behavior that could be relevant for the termination
behavior of the iterator.
*Limitations*:
For many monads, there is a strongly lawful {lean}`MonadAttach` instance, but there are exceptions.
For example, there is no way to provide a computable {lean}`MonadAttach` instance for the CPS monad
transformers
{name (scope := "Init.Control.StateCps")}`StateCpsT` and
{name (scope := "Init.Control.StateCps")}`ExceptCpsT` with a predicate that is not always
{name}`True`. Therefore, such CPS monads only provide the trivial {lean}`MonadAttach` instance
{lean}`MonadAttach.trivial` together with {name}`WeaklyLawfulMonadAttach`, but without
{name}`LawfulMonadAttach`.
For most monads with side effects, {lean}`MonadAttach` is too weak to fully capture the behavior of
computations because the postcondition represented by {name}`MonadAttach.CanReturn` neither depends
on the prior internal state of the monad, nor does it contain information about how the state of the
monad changes with the computation.
-/
add_decl_doc MonadAttach
/--
This type class ensures that every monadic action {given}`x : m α` can be recovered by stripping the
proof component from the subtypes returned by
{lean}`(MonadAttach.attach x) : m { a : α // MonadAttach.CanReturn x a }` . In other words,
the type class ensures that {lean}`Subtype.val <$> MonadAttach.attach x = x`.
-/
add_decl_doc WeaklyLawfulMonadAttach
end

View File

@@ -7,6 +7,7 @@ module
prelude
public import Init.Data.Option.Basic
public import Init.Control.Basic
public import Init.Control.Except
public section
@@ -27,7 +28,7 @@ failure occurred.
/--
Executes an action that might fail in the underlying monad `m`, returning `none` in case of failure.
-/
@[always_inline, inline, expose]
@[always_inline, inline]
def OptionT.run {m : Type u Type v} {α : Type u} (x : OptionT m α) : m (Option α) :=
x
@@ -38,14 +39,13 @@ variable {m : Type u → Type v} [Monad m] {α β : Type u}
Converts an action that returns an `Option` into one that might fail, with `none` indicating
failure.
-/
@[always_inline, inline, expose]
protected def mk (x : m (Option α)) : OptionT m α :=
x
/--
Sequences two potentially-failing actions. The second action is run only if the first succeeds.
-/
@[always_inline, inline, expose]
@[always_inline, inline]
protected def bind (x : OptionT m α) (f : α OptionT m β) : OptionT m β := OptionT.mk do
match ( x) with
| some a => f a
@@ -54,7 +54,7 @@ protected def bind (x : OptionT m α) (f : α → OptionT m β) : OptionT m β :
/--
Succeeds with the provided value.
-/
@[always_inline, inline, expose]
@[always_inline, inline]
protected def pure (a : α) : OptionT m α := OptionT.mk do
pure (some a)
@@ -69,7 +69,7 @@ instance {m : Type u → Type v} [Pure m] : Inhabited (OptionT m α) where
/--
Recovers from failures. Typically used via the `<|>` operator.
-/
@[always_inline, inline, expose] protected def orElse (x : OptionT m α) (y : Unit OptionT m α) : OptionT m α := OptionT.mk do
@[always_inline, inline] protected def orElse (x : OptionT m α) (y : Unit OptionT m α) : OptionT m α := OptionT.mk do
match ( x) with
| some a => pure (some a)
| _ => y ()
@@ -77,7 +77,7 @@ Recovers from failures. Typically used via the `<|>` operator.
/--
A recoverable failure.
-/
@[always_inline, inline, expose] protected def fail : OptionT m α := OptionT.mk do
@[always_inline, inline] protected def fail : OptionT m α := OptionT.mk do
pure none
instance : Alternative (OptionT m) where
@@ -90,7 +90,7 @@ Converts a computation from the underlying monad into one that could fail, even
This function is typically implicitly accessed via a `MonadLiftT` instance as part of [automatic
lifting](lean-manual://section/monad-lifting).
-/
@[always_inline, inline, expose] protected def lift (x : m α) : OptionT m α := OptionT.mk do
@[always_inline, inline] protected def lift (x : m α) : OptionT m α := OptionT.mk do
return some ( x)
instance : MonadLift m (OptionT m) := OptionT.lift
@@ -100,11 +100,11 @@ instance : MonadFunctor m (OptionT m) := ⟨fun f x => f x⟩
/--
Handles failures by treating them as exceptions of type `Unit`.
-/
@[always_inline, inline, expose] protected def tryCatch (x : OptionT m α) (handle : PUnit OptionT m α) : OptionT m α := OptionT.mk do
let some a x | handle
@[always_inline, inline] protected def tryCatch (x : OptionT m α) (handle : Unit OptionT m α) : OptionT m α := OptionT.mk do
let some a x | handle ()
pure <| some a
instance : MonadExceptOf PUnit (OptionT m) where
instance : MonadExceptOf Unit (OptionT m) where
throw := fun _ => OptionT.fail
tryCatch := OptionT.tryCatch
@@ -112,12 +112,6 @@ instance (ε : Type u) [MonadExceptOf ε m] : MonadExceptOf ε (OptionT m) where
throw e := OptionT.mk <| throwThe ε e
tryCatch x handle := OptionT.mk <| tryCatchThe ε x handle
instance [MonadAttach m] : MonadAttach (OptionT m) where
CanReturn x a := MonadAttach.CanReturn x.run (some a)
attach x := .mk ((fun
| some a, h => some a, h
| none, _ => none) <$> MonadAttach.attach x.run)
end OptionT
instance [Monad m] : MonadControl m (OptionT m) where

View File

@@ -8,6 +8,8 @@ The Reader monad transformer for passing immutable State.
module
prelude
public import Init.Control.Basic
public import Init.Control.Id
public import Init.Control.Except
public section
@@ -51,7 +53,3 @@ A monad with access to a read-only value of type `ρ`. The value can be locally
`withReader`, but it cannot be mutated.
-/
abbrev ReaderM (ρ : Type u) := ReaderT ρ Id
instance [Monad m] [MonadAttach m] : MonadAttach (ReaderT ρ m) where
CanReturn x a := Exists (fun r => MonadAttach.CanReturn (x.run r) a)
attach x := fun r => (fun a, h => a, r, h) <$> MonadAttach.attach (x.run r)

View File

@@ -8,6 +8,8 @@ The State monad transformer.
module
prelude
public import Init.Control.Basic
public import Init.Control.Id
public import Init.Control.Except
public section
@@ -25,12 +27,6 @@ of a value and a state.
@[expose] def StateT (σ : Type u) (m : Type u Type v) (α : Type u) : Type (max u v) :=
σ m (α × σ)
/--
Interpret `σ → m (α × σ)` as an element of `StateT σ m α`.
-/
@[always_inline, inline, expose]
def StateT.mk {σ : Type u} {m : Type u Type v} {α : Type u} (x : σ m (α × σ)) : StateT σ m α := x
/--
Executes an action from a monad with added state in the underlying monad `m`. Given an initial
state, it returns a value paired with the final state.
@@ -204,7 +200,3 @@ instance StateT.tryFinally {m : Type u → Type v} {σ : Type u} [MonadFinally m
| some (a, s') => h (some a) s'
| none => h none s
pure ((a, b), s'')
instance [Monad m] [MonadAttach m] : MonadAttach (StateT σ m) where
CanReturn x a := Exists fun s => Exists fun s' => MonadAttach.CanReturn (x.run s) (a, s')
attach x := fun s => (fun a, s', h => a, s, s', h, s') <$> MonadAttach.attach (x.run s)

View File

@@ -68,13 +68,6 @@ instance : MonadStateOf σ (StateCpsT σ m) where
set s := fun _ _ k => k s
modifyGet f := fun _ s k => let (a, s) := f s; k a s
/--
For continuation monads, it is not possible to provide a computable `MonadAttach` instance that
actually adds information about the return value. Therefore, this instance always attaches a proof
of `True`.
-/
instance : MonadAttach (StateCpsT ε m) := .trivial
/--
Runs an action from the underlying monad in the monad with state. The state is not modified.

View File

@@ -64,7 +64,6 @@ instance [Monad m] : Monad (StateRefT' ω σ m) := inferInstanceAs (Monad (Reade
instance : MonadLift m (StateRefT' ω σ m) := StateRefT'.lift
instance (σ m) : MonadFunctor m (StateRefT' ω σ m) := inferInstanceAs (MonadFunctor m (ReaderT _ _))
instance [Alternative m] [Monad m] : Alternative (StateRefT' ω σ m) := inferInstanceAs (Alternative (ReaderT _ _))
instance [Monad m] [MonadAttach m] : MonadAttach (StateRefT' ω σ m) := inferInstanceAs (MonadAttach (ReaderT _ _))
/--
Retrieves the current value of the monad's mutable state.

View File

@@ -8,15 +8,12 @@ notation, basic datatypes and type classes
module
prelude
public meta import Init.Prelude
public import Init.SizeOf
public section
set_option linter.missingDocs true -- keep it documented
-- BEq instance for Option defined here so it's available early in the import chain
-- (before Init.Grind.Config and Init.MetaTypes which need BEq (Option Nat))
deriving instance BEq for Option
@[expose] section
universe u v w
@@ -104,6 +101,7 @@ instance : DecidableEq Empty := fun a => a.elim
/-- Decidable equality for PEmpty -/
instance : DecidableEq PEmpty := fun a => a.elim
set_option genInjectivity false in
/--
Delays evaluation. The delayed code is evaluated at most once.
@@ -147,9 +145,8 @@ Computed values are cached, so the value is not recomputed.
x.fn ()
-- Ensure `Thunk.fn` is still computable even if it shouldn't be accessed directly.
/-- Implementation detail. -/
@[inline] def Thunk.fnImpl (x : Thunk α) : Unit α := fun _ => x.get
@[csimp] theorem Thunk.fn_eq_fnImpl : @Thunk.fn = @Thunk.fnImpl := rfl
@[inline] private def Thunk.fnImpl (x : Thunk α) : Unit α := fun _ => x.get
@[csimp] private theorem Thunk.fn_eq_fnImpl : @Thunk.fn = @Thunk.fnImpl := rfl
/--
Constructs a new thunk that forces `x` and then applies `x` to the result. Upon forcing, the result
@@ -205,7 +202,6 @@ An element of `α ⊕ β` is either an `a : α` wrapped in `Sum.inl` or a `b :
indication of which of the two types was chosen. The union of a singleton set with itself contains
one element, while `Unit ⊕ Unit` contains distinct values `inl ()` and `inr ()`.
-/
@[suggest_for Either]
inductive Sum (α : Type u) (β : Type v) where
/-- Left injection into the sum type `α ⊕ β`. -/
| inl (val : α) : Sum α β
@@ -341,7 +337,7 @@ inductive Exists {α : Sort u} (p : α → Prop) : Prop where
An indication of whether a loop's body terminated early that's used to compile the `for x in xs`
notation.
A collection's `ForIn` or `ForIn'` instance describes how to iterate over its elements. The monadic
A collection's `ForIn` or `ForIn'` instance describe's how to iterate over its elements. The monadic
action that represents the body of the loop returns a `ForInStep α`, where `α` is the local state
used to implement features such as `let mut`.
-/
@@ -382,7 +378,7 @@ class ForIn (m : Type u₁ → Type u₂) (ρ : Type u) (α : outParam (Type v))
More information about the translation of `for` loops into `ForIn.forIn` is available in [the Lean
reference manual](lean-manual://section/monad-iteration-syntax).
-/
forIn {β} (xs : ρ) (b : β) (f : α β m (ForInStep β)) : m β
forIn {β} [Monad m] (xs : ρ) (b : β) (f : α β m (ForInStep β)) : m β
export ForIn (forIn)
@@ -410,7 +406,7 @@ class ForIn' (m : Type u₁ → Type u₂) (ρ : Type u) (α : outParam (Type v)
More information about the translation of `for` loops into `ForIn'.forIn'` is available in [the
Lean reference manual](lean-manual://section/monad-iteration-syntax).
-/
forIn' {β} (x : ρ) (b : β) (f : (a : α) a x β m (ForInStep β)) : m β
forIn' {β} [Monad m] (x : ρ) (b : β) (f : (a : α) a x β m (ForInStep β)) : m β
export ForIn' (forIn')
@@ -514,12 +510,12 @@ abbrev SSuperset [HasSSubset α] (a b : α) := SSubset b a
/-- Notation type class for the union operation ``. -/
class Union (α : Type u) where
/-- `a b` is the union of `a` and `b`. -/
/-- `a b` is the union of`a` and `b`. -/
union : α α α
/-- Notation type class for the intersection operation `∩`. -/
class Inter (α : Type u) where
/-- `a ∩ b` is the intersection of `a` and `b`. -/
/-- `a ∩ b` is the intersection of`a` and `b`. -/
inter : α α α
/-- Notation type class for the set difference `\`. -/
@@ -542,10 +538,10 @@ infix:50 " ⊇ " => Superset
/-- Strict superset relation: `a ⊃ b` -/
infix:50 "" => SSuperset
/-- `a b` is the union of `a` and `b`. -/
/-- `a b` is the union of`a` and `b`. -/
infixl:65 " " => Union.union
/-- `a ∩ b` is the intersection of `a` and `b`. -/
/-- `a ∩ b` is the intersection of`a` and `b`. -/
infixl:70 "" => Inter.inter
/--
@@ -605,11 +601,23 @@ export LawfulSingleton (insert_empty_eq)
attribute [simp] insert_empty_eq
@[deprecated insert_empty_eq (since := "2025-03-12")]
theorem insert_emptyc_eq [EmptyCollection β] [Insert α β] [Singleton α β]
[LawfulSingleton α β] (x : α) : (insert x : β) = singleton x :=
insert_empty_eq _
@[deprecated insert_empty_eq (since := "2025-03-12")]
theorem LawfulSingleton.insert_emptyc_eq [EmptyCollection β] [Insert α β] [Singleton α β]
[LawfulSingleton α β] (x : α) : (insert x : β) = singleton x :=
insert_empty_eq _
/-- Type class used to implement the notation `{ a ∈ c | p a }` -/
class Sep (α : outParam <| Type u) (γ : Type v) where
/-- Computes `{ a ∈ c | p a }`. -/
sep : (α Prop) γ γ
set_option genInjectivity false in
/--
`Task α` is a primitive for asynchronous computation.
It represents a computation that will resolve to a value of type `α`,
@@ -944,7 +952,9 @@ theorem HEq.subst {p : (T : Sort u) → T → Prop} (h₁ : a ≍ b) (h₂ : p
@[symm] theorem HEq.symm (h : a b) : b a :=
h.rec (HEq.refl a)
/-- Propositionally equal terms are also heterogeneously equal. -/
theorem heq_of_eq (h : a = a') : a a' :=
Eq.subst h (HEq.refl a)
/-- Heterogeneous equality is transitive. -/
theorem HEq.trans (h₁ : a b) (h₂ : b c) : a c :=
@@ -1087,6 +1097,14 @@ theorem of_toBoolUsing_eq_true {p : Prop} {d : Decidable p} (h : toBoolUsing d =
theorem of_toBoolUsing_eq_false {p : Prop} {d : Decidable p} (h : toBoolUsing d = false) : ¬p :=
of_decide_eq_false h
set_option linter.missingDocs false in
@[deprecated of_toBoolUsing_eq_true (since := "2025-04-04")]
abbrev ofBoolUsing_eq_true := @of_toBoolUsing_eq_true
set_option linter.missingDocs false in
@[deprecated of_toBoolUsing_eq_false (since := "2025-04-04")]
abbrev ofBoolUsing_eq_false := @of_toBoolUsing_eq_false
instance : Decidable True :=
isTrue trivial
@@ -1149,7 +1167,6 @@ end
else isFalse (fun h => absurd (h hp) hq)
else isTrue (fun h => absurd h hp)
@[inline]
instance {p q} [Decidable p] [Decidable q] : Decidable (p q) :=
if hp : p then
if hq : q then
@@ -1191,13 +1208,17 @@ theorem dif_neg {c : Prop} {h : Decidable c} (hnc : ¬c) {α : Sort u} {t : c
| isTrue hc => absurd hc hnc
| isFalse _ => rfl
@[macro_inline]
-- Remark: dite and ite are "defally equal" when we ignore the proofs.
theorem dif_eq_if (c : Prop) {h : Decidable c} {α : Sort u} (t : α) (e : α) : dite c (fun _ => t) (fun _ => e) = ite c t e :=
match h with
| isTrue _ => rfl
| isFalse _ => rfl
instance {c t e : Prop} [dC : Decidable c] [dT : Decidable t] [dE : Decidable e] : Decidable (if c then t else e) :=
match dC with
| isTrue _ => dT
| isFalse _ => dE
@[inline]
instance {c : Prop} {t : c Prop} {e : ¬c Prop} [dC : Decidable c] [dT : h, Decidable (t h)] [dE : h, Decidable (e h)] : Decidable (if h : c then t h else e h) :=
match dC with
| isTrue hc => dT hc
@@ -1348,12 +1369,12 @@ namespace Subtype
theorem exists_of_subtype {α : Type u} {p : α Prop} : { x // p x } Exists (fun x => p x)
| a, h => a, h
variable {α : Sort u} {p : α Prop}
set_option linter.missingDocs false in
@[deprecated exists_of_subtype (since := "2025-04-04")]
abbrev existsOfSubtype := @exists_of_subtype
protected theorem ext : {a1 a2 : {x // p x}}, val a1 = val a2 a1 = a2
| _, _, _, _, rfl => rfl
variable {α : Type u} {p : α Prop}
@[deprecated Subtype.ext (since := "2025-10-26")]
protected theorem eq : {a1 a2 : {x // p x}}, val a1 = val a2 a1 = a2
| _, _, _, _, rfl => rfl
@@ -1368,12 +1389,12 @@ instance {α : Type u} {p : α → Prop} [BEq α] [ReflBEq α] : ReflBEq {x : α
rfl {x} := BEq.refl x.1
instance {α : Type u} {p : α Prop} [BEq α] [LawfulBEq α] : LawfulBEq {x : α // p x} where
eq_of_beq h := Subtype.ext (eq_of_beq h)
eq_of_beq h := Subtype.eq (eq_of_beq h)
instance {α : Sort u} {p : α Prop} [DecidableEq α] : DecidableEq {x : α // p x} :=
instance {α : Type u} {p : α Prop} [DecidableEq α] : DecidableEq {x : α // p x} :=
fun a, h₁ b, h₂ =>
if h : a = b then isTrue (by subst h; exact rfl)
else isFalse (fun h' => Subtype.noConfusion rfl .rfl (heq_of_eq h') (fun h' => absurd (eq_of_heq h') h))
else isFalse (fun h' => Subtype.noConfusion h' (fun h' => absurd h' h))
end Subtype
@@ -1432,8 +1453,8 @@ instance [DecidableEq α] [DecidableEq β] : DecidableEq (α × β) :=
| isTrue e₁ =>
match decEq b b' with
| isTrue e₂ => isTrue (e₁ e₂ rfl)
| isFalse n₂ => isFalse fun h => Prod.noConfusion rfl rfl (heq_of_eq h) fun _ e₂' => absurd (eq_of_heq e₂') n₂
| isFalse n₁ => isFalse fun h => Prod.noConfusion rfl rfl (heq_of_eq h) fun e₁' _ => absurd (eq_of_heq e₁') n₁
| isFalse n₂ => isFalse fun h => Prod.noConfusion h fun _ e₂' => absurd e₂' n₂
| isFalse n₁ => isFalse fun h => Prod.noConfusion h fun e₁' _ => absurd e₁' n₁
instance [BEq α] [BEq β] : BEq (α × β) where
beq := fun (a₁, b₁) (a₂, b₂) => a₁ == a₂ && b₁ == b₂
@@ -1471,8 +1492,6 @@ def Prod.map {α₁ : Type u₁} {α₂ : Type u₂} {β₁ : Type v₁} {β₂
@[simp] theorem Prod.map_apply (f : α β) (g : γ δ) (x) (y) :
Prod.map f g (x, y) = (f x, g y) := rfl
-- We add `@[grind =]` to these in `Init.Data.Prod`.
@[simp] theorem Prod.map_fst (f : α β) (g : γ δ) (x) : (Prod.map f g x).1 = f x.1 := rfl
@[simp] theorem Prod.map_snd (f : α β) (g : γ δ) (x) : (Prod.map f g x).2 = g x.2 := rfl
@@ -1489,24 +1508,20 @@ protected theorem PSigma.eta {α : Sort u} {β : α → Sort v} {a₁ a₂ : α}
/-! # Universe polymorphic unit -/
theorem PUnit.ext (a b : PUnit) : a = b := by
cases a; cases b; exact rfl
@[deprecated PUnit.ext (since := "2025-10-26")]
theorem PUnit.subsingleton (a b : PUnit) : a = b := by
cases a; cases b; exact rfl
theorem PUnit.eq_punit (a : PUnit) : a = :=
PUnit.ext a
PUnit.subsingleton a
instance : Subsingleton PUnit :=
Subsingleton.intro PUnit.ext
Subsingleton.intro PUnit.subsingleton
instance : Inhabited PUnit where
default :=
instance : DecidableEq PUnit :=
fun a b => isTrue (PUnit.ext a b)
fun a b => isTrue (PUnit.subsingleton a b)
/-! # Setoid -/
@@ -1565,13 +1580,8 @@ instance {p q : Prop} [d : Decidable (p ↔ q)] : Decidable (p = q) :=
| isTrue h => isTrue (propext h)
| isFalse h => isFalse fun heq => h (heq Iff.rfl)
/-- Helper theorem for proving injectivity theorems -/
theorem Lean.injEq_helper {P Q R : Prop} :
(P Q R) (P Q R) := by intro h h₁,h₂; exact h h₁ h₂
gen_injective_theorems% Array
gen_injective_theorems% BitVec
gen_injective_theorems% ByteArray
gen_injective_theorems% Char
gen_injective_theorems% DoResultBC
gen_injective_theorems% DoResultPR
@@ -1596,8 +1606,8 @@ gen_injective_theorems% PSigma
gen_injective_theorems% PSum
gen_injective_theorems% Sigma
gen_injective_theorems% String
gen_injective_theorems% String.Pos.Raw
gen_injective_theorems% Substring.Raw
gen_injective_theorems% String.Pos
gen_injective_theorems% Substring
gen_injective_theorems% Subtype
gen_injective_theorems% Sum
gen_injective_theorems% Task
@@ -2514,7 +2524,8 @@ class Antisymm (r : αα → Prop) : Prop where
/-- An antisymmetric relation `r` satisfies `r a b → r b a → a = b`. -/
antisymm (a b : α) : r a b r b a a = b
/-- `Asymm r` means that the binary relation `r` is asymmetric, that is, `r a b → ¬ r b a`. -/
/-- `Asymm r` means that the binary relation `r` is asymmetric, that is,
`r a b → ¬ r b a`. -/
class Asymm (r : α α Prop) : Prop where
/-- An asymmetric relation satisfies `r a b → ¬ r b a`. -/
asymm : a b, r a b ¬r b a
@@ -2524,19 +2535,20 @@ class Symm (r : αα → Prop) : Prop where
/-- A symmetric relation satisfies `r a b → r b a`. -/
symm : a b, r a b r b a
/-- `Total X r` means that the binary relation `r` on `X` is total, that is, `r a b` or `r b a`. -/
/-- `Total X r` means that the binary relation `r` on `X` is total, that is, that for any
`x y : X` we have `r x y` or `r y x`. -/
class Total (r : α α Prop) : Prop where
/-- A total relation satisfies `r a b` or `r b a`. -/
/-- A total relation satisfies `r a b r b a`. -/
total : a b, r a b r b a
/-- `Irrefl r` means the binary relation `r` is irreflexive, that is, `r x x` never holds. -/
/-- `Irrefl r` means the binary relation `r` is irreflexive, that is, `r x x` never
holds. -/
class Irrefl (r : α α Prop) : Prop where
/-- An irreflexive relation satisfies `¬ r a a`. -/
irrefl : a, ¬r a a
/-- `Trichotomous r` says that `r` is trichotomous, that is, `¬ r a b → ¬ r b a → a = b`. -/
class Trichotomous (r : α α Prop) : Prop where
/-- An trichotomous relation `r` satisfies `¬ r a b → ¬ r b a → a = b`. -/
trichotomous (a b : α) : ¬ r a b ¬ r b a a = b
end Std
/-- Deprecated alias for `XorOp`. -/
@[deprecated XorOp (since := "2025-07-30")]
abbrev Xor := XorOp

View File

@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
module
prelude
public import Init.Data.Basic
public import Init.Data.Nat
public import Init.Data.Bool
public import Init.Data.BitVec
@@ -29,7 +30,6 @@ public import Init.Data.Random
public import Init.Data.ToString
public import Init.Data.Range
public import Init.Data.Hashable
public import Init.Data.LawfulHashable
public import Init.Data.OfScientific
public import Init.Data.Format
public import Init.Data.Stream
@@ -51,4 +51,5 @@ public import Init.Data.Range.Polymorphic
public import Init.Data.Slice
public import Init.Data.Order
public import Init.Data.Rat
public import Init.Data.Dyadic
public section

View File

@@ -7,6 +7,7 @@ Authors: Dany Fabian
module
prelude
public import Init.Classical
public import Init.ByCases
@[expose] public section

View File

@@ -30,3 +30,5 @@ public import Init.Data.Array.Erase
public import Init.Data.Array.Zip
public import Init.Data.Array.InsertIdx
public import Init.Data.Array.Extract
public section

View File

@@ -6,7 +6,10 @@ Authors: Joachim Breitner, Mario Carneiro
module
prelude
public import Init.Data.Array.Mem
public import Init.Data.Array.Lemmas
public import Init.Data.Array.Count
public import Init.Data.List.Attach
import all Init.Data.List.Attach
public section
@@ -81,10 +84,10 @@ well-founded recursion mechanism to prove that the function terminates.
simp [pmap]
/-- Implementation of `pmap` using the zero-copy version of `attach`. -/
@[inline] def pmapImpl {P : α Prop} (f : a, P a β) (xs : Array α) (H : a xs, P a) :
@[inline] private def pmapImpl {P : α Prop} (f : a, P a β) (xs : Array α) (H : a xs, P a) :
Array β := (xs.attachWith _ H).map fun x, h' => f x h'
@[csimp] theorem pmap_eq_pmapImpl : @pmap = @pmapImpl := by
@[csimp] private theorem pmap_eq_pmapImpl : @pmap = @pmapImpl := by
funext α β p f xs H
cases xs
simp only [pmap, pmapImpl, List.attachWith_toArray, List.map_toArray, mk.injEq, List.map_attachWith_eq_pmap]
@@ -92,16 +95,16 @@ well-founded recursion mechanism to prove that the function terminates.
intro a m h₁ h₂
congr
@[simp] theorem pmap_empty {P : α Prop} (f : a, P a β) : pmap f #[] (by simp) = #[] := rfl
@[simp, grind =] theorem pmap_empty {P : α Prop} (f : a, P a β) : pmap f #[] (by simp) = #[] := rfl
@[simp] theorem pmap_push {P : α Prop} (f : a, P a β) (a : α) (xs : Array α) (h : b xs.push a, P b) :
@[simp, grind =] theorem pmap_push {P : α Prop} (f : a, P a β) (a : α) (xs : Array α) (h : b xs.push a, P b) :
pmap f (xs.push a) h =
(pmap f xs (fun a m => by simp at h; exact h a (.inl m))).push (f a (h a (by simp))) := by
simp [pmap]
@[simp] theorem attach_empty : (#[] : Array α).attach = #[] := rfl
@[simp, grind =] theorem attach_empty : (#[] : Array α).attach = #[] := rfl
@[simp] theorem attachWith_empty {P : α Prop} (H : x #[], P x) : (#[] : Array α).attachWith P H = #[] := rfl
@[simp, grind =] theorem attachWith_empty {P : α Prop} (H : x #[], P x) : (#[] : Array α).attachWith P H = #[] := rfl
@[simp] theorem _root_.List.attachWith_mem_toArray {l : List α} :
l.attachWith (fun x => x l.toArray) (fun x h => by simpa using h) =
@@ -118,15 +121,17 @@ theorem pmap_eq_map {p : α → Prop} {f : α → β} {xs : Array α} (H) :
theorem pmap_congr_left {p q : α Prop} {f : a, p a β} {g : a, q a β} (xs : Array α) {H₁ H₂}
(h : a xs, (h₁ h₂), f a h₁ = g a h₂) : pmap f xs H₁ = pmap g xs H₂ := by
cases xs
simp only [List.mem_toArray] at h
simp only [mem_toArray] at h
simp only [List.pmap_toArray, mk.injEq]
rw [List.pmap_congr_left _ h]
@[grind =]
theorem map_pmap {p : α Prop} {g : β γ} {f : a, p a β} {xs : Array α} (H) :
map g (pmap f xs H) = pmap (fun a h => g (f a h)) xs H := by
cases xs
simp [List.map_pmap]
@[grind =]
theorem pmap_map {p : β Prop} {g : b, p b γ} {f : α β} {xs : Array α} (H) :
pmap g (map f xs) H = pmap (fun a h => g (f a) h) xs fun _ h => H _ (mem_map_of_mem h) := by
cases xs
@@ -142,14 +147,14 @@ theorem attachWith_congr {xs ys : Array α} (w : xs = ys) {P : α → Prop} {H :
subst w
simp
@[simp] theorem attach_push {a : α} {xs : Array α} :
@[simp, grind =] theorem attach_push {a : α} {xs : Array α} :
(xs.push a).attach =
(xs.attach.map (fun x, h => x, mem_push_of_mem a h)).push a, by simp := by
cases xs
rw [attach_congr (List.push_toArray _ _)]
simp [Function.comp_def]
@[simp] theorem attachWith_push {a : α} {xs : Array α} {P : α Prop} {H : x xs.push a, P x} :
@[simp, grind =] theorem attachWith_push {a : α} {xs : Array α} {P : α Prop} {H : x xs.push a, P x} :
(xs.push a).attachWith P H =
(xs.attachWith P (fun x h => by simp at H; exact H x (.inl h))).push a, H a (by simp) := by
cases xs
@@ -171,6 +176,9 @@ theorem attach_map_val (xs : Array α) (f : α → β) :
cases xs
simp
@[deprecated attach_map_val (since := "2025-02-17")]
abbrev attach_map_coe := @attach_map_val
-- The argument `xs : Array α` is explicit to allow rewriting from right to left.
theorem attach_map_subtype_val (xs : Array α) : xs.attach.map Subtype.val = xs := by
cases xs; simp
@@ -179,18 +187,21 @@ theorem attachWith_map_val {p : α → Prop} {f : α → β} {xs : Array α} (H
((xs.attachWith p H).map fun (i : { i // p i}) => f i) = xs.map f := by
cases xs; simp
@[deprecated attachWith_map_val (since := "2025-02-17")]
abbrev attachWith_map_coe := @attachWith_map_val
theorem attachWith_map_subtype_val {p : α Prop} {xs : Array α} (H : a xs, p a) :
(xs.attachWith p H).map Subtype.val = xs := by
cases xs; simp
@[simp, grind ]
@[simp, grind]
theorem mem_attach (xs : Array α) : x, x xs.attach
| a, h => by
have := mem_map.1 (by rw [attach_map_subtype_val] <;> exact h)
rcases this with _, _, m, rfl
exact m
@[simp, grind =]
@[simp, grind]
theorem mem_attachWith {xs : Array α} {q : α Prop} (H) (x : {x // q x}) :
x xs.attachWith q H x.1 xs := by
cases xs
@@ -201,13 +212,12 @@ theorem mem_pmap {p : α → Prop} {f : ∀ a, p a → β} {xs H b} :
b pmap f xs H (a : _) (h : a xs), f a (H a h) = b := by
simp only [pmap_eq_map_attach, mem_map, mem_attach, true_and, Subtype.exists, eq_comm]
@[grind]
theorem mem_pmap_of_mem {p : α Prop} {f : a, p a β} {xs H} {a} (h : a xs) :
f a (H a h) pmap f xs H := by
rw [mem_pmap]
exact a, h, rfl
grind_pattern mem_pmap_of_mem => _ pmap f xs H, a xs
@[simp, grind =]
theorem size_pmap {p : α Prop} {f : a, p a β} {xs H} : (pmap f xs H).size = xs.size := by
cases xs; simp
@@ -283,23 +293,25 @@ theorem getElem_attach {xs : Array α} {i : Nat} (h : i < xs.attach.size) :
xs.attach[i] = xs[i]'(by simpa using h), getElem_mem (by simpa using h) :=
getElem_attachWith h
@[simp] theorem pmap_attach {xs : Array α} {p : {x // x xs} Prop} {f : a, p a β} (H) :
@[simp, grind =] theorem pmap_attach {xs : Array α} {p : {x // x xs} Prop} {f : a, p a β} (H) :
pmap f xs.attach H =
xs.pmap (P := fun a => h : a xs, p a, h)
(fun a h => f a, h.1 h.2) (fun a h => h, H a, h (by simp)) := by
ext <;> simp
@[simp] theorem pmap_attachWith {xs : Array α} {p : {x // q x} Prop} {f : a, p a β} (H₁ H₂) :
@[simp, grind =] theorem pmap_attachWith {xs : Array α} {p : {x // q x} Prop} {f : a, p a β} (H₁ H₂) :
pmap f (xs.attachWith q H₁) H₂ =
xs.pmap (P := fun a => h : q a, p a, h)
(fun a h => f a, h.1 h.2) (fun a h => H₁ _ h, H₂ a, H₁ _ h (by simpa)) := by
ext <;> simp
@[grind =]
theorem foldl_pmap {xs : Array α} {P : α Prop} {f : (a : α) P a β}
(H : (a : α), a xs P a) (g : γ β γ) (x : γ) :
(xs.pmap f H).foldl g x = xs.attach.foldl (fun acc a => g acc (f a.1 (H _ a.2))) x := by
rw [pmap_eq_map_attach, foldl_map]
@[grind =]
theorem foldr_pmap {xs : Array α} {P : α Prop} {f : (a : α) P a β}
(H : (a : α), a xs P a) (g : β γ γ) (x : γ) :
(xs.pmap f H).foldr g x = xs.attach.foldr (fun a acc => g (f a.1 (H _ a.2)) acc) x := by
@@ -333,7 +345,7 @@ theorem foldl_attach {xs : Array α} {f : β → α → β} {b : β} :
xs.attach.foldl (fun acc t => f acc t.1) b = xs.foldl f b := by
rcases xs with xs
simp only [List.attach_toArray, List.attachWith_mem_toArray, List.size_toArray,
List.foldl_toArray', List.mem_toArray, List.foldl_subtype]
List.foldl_toArray', mem_toArray, List.foldl_subtype]
congr
ext
simpa using fun a => List.mem_of_getElem? a
@@ -352,23 +364,25 @@ theorem foldr_attach {xs : Array α} {f : α → β → β} {b : β} :
xs.attach.foldr (fun t acc => f t.1 acc) b = xs.foldr f b := by
rcases xs with xs
simp only [List.attach_toArray, List.attachWith_mem_toArray, List.size_toArray,
List.foldr_toArray', List.mem_toArray, List.foldr_subtype]
List.foldr_toArray', mem_toArray, List.foldr_subtype]
congr
ext
simpa using fun a => List.mem_of_getElem? a
@[grind =]
theorem attach_map {xs : Array α} {f : α β} :
(xs.map f).attach = xs.attach.map (fun x, h => f x, mem_map_of_mem h) := by
cases xs
ext <;> simp
@[grind =]
theorem attachWith_map {xs : Array α} {f : α β} {P : β Prop} (H : (b : β), b xs.map f P b) :
(xs.map f).attachWith P H = (xs.attachWith (P f) (fun _ h => H _ (mem_map_of_mem h))).map
fun x, h => f x, h := by
cases xs
simp [List.attachWith_map]
@[simp] theorem map_attachWith {xs : Array α} {P : α Prop} {H : (a : α), a xs P a}
@[simp, grind =] theorem map_attachWith {xs : Array α} {P : α Prop} {H : (a : α), a xs P a}
{f : { x // P x } β} :
(xs.attachWith P H).map f = xs.attach.map fun x, h => f x, H _ h := by
cases xs <;> simp_all
@@ -386,6 +400,9 @@ theorem map_attach_eq_pmap {xs : Array α} {f : { x // x ∈ xs } → β} :
cases xs
ext <;> simp
@[deprecated map_attach_eq_pmap (since := "2025-02-09")]
abbrev map_attach := @map_attach_eq_pmap
@[grind =]
theorem attach_filterMap {xs : Array α} {f : α Option β} :
(xs.filterMap f).attach = xs.attach.filterMap
@@ -421,6 +438,7 @@ theorem filter_attachWith {q : α → Prop} {xs : Array α} {p : {x // q x} →
cases xs
simp [Function.comp_def, List.filter_map]
@[grind =]
theorem pmap_pmap {p : α Prop} {q : β Prop} {g : a, p a β} {f : b, q b γ} {xs} (H₁ H₂) :
pmap f (pmap g xs H₁) H₂ =
pmap (α := { x // x xs }) (fun a h => f (g a h) (H₂ (g a h) (mem_pmap_of_mem a.2))) xs.attach
@@ -428,7 +446,7 @@ theorem pmap_pmap {p : α → Prop} {q : β → Prop} {g : ∀ a, p a → β} {f
cases xs
simp [List.pmap_pmap, List.pmap_map]
@[simp] theorem pmap_append {p : ι Prop} {f : a : ι, p a α} {xs ys : Array ι}
@[simp, grind =] theorem pmap_append {p : ι Prop} {f : a : ι, p a α} {xs ys : Array ι}
(h : a xs ++ ys, p a) :
(xs ++ ys).pmap f h =
(xs.pmap f fun a ha => h a (mem_append_left ys ha)) ++
@@ -443,7 +461,7 @@ theorem pmap_append' {p : α → Prop} {f : ∀ a : α, p a → β} {xs ys : Arr
xs.pmap f h₁ ++ ys.pmap f h₂ :=
pmap_append _
@[simp] theorem attach_append {xs ys : Array α} :
@[simp, grind =] theorem attach_append {xs ys : Array α} :
(xs ++ ys).attach = xs.attach.map (fun x, h => x, mem_append_left ys h) ++
ys.attach.map fun x, h => x, mem_append_right xs h := by
cases xs
@@ -451,59 +469,62 @@ theorem pmap_append' {p : α → Prop} {f : ∀ a : α, p a → β} {xs ys : Arr
rw [attach_congr (List.append_toArray _ _)]
simp [List.attach_append, Function.comp_def]
@[simp] theorem attachWith_append {P : α Prop} {xs ys : Array α}
@[simp, grind =] theorem attachWith_append {P : α Prop} {xs ys : Array α}
{H : (a : α), a xs ++ ys P a} :
(xs ++ ys).attachWith P H = xs.attachWith P (fun a h => H a (mem_append_left ys h)) ++
ys.attachWith P (fun a h => H a (mem_append_right xs h)) := by
simp [attachWith]
@[simp] theorem pmap_reverse {P : α Prop} {f : (a : α) P a β} {xs : Array α}
@[simp, grind =] theorem pmap_reverse {P : α Prop} {f : (a : α) P a β} {xs : Array α}
(H : (a : α), a xs.reverse P a) :
xs.reverse.pmap f H = (xs.pmap f (fun a h => H a (by simpa using h))).reverse := by
induction xs <;> simp_all
@[grind =]
theorem reverse_pmap {P : α Prop} {f : (a : α) P a β} {xs : Array α}
(H : (a : α), a xs P a) :
(xs.pmap f H).reverse = xs.reverse.pmap f (fun a h => H a (by simpa using h)) := by
rw [pmap_reverse]
@[simp] theorem attachWith_reverse {P : α Prop} {xs : Array α}
@[simp, grind =] theorem attachWith_reverse {P : α Prop} {xs : Array α}
{H : (a : α), a xs.reverse P a} :
xs.reverse.attachWith P H =
(xs.attachWith P (fun a h => H a (by simpa using h))).reverse := by
cases xs
simp
@[grind =]
theorem reverse_attachWith {P : α Prop} {xs : Array α}
{H : (a : α), a xs P a} :
(xs.attachWith P H).reverse = (xs.reverse.attachWith P (fun a h => H a (by simpa using h))) := by
cases xs
simp
@[simp] theorem attach_reverse {xs : Array α} :
@[simp, grind =] theorem attach_reverse {xs : Array α} :
xs.reverse.attach = xs.attach.reverse.map fun x, h => x, by simpa using h := by
cases xs
rw [attach_congr List.reverse_toArray]
simp
@[grind =]
theorem reverse_attach {xs : Array α} :
xs.attach.reverse = xs.reverse.attach.map fun x, h => x, by simpa using h := by
cases xs
simp
@[simp] theorem back?_pmap {P : α Prop} {f : (a : α) P a β} {xs : Array α}
@[simp, grind =] theorem back?_pmap {P : α Prop} {f : (a : α) P a β} {xs : Array α}
(H : (a : α), a xs P a) :
(xs.pmap f H).back? = xs.attach.back?.map fun a, m => f a (H a m) := by
cases xs
simp
@[simp] theorem back?_attachWith {P : α Prop} {xs : Array α}
@[simp, grind =] theorem back?_attachWith {P : α Prop} {xs : Array α}
{H : (a : α), a xs P a} :
(xs.attachWith P H).back? = xs.back?.pbind (fun a h => some a, H _ (mem_of_back? h)) := by
cases xs
simp
@[simp]
@[simp, grind =]
theorem back?_attach {xs : Array α} :
xs.attach.back? = xs.back?.pbind fun a h => some a, mem_of_back? h := by
cases xs
@@ -572,6 +593,9 @@ def unattach {α : Type _} {p : α → Prop} (xs : Array { x // p x }) : Array
@[simp] theorem unattach_empty {p : α Prop} : (#[] : Array { x // p x }).unattach = #[] := by
simp [unattach]
@[deprecated unattach_empty (since := "2025-05-26")]
abbrev unattach_nil := @unattach_empty
@[simp] theorem unattach_push {p : α Prop} {a : { x // p x }} {xs : Array { x // p x }} :
(xs.push a).unattach = xs.unattach.push a.1 := by
simp only [unattach, Array.map_push]
@@ -682,7 +706,7 @@ and simplifies these to the function directly taking the value.
{f : { x // p x } Array β} {g : α Array β} (hf : x h, f x, h = g x) :
(xs.flatMap f) = xs.unattach.flatMap g := by
cases xs
simp only [List.flatMap_toArray, List.unattach_toArray,
simp only [List.flatMap_toArray, List.unattach_toArray,
mk.injEq]
rw [List.flatMap_subtype]
simp [hf]
@@ -746,6 +770,9 @@ and simplifies these to the function directly taking the value.
(Array.replicate n x).unattach = Array.replicate n x.1 := by
simp [unattach]
@[deprecated unattach_replicate (since := "2025-03-18")]
abbrev unattach_mkArray := @unattach_replicate
/-! ### Well-founded recursion preprocessing setup -/
@[wf_preprocess] theorem map_wfParam {xs : Array α} {f : α β} :

View File

@@ -6,6 +6,12 @@ Authors: Leonardo de Moura
module
prelude
public import Init.WFTactics
public import Init.Data.Nat.Basic
public import Init.Data.Fin.Basic
public import Init.Data.UInt.BasicAux
public import Init.Data.Repr
public import Init.Data.ToString.Basic
public import Init.GetElem
public import Init.Data.List.ToArrayImpl
import all Init.Data.List.ToArrayImpl
@@ -36,11 +42,11 @@ namespace Array
/-! ### Preliminary theorems -/
@[simp, grind =] theorem size_set {xs : Array α} {i : Nat} {v : α} (h : i < xs.size) :
@[simp, grind] theorem size_set {xs : Array α} {i : Nat} {v : α} (h : i < xs.size) :
(set xs i v h).size = xs.size :=
List.length_set ..
@[simp, grind =] theorem size_push {xs : Array α} (v : α) : (push xs v).size = xs.size + 1 :=
@[simp, grind] theorem size_push {xs : Array α} (v : α) : (push xs v).size = xs.size + 1 :=
List.length_concat ..
theorem ext {xs ys : Array α}
@@ -104,19 +110,13 @@ instance : Membership α (Array α) where
theorem mem_def {a : α} {as : Array α} : a as a as.toList :=
fun | .mk h => h, Array.Mem.mk
@[simp, grind =] theorem _root_.List.mem_toArray {a : α} {l : List α} : a l.toArray a l := by
@[simp, grind =] theorem mem_toArray {a : α} {l : List α} : a l.toArray a l := by
simp [mem_def]
@[deprecated List.mem_toArray (since := "2025-09-04")]
theorem mem_toArray {a : α} {l : List α} : a l.toArray a l :=
List.mem_toArray
@[simp] theorem getElem_mem {xs : Array α} {i : Nat} (h : i < xs.size) : xs[i] xs := by
@[simp, grind] theorem getElem_mem {xs : Array α} {i : Nat} (h : i < xs.size) : xs[i] xs := by
rw [Array.mem_def, getElem_toList]
apply List.getElem_mem
grind_pattern getElem_mem => xs[i] xs
@[simp, grind =] theorem emptyWithCapacity_eq {α n} : @emptyWithCapacity α n = #[] := rfl
@[simp] theorem mkEmpty_eq {α n} : @mkEmpty α n = #[] := rfl
@@ -125,10 +125,19 @@ end Array
namespace List
@[deprecated Array.toArray_toList (since := "2025-02-17")]
abbrev toArray_toList := @Array.toArray_toList
-- This does not need to be a simp lemma, as already after the `whnfR` the right hand side is `as`.
theorem toList_toArray {as : List α} : as.toArray.toList = as := rfl
@[simp, grind =] theorem size_toArray {as : List α} : as.toArray.size = as.length := by simp [Array.size]
@[deprecated toList_toArray (since := "2025-02-17")]
abbrev _root_.Array.toList_toArray := @List.toList_toArray
@[simp, grind] theorem size_toArray {as : List α} : as.toArray.size = as.length := by simp [Array.size]
@[deprecated size_toArray (since := "2025-02-17")]
abbrev _root_.Array.size_toArray := @List.size_toArray
@[simp, grind =] theorem getElem_toArray {xs : List α} {i : Nat} (h : i < xs.toArray.size) :
xs.toArray[i] = xs[i]'(by simpa using h) := rfl
@@ -155,7 +164,7 @@ This is a low-level version of `Array.size` that directly queries the runtime sy
representation of arrays. While this is not provable, `Array.usize` always returns the exact size of
the array since the implementation only supports arrays of size less than `USize.size`.
-/
@[extern "lean_array_size", simp, expose]
@[extern "lean_array_size", simp]
def usize (xs : @& Array α) : USize := xs.size.toUSize
/--
@@ -190,7 +199,7 @@ Examples:
def pop (xs : Array α) : Array α where
toList := xs.toList.dropLast
@[simp, grind =] theorem size_pop {xs : Array α} : xs.pop.size = xs.size - 1 := by
@[simp, grind] theorem size_pop {xs : Array α} : xs.pop.size = xs.size - 1 := by
match xs with
| [] => rfl
| a::as => simp [pop, Nat.succ_sub_succ_eq_sub, size]
@@ -209,6 +218,20 @@ Examples:
def replicate {α : Type u} (n : Nat) (v : α) : Array α where
toList := List.replicate n v
/--
Creates an array that contains `n` repetitions of `v`.
The corresponding `List` function is `List.replicate`.
Examples:
* `Array.mkArray 2 true = #[true, true]`
* `Array.mkArray 3 () = #[(), (), ()]`
* `Array.mkArray 0 "anything" = #[]`
-/
@[extern "lean_mk_array", deprecated replicate (since := "2025-03-18")]
def mkArray {α : Type u} (n : Nat) (v : α) : Array α where
toList := List.replicate n v
/--
Swaps two elements of an array. The modification is performed in-place when the reference to the
array is unique.
@@ -226,7 +249,7 @@ def swap (xs : Array α) (i j : @& Nat) (hi : i < xs.size := by get_elem_tactic)
let xs' := xs.set i v₂
xs'.set j v₁ (Nat.lt_of_lt_of_eq hj (size_set _).symm)
@[simp, grind =] theorem size_swap {xs : Array α} {i j : Nat} {hi hj} : (xs.swap i j hi hj).size = xs.size := by
@[simp] theorem size_swap {xs : Array α} {i j : Nat} {hi hj} : (xs.swap i j hi hj).size = xs.size := by
change ((xs.set i xs[j]).set j xs[i]
(Nat.lt_of_lt_of_eq hj (size_set _).symm)).size = xs.size
rw [size_set, size_set]
@@ -242,7 +265,7 @@ Examples:
* `#["red", "green", "blue", "brown"].swapIfInBounds 0 4 = #["red", "green", "blue", "brown"]`
* `#["red", "green", "blue", "brown"].swapIfInBounds 9 2 = #["red", "green", "blue", "brown"]`
-/
@[extern "lean_array_swap", expose]
@[extern "lean_array_swap", grind]
def swapIfInBounds (xs : Array α) (i j : @& Nat) : Array α :=
if h₁ : i < xs.size then
if h₂ : j < xs.size then swap xs i j
@@ -385,6 +408,10 @@ that requires a proof the array is non-empty.
def back? (xs : Array α) : Option α :=
xs[xs.size - 1]?
@[deprecated "Use `a[i]?` instead." (since := "2025-02-12"), expose]
def get? (xs : Array α) (i : Nat) : Option α :=
if h : i < xs.size then some xs[i] else none
/--
Swaps a new element with the element at the given index.
@@ -416,7 +443,7 @@ def swapAt! (xs : Array α) (i : Nat) (v : α) : α × Array α :=
swapAt xs i v
else
have : Inhabited (α × Array α) := (v, xs)
panic! String.Internal.append (String.Internal.append "index " (toString i)) " out of bounds"
panic! ("index " ++ toString i ++ " out of bounds")
/--
Returns the first `n` elements of an array. The resulting array is produced by repeatedly calling
@@ -448,7 +475,7 @@ Examples:
-/
abbrev take (xs : Array α) (i : Nat) : Array α := extract xs 0 i
@[simp, grind =] theorem take_eq_extract {xs : Array α} {i : Nat} : xs.take i = xs.extract 0 i := rfl
@[simp] theorem take_eq_extract {xs : Array α} {i : Nat} : xs.take i = xs.extract 0 i := rfl
/--
Removes the first `i` elements of `xs`. If `xs` has fewer than `i` elements, the new array is empty.
@@ -462,7 +489,7 @@ Examples:
-/
abbrev drop (xs : Array α) (i : Nat) : Array α := extract xs i xs.size
@[simp, grind =] theorem drop_eq_extract {xs : Array α} {i : Nat} : xs.drop i = xs.extract i xs.size := rfl
@[simp] theorem drop_eq_extract {xs : Array α} {i : Nat} : xs.drop i = xs.extract i xs.size := rfl
@[inline]
unsafe def modifyMUnsafe [Monad m] (xs : Array α) (i : Nat) (f : α m α) : m (Array α) := do
@@ -570,7 +597,7 @@ protected def forIn' {α : Type u} {β : Type v} {m : Type v → Type w} [Monad
| ForInStep.yield b => loop i (Nat.le_of_lt h') b
loop as.size (Nat.le_refl _) b
instance [Monad m] : ForIn' m (Array α) α inferInstance where
instance : ForIn' m (Array α) α inferInstance where
forIn' := Array.forIn'
-- No separate `ForIn` instance is required because it can be derived from `ForIn'`.
@@ -589,8 +616,6 @@ unsafe def foldlMUnsafe {α : Type u} {β : Type v} {m : Type v → Type w} [Mon
if start < stop then
if stop as.size then
fold (USize.ofNat start) (USize.ofNat stop) init
else if start < as.size then
fold (USize.ofNat start) (USize.ofNat as.size) init
else
pure init
else
@@ -736,7 +761,8 @@ of results.
def mapM {α : Type u} {β : Type v} {m : Type v Type w} [Monad m] (f : α m β) (as : Array α) : m (Array β) :=
-- Note: we cannot use `foldlM` here for the reference implementation because this calls
-- `bind` and `pure` too many times. (We are not assuming `m` is a `LawfulMonad`)
let rec map (i : Nat) (bs : Array β) : m (Array β) := do
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
map (i : Nat) (bs : Array β) : m (Array β) := do
if hlt : i < as.size then
map (i+1) (bs.push ( f as[i]))
else
@@ -896,7 +922,8 @@ entire array is checked.
@[implemented_by anyMUnsafe, expose]
def anyM {α : Type u} {m : Type Type w} [Monad m] (p : α m Bool) (as : Array α) (start := 0) (stop := as.size) : m Bool :=
let any (stop : Nat) (h : stop as.size) :=
let rec loop (j : Nat) : m Bool := do
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
loop (j : Nat) : m Bool := do
if hlt : j < stop then
have : j < as.size := Nat.lt_of_lt_of_le hlt h
if ( p as[j]) then
@@ -1003,7 +1030,7 @@ unless `start < stop`. By default, the entire array is used.
protected def forM {α : Type u} {m : Type v Type w} [Monad m] (f : α m PUnit) (as : Array α) (start := 0) (stop := as.size) : m PUnit :=
as.foldlM (fun _ => f) start stop
instance [Monad m] : ForM m (Array α) α where
instance : ForM m (Array α) α where
forM xs f := Array.forM f xs
-- We simplify `Array.forM` to `forM`.
@@ -1234,7 +1261,8 @@ Examples:
-/
@[inline, expose]
def findIdx? {α : Type u} (p : α Bool) (as : Array α) : Option Nat :=
let rec loop (j : Nat) :=
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
loop (j : Nat) :=
if h : j < as.size then
if p as[j] then some j else loop (j + 1)
else none
@@ -1251,7 +1279,8 @@ Examples:
-/
@[inline]
def findFinIdx? {α : Type u} (p : α Bool) (as : Array α) : Option (Fin as.size) :=
let rec loop (j : Nat) :=
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
loop (j : Nat) :=
if h : j < as.size then
if p as[j] then some j, h else loop (j + 1)
else none
@@ -1287,6 +1316,7 @@ Examples:
@[inline, expose]
def findIdx (p : α Bool) (as : Array α) : Nat := (as.findIdx? p).getD as.size
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
def idxOfAux [BEq α] (xs : Array α) (v : α) (i : Nat) : Option (Fin xs.size) :=
if h : i < xs.size then
if xs[i] == v then some i, h
@@ -1297,7 +1327,7 @@ decreasing_by simp_wf; decreasing_trivial_pre_omega
/--
Returns the index of the first element equal to `a`, or `none` if no element is equal
Returns the index of the first element equal to `a`, or the size of the array if no element is equal
to `a`. The index is returned as a `Fin`, which guarantees that it is in bounds.
Examples:
@@ -1350,7 +1380,7 @@ Examples:
* `#[2, 4, 5, 6].any (· % 2 = 0) = true`
* `#[2, 4, 5, 6].any (· % 2 = 1) = true`
-/
@[inline, expose, suggest_for Array.some]
@[inline, expose]
def any (as : Array α) (p : α Bool) (start := 0) (stop := as.size) : Bool :=
Id.run <| as.anyM (pure <| p ·) start stop
@@ -1368,7 +1398,7 @@ Examples:
* `#[2, 4, 6].all (· % 2 = 0) = true`
* `#[2, 4, 5, 6].all (· % 2 = 0) = false`
-/
@[inline, suggest_for Array.every]
@[inline]
def all (as : Array α) (p : α Bool) (start := 0) (stop := as.size) : Bool :=
Id.run <| as.allM (pure <| p ·) start stop
@@ -1696,6 +1726,7 @@ Examples:
* `#[3, 2, 3, 4].popWhile (· > 2) = #[3, 2]`
* `(#[] : Array Nat).popWhile (· > 2) = #[]`
-/
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
def popWhile (p : α Bool) (as : Array α) : Array α :=
if h : as.size > 0 then
if p (as[as.size - 1]'(Nat.sub_lt h (by decide))) then
@@ -1706,7 +1737,7 @@ def popWhile (p : α → Bool) (as : Array α) : Array α :=
as
decreasing_by simp_wf; decreasing_trivial_pre_omega
@[simp, grind =] theorem popWhile_empty {p : α Bool} :
@[simp] theorem popWhile_empty {p : α Bool} :
popWhile p #[] = #[] := by
simp [popWhile]
@@ -1720,7 +1751,8 @@ Examples:
* `#[0, 1, 2, 3, 2, 1].takeWhile (· < 0) = #[]`
-/
def takeWhile (p : α Bool) (as : Array α) : Array α :=
let rec go (i : Nat) (acc : Array α) : Array α :=
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
go (i : Nat) (acc : Array α) : Array α :=
if h : i < as.size then
let a := as[i]
if p a then
@@ -1743,6 +1775,7 @@ Examples:
* `#["apple", "pear", "orange"].eraseIdx 1 = #["apple", "orange"]`
* `#["apple", "pear", "orange"].eraseIdx 2 = #["apple", "pear"]`
-/
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
def eraseIdx (xs : Array α) (i : Nat) (h : i < xs.size := by get_elem_tactic) : Array α :=
if h' : i + 1 < xs.size then
let xs' := xs.swap (i + 1) i
@@ -1753,8 +1786,7 @@ termination_by xs.size - i
decreasing_by simp_wf; exact Nat.sub_succ_lt_self _ _ h
-- This is required in `Lean.Data.PersistentHashMap`.
@[simp, grind =]
theorem size_eraseIdx {xs : Array α} (i : Nat) (h) : (xs.eraseIdx i h).size = xs.size - 1 := by
@[simp] theorem size_eraseIdx {xs : Array α} (i : Nat) (h) : (xs.eraseIdx i h).size = xs.size - 1 := by
induction xs, i, h using Array.eraseIdx.induct with
| @case1 xs i h h' xs' ih =>
unfold eraseIdx
@@ -1776,6 +1808,7 @@ Examples:
* `#["apple", "pear", "orange"].eraseIdxIfInBounds 3 = #["apple", "pear", "orange"]`
* `#["apple", "pear", "orange"].eraseIdxIfInBounds 5 = #["apple", "pear", "orange"]`
-/
@[grind]
def eraseIdxIfInBounds (xs : Array α) (i : Nat) : Array α :=
if h : i < xs.size then xs.eraseIdx i h else xs
@@ -1838,7 +1871,8 @@ Examples:
* `#["tues", "thur", "sat"].insertIdx 3 "wed" = #["tues", "thur", "sat", "wed"]`
-/
@[inline] def insertIdx (as : Array α) (i : Nat) (a : α) (_ : i as.size := by get_elem_tactic) : Array α :=
let rec loop (as : Array α) (j : Fin as.size) :=
let rec @[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
loop (as : Array α) (j : Fin as.size) :=
if i < j then
let j' : Fin as.size := j-1, Nat.lt_of_le_of_lt (Nat.pred_le _) j.2
let as := as.swap j' j
@@ -1892,6 +1926,7 @@ def insertIdxIfInBounds (as : Array α) (i : Nat) (a : α) : Array α :=
else
as
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
def isPrefixOfAux [BEq α] (as bs : Array α) (hle : as.size bs.size) (i : Nat) : Bool :=
if h : i < as.size then
let a := as[i]
@@ -1920,7 +1955,7 @@ def isPrefixOf [BEq α] (as bs : Array α) : Bool :=
else
false
@[specialize]
@[semireducible, specialize] -- This is otherwise irreducible because it uses well-founded recursion.
def zipWithMAux {m : Type v Type w} [Monad m] (as : Array α) (bs : Array β) (f : α β m γ) (i : Nat) (cs : Array γ) : m (Array γ) := do
if h : i < as.size then
let a := as[i]
@@ -2083,6 +2118,7 @@ private def allDiffAuxAux [BEq α] (as : Array α) (a : α) : forall (i : Nat),
have : i < as.size := Nat.lt_trans (Nat.lt_succ_self _) h;
a != as[i] && allDiffAuxAux as a i this
@[semireducible] -- This is otherwise irreducible because it uses well-founded recursion.
private def allDiffAux [BEq α] (as : Array α) (i : Nat) : Bool :=
if h : i < as.size then
allDiffAuxAux as as[i] i h && allDiffAux as (i+1)
@@ -2133,6 +2169,8 @@ instance {α : Type u} [Repr α] : Repr (Array α) where
reprPrec xs _ := Array.repr xs
instance [ToString α] : ToString (Array α) where
toString xs := String.Internal.append "#" (toString xs.toList)
toString xs := "#" ++ toString xs.toList
end Array
export Array (mkArray)

View File

@@ -6,8 +6,10 @@ Authors: Leonardo de Moura
module
prelude
public import Init.Data.Array.Basic
import all Init.Data.Array.Basic
public import Init.Data.Nat.Linear
public import Init.NotationExtra
public section

View File

@@ -6,7 +6,9 @@ Authors: Leonardo de Moura
module
prelude
public import Init.Data.Array.Basic
public import Init.Data.Int.DivMod.Lemmas
public import Init.Omega
public section
universe u v

View File

@@ -8,6 +8,7 @@ module
prelude
public import Init.Data.List.TakeDrop
public import Init.Data.Array.Basic
import all Init.Data.Array.Basic
public section
@@ -23,6 +24,29 @@ set_option linter.indexVariables true -- Enforce naming conventions for index va
namespace Array
/--
Use the indexing notation `a[i]` instead.
Access an element from an array without needing a runtime bounds checks,
using a `Nat` index and a proof that it is in bounds.
This function does not use `get_elem_tactic` to automatically find the proof that
the index is in bounds. This is because the tactic itself needs to look up values in
arrays.
-/
@[deprecated "Use indexing notation `as[i]` instead" (since := "2025-02-17")]
def get {α : Type u} (xs : @& Array α) (i : @& Nat) (h : LT.lt i xs.size) : α :=
xs.toList.get i, h
/--
Use the indexing notation `a[i]!` instead.
Access an element from an array, or panic if the index is out of bounds.
-/
@[deprecated "Use indexing notation `as[i]!` instead" (since := "2025-02-17"), expose]
def get! {α : Type u} [Inhabited α] (xs : @& Array α) (i : @& Nat) : α :=
Array.getD xs i default
theorem foldlM_toList.aux [Monad m]
{f : β α m β} {xs : Array α} {i j} (H : xs.size i + j) {b} :
foldlM.loop f xs xs.size (Nat.le_refl _) i j b = (xs.toList.drop j).foldlM f b := by
@@ -31,7 +55,7 @@ theorem foldlM_toList.aux [Monad m]
· cases Nat.not_le_of_gt _ (Nat.zero_add _ H)
· rename_i i; rw [Nat.succ_add] at H
simp [foldlM_toList.aux (j := j+1) H]
rw (occs := [2]) [ List.getElem_cons_drop _]
rw (occs := [2]) [ List.getElem_cons_drop_succ_eq_drop _]
simp
· rw [List.drop_of_length_le (Nat.ge_of_not_lt _)]; simp
@@ -73,6 +97,9 @@ theorem foldrM_eq_reverse_foldlM_toList [Monad m] {f : α → β → m β} {init
rcases xs with xs
simp [push, List.concat_eq_append]
@[deprecated toList_push (since := "2025-05-26")]
abbrev push_toList := @toList_push
@[simp, grind =] theorem toListAppend_eq {xs : Array α} {l : List α} : xs.toListAppend l = xs.toList ++ l := by
simp [toListAppend, foldr_toList]
@@ -81,6 +108,9 @@ theorem foldrM_eq_reverse_foldlM_toList [Monad m] {f : α → β → m β} {init
@[simp, grind =] theorem toList_pop {xs : Array α} : xs.pop.toList = xs.toList.dropLast := rfl
@[deprecated toList_pop (since := "2025-02-17")]
abbrev pop_toList := @Array.toList_pop
@[simp] theorem append_eq_append {xs ys : Array α} : xs.append ys = xs ++ ys := rfl
@[simp, grind =] theorem toList_append {xs ys : Array α} :
@@ -97,15 +127,9 @@ theorem foldrM_eq_reverse_foldlM_toList [Monad m] {f : α → β → m β} {init
@[simp, grind =] theorem empty_append {xs : Array α} : #[] ++ xs = xs := by
apply ext'; simp only [toList_append, List.nil_append]
@[simp] theorem append_assoc {xs ys zs : Array α} : xs ++ ys ++ zs = xs ++ (ys ++ zs) := by
@[simp, grind _=_] theorem append_assoc {xs ys zs : Array α} : xs ++ ys ++ zs = xs ++ (ys ++ zs) := by
apply ext'; simp only [toList_append, List.append_assoc]
grind_pattern append_assoc => (xs ++ ys) ++ zs where
xs =/= #[]; ys =/= #[]; zs =/= #[]
grind_pattern append_assoc => xs ++ (ys ++ zs) where
xs =/= #[]; ys =/= #[]; zs =/= #[]
@[simp] theorem appendList_eq_append {xs : Array α} {l : List α} : xs.appendList l = xs ++ l := rfl
@[simp, grind =] theorem toList_appendList {xs : Array α} {l : List α} :
@@ -113,4 +137,6 @@ grind_pattern append_assoc => xs ++ (ys ++ zs) where
rw [ appendList_eq_append]; unfold Array.appendList
induction l generalizing xs <;> simp [*]
end Array

View File

@@ -6,6 +6,7 @@ Authors: Kim Morrison
module
prelude
public import Init.Data.Array.Basic
import all Init.Data.Array.Basic
public import Init.Data.Array.Lemmas
public import Init.Data.List.Nat.Count
@@ -62,12 +63,12 @@ theorem size_eq_countP_add_countP {xs : Array α} : xs.size = countP p xs + coun
rcases xs with xs
simp [List.length_eq_countP_add_countP (p := p)]
@[grind _=_]
theorem countP_eq_size_filter {xs : Array α} : countP p xs = (filter p xs).size := by
rcases xs with xs
simp [List.countP_eq_length_filter]
grind_pattern countP_eq_size_filter => xs.countP p, xs.filter p
@[grind =]
theorem countP_eq_size_filter' : countP p = size filter p := by
funext xs
apply countP_eq_size_filter
@@ -99,6 +100,9 @@ theorem countP_le_size : countP p xs ≤ xs.size := by
theorem countP_replicate {a : α} {n : Nat} : countP p (replicate n a) = if p a then n else 0 := by
simp [ List.toArray_replicate, List.countP_replicate]
@[deprecated countP_replicate (since := "2025-03-18")]
abbrev countP_mkArray := @countP_replicate
theorem boole_getElem_le_countP {xs : Array α} {i : Nat} (h : i < xs.size) :
(if p xs[i] then 1 else 0) xs.countP p := by
rcases xs with xs
@@ -259,9 +263,15 @@ theorem count_eq_size {xs : Array α} : count a xs = xs.size ↔ ∀ b ∈ xs, a
@[simp] theorem count_replicate_self {a : α} {n : Nat} : count a (replicate n a) = n := by
simp [ List.toArray_replicate]
@[deprecated count_replicate_self (since := "2025-03-18")]
abbrev count_mkArray_self := @count_replicate_self
theorem count_replicate {a b : α} {n : Nat} : count a (replicate n b) = if b == a then n else 0 := by
simp [ List.toArray_replicate, List.count_replicate]
@[deprecated count_replicate (since := "2025-03-18")]
abbrev count_mkArray := @count_replicate
theorem filter_beq {xs : Array α} (a : α) : xs.filter (· == a) = replicate (count a xs) a := by
rcases xs with xs
simp [List.filter_beq]
@@ -275,6 +285,9 @@ theorem replicate_count_eq_of_count_eq_size {xs : Array α} (h : count a xs = xs
rw [ toList_inj]
simp [List.replicate_count_eq_of_count_eq_length (by simpa using h)]
@[deprecated replicate_count_eq_of_count_eq_size (since := "2025-03-18")]
abbrev mkArray_count_eq_of_count_eq_size := @replicate_count_eq_of_count_eq_size
@[simp] theorem count_filter {xs : Array α} (h : p a) : count a (filter p xs) = count a xs := by
rcases xs with xs
simp [List.count_filter, h]

View File

@@ -6,9 +6,11 @@ Authors: Leonardo de Moura
module
prelude
public import Init.Data.Array.Basic
import all Init.Data.Array.Basic
public import Init.Data.BEq
public import Init.Data.List.Nat.BEq
public import Init.ByCases
public section
@@ -89,57 +91,11 @@ theorem isEqv_self_beq [BEq α] [ReflBEq α] (xs : Array α) : Array.isEqv xs xs
theorem isEqv_self [DecidableEq α] (xs : Array α) : Array.isEqv xs xs (· = ·) = true := by
simp [isEqv, isEqvAux_self]
def instDecidableEqImpl [DecidableEq α] : DecidableEq (Array α) := fun xs ys =>
match h:isEqv xs ys (fun a b => a = b) with
| true => isTrue (eq_of_isEqv xs ys h)
| false => isFalse (by subst ·; rw [isEqv_self] at h; contradiction)
instance instDecidableEq [DecidableEq α] : DecidableEq (Array α) := fun xs ys =>
match xs with
| [] =>
match ys with
| [] => isTrue rfl
| _ :: _ => isFalse (fun h => Array.noConfusion rfl (heq_of_eq h) (fun h => List.noConfusion rfl h))
| a :: as =>
match ys with
| [] => isFalse (fun h => Array.noConfusion rfl (heq_of_eq h) (fun h => List.noConfusion rfl h))
| b :: bs => instDecidableEqImpl a :: as b :: bs
@[csimp]
theorem instDecidableEq_csimp : @instDecidableEq = @instDecidableEqImpl :=
Subsingleton.allEq _ _
/--
Equality with `#[]` is decidable even if the underlying type does not have decidable equality.
-/
instance instDecidableEqEmp (xs : Array α) : Decidable (xs = #[]) :=
match xs with
| [] => isTrue rfl
| _ :: _ => isFalse (fun h => Array.noConfusion rfl (heq_of_eq h) (fun h => List.noConfusion rfl h))
/--
Equality with `#[]` is decidable even if the underlying type does not have decidable equality.
-/
instance instDecidableEmpEq (ys : Array α) : Decidable (#[] = ys) :=
match ys with
| [] => isTrue rfl
| _ :: _ => isFalse (fun h => Array.noConfusion rfl (heq_of_eq h) (fun h => List.noConfusion rfl h))
@[inline]
def instDecidableEqEmpImpl (xs : Array α) : Decidable (xs = #[]) :=
decidable_of_iff xs.isEmpty <| by rcases xs with <;> simp [Array.isEmpty]
@[inline]
def instDecidableEmpEqImpl (xs : Array α) : Decidable (#[] = xs) :=
decidable_of_iff xs.isEmpty <| by rcases xs with <;> simp [Array.isEmpty]
@[csimp]
theorem instDecidableEqEmp_csimp : @instDecidableEqEmp = @instDecidableEqEmpImpl :=
Subsingleton.allEq _ _
@[csimp]
theorem instDecidableEmpEq_csimp : @instDecidableEmpEq = @instDecidableEmpEqImpl :=
Subsingleton.allEq _ _
instance [DecidableEq α] : DecidableEq (Array α) :=
fun xs ys =>
match h:isEqv xs ys (fun a b => a = b) with
| true => isTrue (eq_of_isEqv xs ys h)
| false => isFalse fun h' => by subst h'; rw [isEqv_self] at h; contradiction
theorem beq_eq_decide [BEq α] (xs ys : Array α) :
(xs == ys) = if h : xs.size = ys.size then

View File

@@ -6,8 +6,11 @@ Authors: Kim Morrison
module
prelude
public import Init.Data.Array.Basic
import all Init.Data.Array.Basic
public import Init.Data.Array.Lemmas
public import Init.Data.List.Nat.Erase
public import Init.Data.List.Nat.Basic
public section
@@ -88,7 +91,7 @@ theorem mem_of_mem_eraseP {xs : Array α} : a ∈ xs.eraseP p → a ∈ xs := by
rcases xs with xs
simpa using List.mem_of_mem_eraseP
@[simp, grind =] theorem mem_eraseP_of_neg {xs : Array α} (pa : ¬p a) : a xs.eraseP p a xs := by
@[simp, grind] theorem mem_eraseP_of_neg {xs : Array α} (pa : ¬p a) : a xs.eraseP p a xs := by
rcases xs with xs
simpa using List.mem_eraseP_of_neg pa
@@ -139,16 +142,25 @@ theorem eraseP_replicate {n : Nat} {a : α} {p : α → Bool} :
simp only [ List.toArray_replicate, List.eraseP_toArray, List.eraseP_replicate]
split <;> simp
@[deprecated eraseP_replicate (since := "2025-03-18")]
abbrev eraseP_mkArray := @eraseP_replicate
@[simp] theorem eraseP_replicate_of_pos {n : Nat} {a : α} (h : p a) :
(replicate n a).eraseP p = replicate (n - 1) a := by
simp only [ List.toArray_replicate, List.eraseP_toArray]
simp [h]
@[deprecated eraseP_replicate_of_pos (since := "2025-03-18")]
abbrev eraseP_mkArray_of_pos := @eraseP_replicate_of_pos
@[simp] theorem eraseP_replicate_of_neg {n : Nat} {a : α} (h : ¬p a) :
(replicate n a).eraseP p = replicate n a := by
simp only [ List.toArray_replicate, List.eraseP_toArray]
simp [h]
@[deprecated eraseP_replicate_of_neg (since := "2025-03-18")]
abbrev eraseP_mkArray_of_neg := @eraseP_replicate_of_neg
theorem eraseP_eq_iff {p} {xs : Array α} :
xs.eraseP p = ys
(( a xs, ¬ p a) xs = ys)
@@ -228,7 +240,7 @@ theorem mem_of_mem_erase {a b : α} {xs : Array α} (h : a ∈ xs.erase b) : a
rcases xs with xs
simpa using List.mem_of_mem_erase (by simpa using h)
@[simp, grind =] theorem mem_erase_of_ne [LawfulBEq α] {a b : α} {xs : Array α} (ab : a b) :
@[simp, grind] theorem mem_erase_of_ne [LawfulBEq α] {a b : α} {xs : Array α} (ab : a b) :
a xs.erase b a xs :=
erase_eq_eraseP b xs mem_eraseP_of_neg (mt eq_of_beq ab.symm)
@@ -259,7 +271,7 @@ theorem erase_append [LawfulBEq α] {a : α} {xs ys : Array α} :
(xs ++ ys).erase a = if a xs then xs.erase a ++ ys else xs ++ ys.erase a := by
rcases xs with xs
rcases ys with ys
simp only [List.append_toArray, List.erase_toArray, List.erase_append, List.mem_toArray]
simp only [List.append_toArray, List.erase_toArray, List.erase_append, mem_toArray]
split <;> simp
@[grind =]
@@ -269,6 +281,9 @@ theorem erase_replicate [LawfulBEq α] {n : Nat} {a b : α} :
simp only [List.erase_replicate, beq_iff_eq, List.toArray_replicate]
split <;> simp
@[deprecated erase_replicate (since := "2025-03-18")]
abbrev erase_mkArray := @erase_replicate
-- The arguments `a b` are explicit,
-- so they can be specified to prevent `simp` repeatedly applying the lemma.
@[grind =]
@@ -296,20 +311,19 @@ theorem erase_eq_iff [LawfulBEq α] {a : α} {xs : Array α} :
simp only [ List.toArray_replicate, List.erase_toArray]
simp
@[deprecated erase_replicate_self (since := "2025-03-18")]
abbrev erase_mkArray_self := @erase_replicate_self
@[simp] theorem erase_replicate_ne [LawfulBEq α] {a b : α} (h : !b == a) :
(replicate n a).erase b = replicate n a := by
rw [erase_of_not_mem]
simp_all
@[deprecated erase_replicate_ne (since := "2025-03-18")]
abbrev erase_mkArray_ne := @erase_replicate_ne
end erase
/-! ### eraseIdxIfInBounds -/
@[grind =]
theorem eraseIdxIfInBounds_eq {xs : Array α} {i : Nat} :
xs.eraseIdxIfInBounds i = if h : i < xs.size then xs.eraseIdx i else xs := by
simp [eraseIdxIfInBounds]
/-! ### eraseIdx -/
theorem eraseIdx_eq_eraseIdxIfInBounds {xs : Array α} {i : Nat} (h : i < xs.size) :
@@ -389,6 +403,9 @@ theorem eraseIdx_append_of_size_le {xs : Array α} {k : Nat} (hk : xs.size ≤ k
simp at hk
simp [List.eraseIdx_append_of_length_le, *]
@[deprecated eraseIdx_append_of_size_le (since := "2025-06-11")]
abbrev eraseIdx_append_of_length_le := @eraseIdx_append_of_size_le
@[grind =]
theorem eraseIdx_append {xs ys : Array α} (h : k < (xs ++ ys).size) :
eraseIdx (xs ++ ys) k =
@@ -408,6 +425,9 @@ theorem eraseIdx_replicate {n : Nat} {a : α} {k : Nat} {h} :
simp only [ List.toArray_replicate, List.eraseIdx_toArray]
simp [List.eraseIdx_replicate, h]
@[deprecated eraseIdx_replicate (since := "2025-03-18")]
abbrev eraseIdx_mkArray := @eraseIdx_replicate
theorem mem_eraseIdx_iff_getElem {x : α} {xs : Array α} {k} {h} : x xs.eraseIdx k h i w, i k xs[i]'w = x := by
rcases xs with xs
simp [List.mem_eraseIdx_iff_getElem, *]

View File

@@ -7,6 +7,7 @@ module
prelude
public import Init.Data.Array.Lemmas
public import Init.Data.List.Nat.TakeDrop
public section
@@ -200,7 +201,7 @@ theorem getElem?_extract_of_succ {as : Array α} {j : Nat} :
simp [getElem?_extract]
omega
@[simp] theorem extract_extract {as : Array α} {i j k l : Nat} :
@[simp, grind =] theorem extract_extract {as : Array α} {i j k l : Nat} :
(as.extract i j).extract k l = as.extract (i + k) (min (i + l) j) := by
ext m h₁ h₂
· simp
@@ -208,9 +209,6 @@ theorem getElem?_extract_of_succ {as : Array α} {j : Nat} :
· simp only [size_extract] at h₁ h₂
simp [Nat.add_assoc]
grind_pattern extract_extract => (as.extract i j).extract k l where
as =/= #[]
theorem extract_eq_empty_of_eq_empty {as : Array α} {i j : Nat} (h : as = #[]) :
as.extract i j = #[] := by
simp [h]
@@ -292,6 +290,9 @@ theorem extract_append_right {as bs : Array α} :
· simp only [size_extract, size_replicate] at h₁ h₂
simp only [getElem_extract, getElem_replicate]
@[deprecated extract_replicate (since := "2025-03-18")]
abbrev extract_mkArray := @extract_replicate
theorem extract_eq_extract_right {as : Array α} {i j j' : Nat} :
as.extract i j = as.extract i j' min (j - i) (as.size - i) = min (j' - i) (as.size - i) := by
rcases as with as
@@ -409,6 +410,8 @@ theorem popWhile_append {xs ys : Array α} :
rcases ys with ys
simp only [List.append_toArray, List.popWhile_toArray, List.reverse_append, List.dropWhile_append,
List.isEmpty_iff, List.isEmpty_toArray, List.isEmpty_reverse]
-- Why do these not fire with `simp`?
rw [List.popWhile_toArray, List.isEmpty_toArray, List.isEmpty_reverse]
split
· rfl
· simp
@@ -427,20 +430,32 @@ theorem popWhile_append {xs ys : Array α} :
(replicate n a).takeWhile p = (replicate n a).filter p := by
simp [ List.toArray_replicate]
@[deprecated takeWhile_replicate_eq_filter (since := "2025-03-18")]
abbrev takeWhile_mkArray_eq_filter := @takeWhile_replicate_eq_filter
theorem takeWhile_replicate {p : α Bool} :
(replicate n a).takeWhile p = if p a then replicate n a else #[] := by
simp [takeWhile_replicate_eq_filter, filter_replicate]
@[deprecated takeWhile_replicate (since := "2025-03-18")]
abbrev takeWhile_mkArray := @takeWhile_replicate
@[simp] theorem popWhile_replicate_eq_filter_not {p : α Bool} :
(replicate n a).popWhile p = (replicate n a).filter (fun a => !p a) := by
simp [ List.toArray_replicate, List.filter_reverse]
@[deprecated popWhile_replicate_eq_filter_not (since := "2025-03-18")]
abbrev popWhile_mkArray_eq_filter_not := @popWhile_replicate_eq_filter_not
theorem popWhile_replicate {p : α Bool} :
(replicate n a).popWhile p = if p a then #[] else replicate n a := by
simp only [popWhile_replicate_eq_filter_not, size_replicate, filter_replicate, Bool.not_eq_eq_eq_not,
Bool.not_true]
split <;> simp_all
@[deprecated popWhile_replicate (since := "2025-03-18")]
abbrev popWhile_mkArray := @popWhile_replicate
theorem extract_takeWhile {as : Array α} {i : Nat} :
(as.takeWhile p).extract 0 i = (as.extract 0 i).takeWhile p := by
rcases as with as

View File

@@ -6,6 +6,7 @@ Authors: François G. Dorais
module
prelude
public import Init.Data.List.FinRange
public import Init.Data.Array.OfFn
public section

Some files were not shown because too many files have changed in this diff Show More