mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-21 12:24:11 +00:00
Compare commits
23 Commits
sym_replac
...
sofia/asyn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0de0b095c8 | ||
|
|
13050f6a06 | ||
|
|
ecac1916d8 | ||
|
|
59447a4070 | ||
|
|
49b5467f90 | ||
|
|
cf93a2d8e2 | ||
|
|
46af2462f2 | ||
|
|
33c77395ef | ||
|
|
3573787791 | ||
|
|
ac7e968d85 | ||
|
|
b433c6a224 | ||
|
|
048d3ef500 | ||
|
|
0143e8e35c | ||
|
|
51b1751f4e | ||
|
|
3d4f79a0fe | ||
|
|
65388f5495 | ||
|
|
7355e491da | ||
|
|
2fa13ef0f8 | ||
|
|
63fec537d3 | ||
|
|
34cfa8f554 | ||
|
|
b2f317429b | ||
|
|
1d87d38a91 | ||
|
|
448bdd3e10 |
@@ -1,47 +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.
|
||||
```
|
||||
@@ -1,70 +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.
|
||||
|
||||
## 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
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -4,7 +4,6 @@ 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.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/awaiting-manual.yml
vendored
2
.github/workflows/awaiting-manual.yml
vendored
@@ -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;
|
||||
|
||||
2
.github/workflows/awaiting-mathlib.yml
vendored
2
.github/workflows/awaiting-mathlib.yml
vendored
@@ -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;
|
||||
|
||||
9
.github/workflows/build-template.yml
vendored
9
.github/workflows/build-template.yml
vendored
@@ -3,6 +3,9 @@ name: build-template
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
check-level:
|
||||
type: string
|
||||
required: true
|
||||
config:
|
||||
type: string
|
||||
required: true
|
||||
@@ -213,21 +216,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
|
||||
if: (matrix.wasm || !matrix.cross) && (inputs.check-level >= 1 || matrix.test)
|
||||
- name: Test Summary
|
||||
uses: test-summary/action@v2
|
||||
with:
|
||||
|
||||
4
.github/workflows/check-stage0.yml
vendored
4
.github/workflows/check-stage0.yml
vendored
@@ -11,8 +11,8 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
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;
|
||||
|
||||
57
.github/workflows/check-stdlib-flags.yml
vendored
57
.github/workflows/check-stdlib-flags.yml
vendored
@@ -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');
|
||||
}
|
||||
155
.github/workflows/ci.yml
vendored
155
.github/workflows/ci.yml
vendored
@@ -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
|
||||
@@ -52,7 +56,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
# 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 | grep -oE '[0-9]+')
|
||||
|
||||
# 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*",
|
||||
@@ -241,47 +186,36 @@ jobs:
|
||||
{
|
||||
"name": "Linux Lake",
|
||||
"os": large ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
|
||||
"enabled": true,
|
||||
"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",
|
||||
},
|
||||
{
|
||||
// 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.
|
||||
"CTEST_OPTIONS": "-E 'StackOverflow|reverse-ffi|interactive|async_select_channel|9366|run/bv_'"
|
||||
},
|
||||
// exclude seriously slow/problematic tests (laketests crash)
|
||||
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
|
||||
},*/
|
||||
{
|
||||
"name": "macOS",
|
||||
"os": "macos-15-intel",
|
||||
"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*",
|
||||
@@ -292,7 +226,7 @@ jobs:
|
||||
{
|
||||
"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-sequoia-arm64-6x14" : "macos-15",
|
||||
"CMAKE_OPTIONS": "-DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
|
||||
"release": true,
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
@@ -301,16 +235,14 @@ 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\"",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-w64-windows-gnu.tar.zst",
|
||||
@@ -322,8 +254,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*",
|
||||
@@ -336,7 +267,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}"
|
||||
//}
|
||||
@@ -348,7 +279,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
|
||||
@@ -362,7 +293,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"]));
|
||||
|
||||
@@ -372,6 +303,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 }}
|
||||
@@ -387,6 +319,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 }}
|
||||
@@ -417,7 +350,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')
|
||||
@@ -430,11 +363,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8
|
||||
with:
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
@@ -459,10 +392,8 @@ jobs:
|
||||
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@v5
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Prepare Nightly Release
|
||||
@@ -480,7 +411,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@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8
|
||||
with:
|
||||
body_path: diff.md
|
||||
prerelease: true
|
||||
|
||||
7
.github/workflows/grove.yml
vendored
7
.github/workflows/grove.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/labels-from-comments.yml
vendored
2
.github/workflows/labels-from-comments.yml
vendored
@@ -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: |
|
||||
|
||||
2
.github/workflows/pr-body.yml
vendored
2
.github/workflows/pr-body.yml
vendored
@@ -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;
|
||||
|
||||
67
.github/workflows/pr-release.yml
vendored
67
.github/workflows/pr-release.yml
vendored
@@ -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@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
|
||||
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@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
|
||||
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 =
|
||||
@@ -393,7 +401,6 @@ jobs:
|
||||
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
|
||||
@@ -453,7 +460,6 @@ jobs:
|
||||
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).
|
||||
@@ -536,7 +530,6 @@ jobs:
|
||||
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
|
||||
|
||||
4
.github/workflows/pr-title.yml
vendored
4
.github/workflows/pr-title.yml
vendored
@@ -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).');
|
||||
}
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -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
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,6 +20,7 @@ tasks.json
|
||||
settings.json
|
||||
.gdb_history
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
script/__pycache__
|
||||
*.produced.out
|
||||
CMakeSettings.json
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`.
|
||||
|
||||
191
doc/dev/ffi.md
191
doc/dev/ffi.md
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
}
|
||||
],
|
||||
"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",
|
||||
|
||||
@@ -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).
|
||||
132
script/AnalyzeGrindAnnotations.lean
Normal file
132
script/AnalyzeGrindAnnotations.lean
Normal file
@@ -0,0 +1,132 @@
|
||||
/-
|
||||
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
|
||||
|
||||
-- 1. grind immediately sees `(#[] : Array α) = ([] : List α).toArray` but probably this should be hidden.
|
||||
-- 2. `Vector.toArray_empty` keys on `Array.mk []` rather than `#v[].toArray`
|
||||
-- I guess we could add `(#[].extract _ _).extract _ _` as a stop pattern.
|
||||
run_meta analyzeEMatchTheorem ``Array.extract_empty {}
|
||||
|
||||
-- Neither `Option.bind_some` nor `Option.bind_fun_some` fire, because the terms appear inside
|
||||
-- lambdas. So we get crazy things like:
|
||||
-- `fun x => ((some x).bind some).bind fun x => (some x).bind fun x => (some x).bind some`
|
||||
-- We could consider replacing `filterMap_some` with
|
||||
-- `filterMap g (filterMap f xs) = filterMap (f >=> g) xs`
|
||||
-- to avoid the lambda that `grind` struggles with, but this would require more API around the fish.
|
||||
run_meta analyzeEMatchTheorem ``Array.filterMap_some {}
|
||||
|
||||
-- Not entirely certain what is wrong here, but certainly
|
||||
-- `eq_empty_of_append_eq_empty` is firing too often.
|
||||
-- Ideally we could instantiate this is we fine `xs ++ ys` in the same equivalence class,
|
||||
-- note just as soon as we see `xs ++ ys`.
|
||||
-- I've tried removing this in https://github.com/leanprover/lean4/pull/10162
|
||||
run_meta analyzeEMatchTheorem ``Array.range'_succ {}
|
||||
|
||||
-- Perhaps the same story here.
|
||||
run_meta analyzeEMatchTheorem ``Array.range_succ {}
|
||||
|
||||
-- `zip_map_left` and `zip_map_right` are bad grind lemmas,
|
||||
-- checking if they can be removed in https://github.com/leanprover/lean4/pull/10163
|
||||
run_meta analyzeEMatchTheorem ``Array.zip_map {}
|
||||
|
||||
-- It seems crazy to me that as soon as we have `0 >>> n = 0`, we instantiate based on the
|
||||
-- pattern `0 >>> n >>> m` by substituting `0` into `0 >>> n` to produce the `0 >>> n >>> n`.
|
||||
-- I don't think any forbidden subterms can help us here. I don't know what to do. :-(
|
||||
run_meta analyzeEMatchTheorem ``Int.zero_shiftRight {}
|
||||
@@ -1,30 +1,53 @@
|
||||
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
|
||||
A simple script that inserts `module` and `@[expose] 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
|
||||
initSearchPath (← findSysroot)
|
||||
-- the list of root modules
|
||||
let mut mods := args.toArray.map (·.toName)
|
||||
|
||||
if mods.isEmpty then
|
||||
-- Determine default module(s) to run modulize on
|
||||
mods ← try
|
||||
let (elanInstall?, leanInstall?, lakeInstall?) ← Lake.findInstall?
|
||||
let config ← Lake.MonadError.runEIO <| Lake.mkLoadConfig { elanInstall?, leanInstall?, lakeInstall? }
|
||||
let some workspace ← Lake.loadWorkspace config |>.toBaseIO
|
||||
| throw <| IO.userError "failed to load Lake workspace"
|
||||
let defaultTargetModules := workspace.root.defaultTargets.flatMap fun target =>
|
||||
if let some lib := workspace.root.findLeanLib? target then
|
||||
lib.roots
|
||||
else if let some exe := workspace.root.findLeanExe? target then
|
||||
#[exe.config.root]
|
||||
else
|
||||
#[]
|
||||
pure defaultTargetModules
|
||||
catch _ =>
|
||||
pure #[]
|
||||
|
||||
-- Only submodules of `pkg` will be edited or have info reported on them
|
||||
let pkg := mods[0]!.components.head!
|
||||
|
||||
-- Load all the modules
|
||||
let imps := mods.map ({ module := · })
|
||||
let env ← importModules imps {}
|
||||
|
||||
let srcSearchPath ← getSrcSearchPath
|
||||
|
||||
for mod in env.header.moduleNames do
|
||||
if !pkg.isPrefixOf mod then
|
||||
continue
|
||||
|
||||
for path in args do
|
||||
-- Parse the input file
|
||||
let some path ← srcSearchPath.findModuleWithExt "lean" mod
|
||||
| throw <| .userError "error: failed to find source file for {mod}"
|
||||
let mut text ← IO.FS.readFile path
|
||||
let inputCtx := Parser.mkInputContext text path
|
||||
let inputCtx := Parser.mkInputContext text path.toString
|
||||
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
|
||||
@@ -34,42 +57,35 @@ def main (args : List String) : IO Unit := do
|
||||
if moduleTk?.isSome then
|
||||
continue
|
||||
|
||||
let looksMeta := mod.components.any (· ∈ [`Tactic, `Linter])
|
||||
|
||||
-- initial whitespace if empty header
|
||||
let startPos := header.raw.getPos? |>.getD parserState.pos
|
||||
|
||||
let dummyEnv ← mkEmptyEnvironment
|
||||
let (initCmd, parserState', _) :=
|
||||
Parser.parseCommand inputCtx { env := dummyEnv, options := {} } parserState msgs
|
||||
|
||||
-- insert section if any trailing command
|
||||
if !initCmd.isOfKind ``Parser.Command.eoi 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
|
||||
-- insert section if any trailing text
|
||||
if header.raw.getTrailingTailPos?.all (· < text.endPos) then
|
||||
let insertPos := header.raw.getTailPos? |>.getD startPos -- empty header
|
||||
let mut sec := if looksMeta then
|
||||
"public meta section"
|
||||
else
|
||||
"@[expose] public section"
|
||||
if !imps.isEmpty then
|
||||
sec := "\n\n" ++ sec
|
||||
if insertPos?.isNone then
|
||||
if header.raw.getTailPos?.isNone then
|
||||
sec := sec ++ "\n\n"
|
||||
text := text.extract 0 insertPos ++ sec ++ text.extract insertPos text.rawEndPos
|
||||
text := text.extract 0 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 "
|
||||
text := text.extract 0 insertPos ++ prfx ++ text.extract insertPos text.rawEndPos
|
||||
let prfx := if looksMeta then "public meta " else "public "
|
||||
text := text.extract 0 insertPos ++ prfx ++ text.extract insertPos text.endPos
|
||||
|
||||
-- insert `module` header
|
||||
let mut initText := text.extract 0 startPos
|
||||
if !initText.trim.isEmpty then
|
||||
-- If there is a header comment, preserve it and put `module` in the line after
|
||||
initText := initText.trimRight ++ "\n"
|
||||
text := initText ++ "module\n\n" ++ text.extract startPos text.rawEndPos
|
||||
text := initText ++ "module\n\n" ++ text.extract startPos text.endPos
|
||||
|
||||
IO.FS.writeFile path text
|
||||
|
||||
@@ -3,21 +3,16 @@ Copyright (c) 2023 Mario Carneiro. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Mario Carneiro, Sebastian Ullrich
|
||||
-/
|
||||
module
|
||||
|
||||
import Lean.Environment
|
||||
import Lake.CLI.Main
|
||||
import Lean.ExtraModUses
|
||||
|
||||
import Lake.CLI.Main
|
||||
import Lean.Parser.Module
|
||||
import Lake.Load.Workspace
|
||||
|
||||
/-! # Shake: A Lean import minimizer
|
||||
/-! # `lake exe shake` command
|
||||
|
||||
This command will check the current project (or a specified target module) and all dependencies for
|
||||
unused imports. This works by looking at generated `.olean` files to deduce required imports and
|
||||
ensuring that every import is used to contribute some constant or other elaboration dependency
|
||||
recorded by `recordExtraModUse` and friends.
|
||||
recorded by `recordExtraModUse`. Because recompilation is not needed this is quite fast (about 8
|
||||
seconds to check `Mathlib` and all dependencies).
|
||||
-/
|
||||
|
||||
/-- help string for the command line interface -/
|
||||
@@ -33,83 +28,13 @@ Options:
|
||||
--force
|
||||
Skips the `lake build --no-build` sanity check
|
||||
|
||||
--keep-implied
|
||||
Preserves existing imports that are implied by other imports and thus not technically needed
|
||||
anymore
|
||||
|
||||
--keep-prefix
|
||||
If an import `X` would be replaced in favor of a more specific import `X.Y...` it implies,
|
||||
preserves the original import instead. More generally, prefers inserting `import X` even if it
|
||||
was not part of the original imports as long as it was in the original transitive import closure
|
||||
of the current module.
|
||||
|
||||
--keep-public
|
||||
Preserves all `public` imports to avoid breaking changes for external downstream modules
|
||||
|
||||
--add-public
|
||||
Adds new imports as `public` if they have been in the original public closure of that module.
|
||||
In other words, public imports will not be removed from a module unless they are unused even
|
||||
in the private scope, and those that are removed will be re-added as `public` in downstream
|
||||
modules even if only needed in the private scope there. Unlike `--keep-public`, this may
|
||||
introduce breaking changes but will still limit the number of inserted imports.
|
||||
|
||||
--explain
|
||||
Gives constants explaining why each module is needed
|
||||
|
||||
--fix
|
||||
Apply the suggested fixes directly. Make sure you have a clean checkout
|
||||
before running this, so you can review the changes.
|
||||
|
||||
--gh-style
|
||||
Outputs messages that can be parsed by `gh-problem-matcher-wrap`
|
||||
|
||||
Annotations:
|
||||
The following annotations can be added to Lean files in order to configure the behavior of
|
||||
`shake`. Only the substring `shake: ` directly followed by a directive is checked for, so multiple
|
||||
directives can be mixed in one line such as `-- shake: keep-downstream, shake: keep-all`, and they
|
||||
can be surrounded by arbitrary comments such as `-- shake: keep (metaprogram output dependency)`.
|
||||
|
||||
* `module -- shake: keep-downstream`:
|
||||
Preserves this module in all (current) downstream modules, adding new imports of it if needed.
|
||||
|
||||
* `module -- shake: keep-all`:
|
||||
Preserves all existing imports in this module as is. New imports now needed because of upstream
|
||||
changes may still be added.
|
||||
|
||||
* `import X -- shake: keep`:
|
||||
Preserves this specific import in the current module. The most common use case is to preserve a
|
||||
public import that will be needed in downstream modules to make sense of the output of a
|
||||
metaprogram defined in this module. For example, if a tactic is defined that may synthesize a
|
||||
reference to a theorem when run, there is no way for `shake` to detect this by itself and the
|
||||
module of that theorem should be publicly imported and annotated with `keep` in the tactic's
|
||||
module.
|
||||
```
|
||||
public import X -- shake: keep (metaprogram output dependency)
|
||||
|
||||
...
|
||||
|
||||
elab \"my_tactic\" : tactic => do
|
||||
... mkConst ``f -- `f`, defined in `X`, may appear in the output of this tactic
|
||||
```
|
||||
"
|
||||
|
||||
open Lean
|
||||
|
||||
/-- The parsed CLI arguments. See `help` for more information -/
|
||||
structure Args where
|
||||
help : Bool := false
|
||||
keepImplied : Bool := false
|
||||
keepPrefix : Bool := false
|
||||
keepPublic : Bool := false
|
||||
addPublic : Bool := false
|
||||
force : Bool := false
|
||||
githubStyle : Bool := false
|
||||
explain : Bool := false
|
||||
trace : Bool := false
|
||||
fix : Bool := false
|
||||
/-- `<MODULE>..`: the list of root modules to check -/
|
||||
mods : Array Name := #[]
|
||||
|
||||
/-- We use `Nat` as a bitset for doing efficient set operations.
|
||||
The bit indexes will usually be a module index. -/
|
||||
structure Bitset where
|
||||
@@ -163,7 +88,7 @@ def ofImport : Lean.Import → NeedsKind
|
||||
|
||||
end NeedsKind
|
||||
|
||||
/-- Logically, a map `NeedsKind → Set ModuleIdx`, or `Set Import`. -/
|
||||
/-- Logically, a map `NeedsKind → Bitset`. -/
|
||||
structure Needs where
|
||||
pub : Bitset
|
||||
priv : Bitset
|
||||
@@ -199,20 +124,6 @@ def Needs.union (needs : Needs) (k : NeedsKind) (s : Bitset) : Needs :=
|
||||
def Needs.sub (needs : Needs) (k : NeedsKind) (s : Bitset) : Needs :=
|
||||
needs.modify k (fun s' => s' ^^^ (s' ∩ s))
|
||||
|
||||
instance : Union Needs where
|
||||
union a b := {
|
||||
pub := a.pub ∪ b.pub
|
||||
priv := a.priv ∪ b.priv
|
||||
metaPub := a.metaPub ∪ b.metaPub
|
||||
metaPriv := a.metaPriv ∪ b.metaPriv }
|
||||
|
||||
/-- The list of edits that will be applied in `--fix`. `edits[i] = (removed, added)` where:
|
||||
|
||||
* If `j ∈ removed` then we want to delete module named `j` from the imports of `i`
|
||||
* If `j ∈ added` then we want to add module index `j` to the imports of `i`.
|
||||
-/
|
||||
abbrev Edits := Std.HashMap Name (Array Import × Array Import)
|
||||
|
||||
/-- The main state of the checker, containing information on all loaded modules. -/
|
||||
structure State where
|
||||
env : Environment
|
||||
@@ -220,11 +131,10 @@ structure State where
|
||||
`transDeps[i]` is the (non-reflexive) transitive closure of `mods[i].imports`. More specifically,
|
||||
* `j ∈ transDeps[i].pub` if `i -(public import)->+ j`
|
||||
* `j ∈ transDeps[i].priv` if `i -(import ...)-> _ -(public import)->* j`
|
||||
* `j ∈ transDeps[i].priv` if `i -(import all)->+ i'` and `j ∈ transDeps[i'].pub/priv`
|
||||
* `j ∈ transDeps[i].metaPub` if `i -(public (meta)? import)->* _ -(public meta import)-> _ -(public (meta)? import)->* j`
|
||||
* `j ∈ transDeps[i].metaPriv` if `i -(meta import ...)-> _ -(public (meta)? import)->* j`
|
||||
* `j ∈ transDeps[i].metaPriv` if `i -(import ...)-> i'` and `j ∈ transDeps[i'].metaPub`
|
||||
* `j ∈ transDeps[i].metaPriv` if `i -(import all)->+ i'` and `j ∈ transDeps[i'].metaPub/metaPriv`
|
||||
* `j ∈ transDeps[i].priv` if `i -(import all)->+ -(public import ...)-> _ -(public import)->* j`
|
||||
* `j ∈ transDeps[i].metaPub` if `i -(public (meta)? import)->* _ -(public meta import)-> _ -(public (meta)? import ...)->* j`
|
||||
* `j ∈ transDeps[i].metaPriv` if `i -(meta import ...)-> _ -(public (meta)? import ...)->* j`
|
||||
* `j ∈ transDeps[i].metaPriv` if `i -(import all)->+ -(public meta import ...)-> _ -(public (meta)? import ...)->* j`
|
||||
-/
|
||||
transDeps : Array Needs := #[]
|
||||
/--
|
||||
@@ -232,10 +142,6 @@ structure State where
|
||||
changes to upstream headers.
|
||||
-/
|
||||
transDepsOrig : Array Needs := #[]
|
||||
/-- Modules that should always be preserved downstream. -/
|
||||
preserve : Needs := default
|
||||
/-- Edits to be applied to the module imports. -/
|
||||
edits : Edits := {}
|
||||
|
||||
def State.mods (s : State) := s.env.header.moduleData
|
||||
def State.modNames (s : State) := s.env.header.moduleNames
|
||||
@@ -256,10 +162,10 @@ def addTransitiveImps (transImps : Needs) (imp : Import) (j : Nat) (impTransImps
|
||||
-- `j ∈ transDeps[i].priv` if `i -(import ...)-> _ -(public import)->* j`
|
||||
transImps := transImps.union .priv {j} |>.union .priv (impTransImps.get .pub)
|
||||
if imp.importAll then
|
||||
-- `j ∈ transDeps[i].priv` if `i -(import all)->+ i'` and `j ∈ transDeps[i'].pub/priv`
|
||||
transImps := transImps.union .priv (impTransImps.get .pub ∪ impTransImps.get .priv)
|
||||
-- `j ∈ transDeps[i].priv` if `i -(import all)->+ -(public import ...)-> _ -(public import)->* j`
|
||||
transImps := transImps.union .priv (impTransImps.get .pub)
|
||||
|
||||
-- `j ∈ transDeps[i].metaPub` if `i -(public (meta)? import)->* _ -(public meta import)-> _ -(public (meta)? import)->* j`
|
||||
-- `j ∈ transDeps[i].metaPub` if `i -(public (meta)? import)->* _ -(public meta import)-> _ -(public (meta)? import ...)->* j`
|
||||
if imp.isExported then
|
||||
transImps := transImps.union .metaPub (impTransImps.get .metaPub)
|
||||
if imp.isMeta then
|
||||
@@ -267,49 +173,20 @@ def addTransitiveImps (transImps : Needs) (imp : Import) (j : Nat) (impTransImps
|
||||
|
||||
if !imp.isExported then
|
||||
if imp.isMeta then
|
||||
-- `j ∈ transDeps[i].metaPriv` if `i -(meta import ...)-> _ -(public (meta)? import)->* j`
|
||||
-- `j ∈ transDeps[i].metaPriv` if `i -(meta import ...)-> _ -(public (meta)? import ...)->* j`
|
||||
transImps := transImps.union .metaPriv {j} |>.union .metaPriv (impTransImps.get .pub ∪ impTransImps.get .metaPub)
|
||||
if imp.importAll then
|
||||
-- `j ∈ transDeps[i].metaPriv` if `i -(import all)->+ i'` and `j ∈ transDeps[i'].metaPub/metaPriv`
|
||||
transImps := transImps.union .metaPriv (impTransImps.get .metaPub ∪ impTransImps.get .metaPriv)
|
||||
else
|
||||
-- `j ∈ transDeps[i].metaPriv` if `i -(import ...)-> i'` and `j ∈ transDeps[i'].metaPub`
|
||||
-- `j ∈ transDeps[i].metaPriv` if `i -(import all)->+ -(public meta import ...)-> _ -(public (meta)? import ...)->* j`
|
||||
transImps := transImps.union .metaPriv (impTransImps.get .metaPub)
|
||||
|
||||
transImps
|
||||
|
||||
def isDeclMeta' (env : Environment) (declName : Name) : Bool :=
|
||||
-- Matchers are not compiled by themselves but inlined by the compiler, so there is no IR decl
|
||||
-- to be tagged as `meta`.
|
||||
-- TODO: It would be better to base the entire `meta` inference on the IR only and consider module
|
||||
-- references from any other context as compatible with both phases.
|
||||
let inferFor :=
|
||||
if declName.isStr && (declName.getString!.startsWith "match_" || declName.getString! == "_unsafe_rec") then declName.getPrefix else declName
|
||||
-- `isMarkedMeta` knows about non-defs such as `meta structure`, isDeclMeta knows about decls
|
||||
-- implicitly marked meta
|
||||
isMarkedMeta env inferFor || isDeclMeta env inferFor
|
||||
|
||||
/--
|
||||
Given an `Expr` reference, returns the declaration name that should be considered the reference, if
|
||||
any.
|
||||
-/
|
||||
def getDepConstName? (env : Environment) (ref : Name) : Option Name := do
|
||||
-- Ignore references to reserved names, they can be re-generated in-place
|
||||
guard <| !isReservedName env ref
|
||||
-- `_simp_...` constants are similar, use base decl instead
|
||||
return if ref.isStr && ref.getString!.startsWith "_simp_" then
|
||||
ref.getPrefix
|
||||
else
|
||||
ref
|
||||
|
||||
/-- Calculates the needs for a given module `mod` from constants and recorded extra uses. -/
|
||||
def calcNeeds (s : State) (i : ModuleIdx) : Needs := Id.run do
|
||||
let env := s.env
|
||||
def calcNeeds (env : Environment) (i : ModuleIdx) : Needs := Id.run do
|
||||
let mut needs := default
|
||||
for ci in env.header.moduleData[i]!.constants do
|
||||
-- Added guard for cases like `structure` that are still exported even if private
|
||||
let pubCI? := guard (!isPrivateName ci.name) *> (env.setExporting true).find? ci.name
|
||||
let k := { isExported := pubCI?.isSome, isMeta := isDeclMeta' env ci.name }
|
||||
let pubCI? := env.setExporting true |>.find? ci.name
|
||||
let k := { isExported := pubCI?.isSome, isMeta := isMeta env ci.name }
|
||||
needs := visitExpr k ci.type needs
|
||||
if let some e := ci.value? (allowOpaque := true) then
|
||||
-- type and value has identical visibility under `meta`
|
||||
@@ -324,19 +201,12 @@ def calcNeeds (s : State) (i : ModuleIdx) : Needs := Id.run do
|
||||
return needs
|
||||
where
|
||||
/-- Accumulate the results from expression `e` into `deps`. -/
|
||||
visitExpr (k : NeedsKind) (e : Expr) (deps : Needs) : Needs :=
|
||||
let env := s.env
|
||||
Lean.Expr.foldConsts e deps fun c deps => Id.run do
|
||||
let mut deps := deps
|
||||
if let some c := getDepConstName? env c then
|
||||
if let some j := env.getModuleIdxFor? c then
|
||||
let k := { k with isMeta := k.isMeta && !isDeclMeta' env c }
|
||||
if j != i then
|
||||
deps := deps.union k {j}
|
||||
for indMod in (indirectModUseExt.getState env)[c]?.getD #[] do
|
||||
if s.transDeps[i]!.has k indMod then
|
||||
deps := deps.union k {indMod}
|
||||
return deps
|
||||
visitExpr (k : NeedsKind) e deps :=
|
||||
Lean.Expr.foldConsts e deps fun c deps => match env.getModuleIdxFor? c with
|
||||
| some j =>
|
||||
let k := { k with isMeta := k.isMeta && !isMeta env c }
|
||||
if j != i then deps.union k {j} else deps
|
||||
| _ => deps
|
||||
|
||||
/--
|
||||
Calculates the same as `calcNeeds` but tracing each module to a use-def declaration pair or
|
||||
@@ -346,9 +216,8 @@ def getExplanations (env : Environment) (i : ModuleIdx) :
|
||||
Std.HashMap (ModuleIdx × NeedsKind) (Option (Name × Name)) := Id.run do
|
||||
let mut deps := default
|
||||
for ci in env.header.moduleData[i]!.constants do
|
||||
-- Added guard for cases like `structure` that are still exported even if private
|
||||
let pubCI? := guard (!isPrivateName ci.name) *> (env.setExporting true).find? ci.name
|
||||
let k := { isExported := pubCI?.isSome, isMeta := isDeclMeta' env ci.name }
|
||||
let pubCI? := env.setExporting true |>.find? ci.name
|
||||
let k := { isExported := pubCI?.isSome, isMeta := isMeta env ci.name }
|
||||
deps := visitExpr k ci.name ci.type deps
|
||||
if let some e := ci.value? (allowOpaque := true) then
|
||||
let k := if k.isMeta then k else
|
||||
@@ -364,18 +233,18 @@ def getExplanations (env : Environment) (i : ModuleIdx) :
|
||||
where
|
||||
/-- Accumulate the results from expression `e` into `deps`. -/
|
||||
visitExpr (k : NeedsKind) name e deps :=
|
||||
Lean.Expr.foldConsts e deps fun c deps => Id.run do
|
||||
let mut deps := deps
|
||||
if let some c := getDepConstName? env c then
|
||||
if let some j := env.getModuleIdxFor? c then
|
||||
let k := { k with isMeta := k.isMeta && !isDeclMeta' env c }
|
||||
if
|
||||
if let some (some (name', _)) := deps[(j, k)]? then
|
||||
decide (name.toString.length < name'.toString.length)
|
||||
else true
|
||||
then
|
||||
deps := deps.insert (j, k) (name, c)
|
||||
return deps
|
||||
Lean.Expr.foldConsts e deps fun c deps => match env.getModuleIdxFor? c with
|
||||
| some i =>
|
||||
let k := { k with isMeta := k.isMeta && !isMeta env c }
|
||||
if
|
||||
if let some (some (name', _)) := deps[(i, k)]? then
|
||||
decide (name.toString.length < name'.toString.length)
|
||||
else true
|
||||
then
|
||||
deps.insert (i, k) (name, c)
|
||||
else
|
||||
deps
|
||||
| _ => deps
|
||||
|
||||
partial def initStateFromEnv (env : Environment) : State := Id.run do
|
||||
let mut s := { env }
|
||||
@@ -391,6 +260,13 @@ partial def initStateFromEnv (env : Environment) : State := Id.run do
|
||||
s := { s with transDepsOrig := s.transDeps }
|
||||
return s
|
||||
|
||||
/-- The list of edits that will be applied in `--fix`. `edits[i] = (removed, added)` where:
|
||||
|
||||
* If `j ∈ removed` then we want to delete module named `j` from the imports of `i`
|
||||
* If `j ∈ added` then we want to add module index `j` to the imports of `i`.
|
||||
-/
|
||||
abbrev Edits := Std.HashMap Name (Array Import × Array Import)
|
||||
|
||||
/-- Register that we want to remove `tgt` from the imports of `src`. -/
|
||||
def Edits.remove (ed : Edits) (src : Name) (tgt : Import) : Edits :=
|
||||
match ed.get? src with
|
||||
@@ -409,8 +285,8 @@ Returns `(path, inputCtx, imports, endPos)` where `imports` is the `Lean.Parser.
|
||||
and `endPos` is the position of the end of the header.
|
||||
-/
|
||||
def parseHeaderFromString (text path : String) :
|
||||
IO (System.FilePath × (ictx : Parser.InputContext) ×
|
||||
TSyntax ``Parser.Module.header × String.Pos ictx.fileMap.source) := do
|
||||
IO (System.FilePath × Parser.InputContext ×
|
||||
TSyntaxArray ``Parser.Module.import × String.Pos) := do
|
||||
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
|
||||
@@ -418,8 +294,8 @@ def parseHeaderFromString (text path : String) :
|
||||
throw <| .userError "parse errors in file"
|
||||
-- the insertion point for `add` is the first newline after the imports
|
||||
let insertion := header.raw.getTailPos?.getD parserState.pos
|
||||
let insertion := inputCtx.fileMap.source.pos! insertion |>.find (· == '\n') |>.next!
|
||||
pure ⟨path, inputCtx, header, insertion⟩
|
||||
let insertion := text.findAux (· == '\n') text.endPos insertion + ⟨1⟩
|
||||
pure (path, inputCtx, .mk header.raw[2].getArgs, insertion)
|
||||
|
||||
/-- Parse a source file to extract the location of the import lines, for edits and error messages.
|
||||
|
||||
@@ -427,19 +303,14 @@ Returns `(path, inputCtx, imports, endPos)` where `imports` is the `Lean.Parser.
|
||||
and `endPos` is the position of the end of the header.
|
||||
-/
|
||||
def parseHeader (srcSearchPath : SearchPath) (mod : Name) :
|
||||
IO (System.FilePath × (ictx : Parser.InputContext) ×
|
||||
TSyntax ``Parser.Module.header × String.Pos ictx.fileMap.source) := do
|
||||
IO (System.FilePath × Parser.InputContext ×
|
||||
TSyntaxArray ``Parser.Module.import × String.Pos) := do
|
||||
-- Parse the input file
|
||||
let some path ← srcSearchPath.findModuleWithExt "lean" mod
|
||||
| throw <| .userError s!"error: failed to find source file for {mod}"
|
||||
let text ← IO.FS.readFile path
|
||||
parseHeaderFromString text path.toString
|
||||
|
||||
def decodeHeader : TSyntax ``Parser.Module.header → Option (TSyntax `module) × Option (TSyntax `prelude) × TSyntaxArray ``Parser.Module.import
|
||||
| `(Parser.Module.header| $[module%$moduleTk?]? $[prelude%$preludeTk?]? $imports*) =>
|
||||
(moduleTk?.map .mk, preludeTk?.map .mk, imports)
|
||||
| stx => panic! s!"unexpected header syntax {stx}"
|
||||
|
||||
def decodeImport : TSyntax ``Parser.Module.import → Import
|
||||
| `(Parser.Module.import| $[public%$pubTk?]? $[meta%$metaTk?]? import $[all%$allTk?]? $id) =>
|
||||
{ module := id.getId, isExported := pubTk?.isSome, isMeta := metaTk?.isSome, importAll := allTk?.isSome }
|
||||
@@ -447,174 +318,63 @@ def decodeImport : TSyntax ``Parser.Module.import → Import
|
||||
|
||||
/-- Analyze and report issues from module `i`. Arguments:
|
||||
|
||||
* `pkg`: the first component of the module name
|
||||
* `srcSearchPath`: Used to find the path for error reporting purposes
|
||||
* `i`: the module index
|
||||
* `needs`: the module's calculated needs
|
||||
* `pinned`: dependencies that should be preserved even if unused
|
||||
* `edits`: accumulates the list of edits to apply if `--fix` is true
|
||||
* `addOnly`: if true, only add missing imports, do not remove unused ones
|
||||
-/
|
||||
def visitModule (pkg : Name) (srcSearchPath : SearchPath)
|
||||
(i : Nat) (needs : Needs) (headerStx : TSyntax ``Parser.Module.header) (args : Args)
|
||||
(addOnly := false) : StateT State IO Unit := do
|
||||
if isExtraRevModUse (← get).env i then
|
||||
modify fun s => { s with preserve := s.preserve.union (if args.addPublic then .pub else .priv) {i} }
|
||||
if args.trace then
|
||||
IO.eprintln s!"Preserving `{(← get).modNames[i]!}` because of recorded extra rev use"
|
||||
|
||||
-- only process modules in the selected package
|
||||
-- TODO: should be after `keep-downstream` but core headers are not found yet?
|
||||
if !pkg.isPrefixOf (← get).modNames[i]! then
|
||||
return
|
||||
|
||||
let (module?, prelude?, imports) := decodeHeader headerStx
|
||||
if module?.any (·.raw.getTrailing?.any (·.toString.contains "shake: keep-downstream")) then
|
||||
modify fun s => { s with preserve := s.preserve.union (if args.addPublic then .pub else .priv) {i} }
|
||||
|
||||
def visitModule (srcSearchPath : SearchPath)
|
||||
(i : Nat) (needs : Needs) (preserve : Needs) (edits : Edits)
|
||||
(addOnly := false) (githubStyle := false) (explain := false) : StateT State IO Edits := do
|
||||
let s ← get
|
||||
|
||||
let addOnly := addOnly || module?.any (·.raw.getTrailing?.any (·.toString.contains "shake: keep-all"))
|
||||
-- Do transitive reduction of `needs` in `deps`.
|
||||
let mut deps := needs
|
||||
for j in [0:s.mods.size] do
|
||||
let transDeps := s.transDeps[j]!
|
||||
for k in NeedsKind.all do
|
||||
if s.transDepsOrig[i]!.has k j && preserve.has k j then
|
||||
deps := deps.union k {j}
|
||||
if deps.has k j then
|
||||
let transDeps := addTransitiveImps .empty { k with module := .anonymous } j transDeps
|
||||
for k' in NeedsKind.all do
|
||||
deps := deps.sub k' (transDeps.sub k' {j} |>.get k')
|
||||
|
||||
-- Add additional preserved imports
|
||||
for impStx in imports do
|
||||
let imp := decodeImport impStx
|
||||
-- Any import which is not in `transDeps` was unused.
|
||||
-- Also accumulate `newDeps` which is the transitive closure of the remaining imports
|
||||
let mut toRemove : Array Import := #[]
|
||||
let mut newDeps := Needs.empty
|
||||
for imp in s.mods[i]!.imports do
|
||||
let j := s.env.getModuleIdx? imp.module |>.get!
|
||||
let k := NeedsKind.ofImport imp
|
||||
if addOnly ||
|
||||
args.keepPublic && imp.isExported ||
|
||||
impStx.raw.getTrailing?.any (·.toString.contains "shake: keep") then
|
||||
deps := deps.union k {j}
|
||||
if args.trace then
|
||||
IO.eprintln s!"Adding `{imp}` as additional dependency"
|
||||
if
|
||||
-- skip folder-nested imports
|
||||
s.modNames[i]!.isPrefixOf imp.module ||
|
||||
imp.importAll then
|
||||
newDeps := addTransitiveImps newDeps imp j s.transDeps[j]!
|
||||
else
|
||||
let k := NeedsKind.ofImport imp
|
||||
if !addOnly && !deps.has k j && !deps.has { k with isExported := false } j then
|
||||
toRemove := toRemove.push imp
|
||||
else
|
||||
newDeps := addTransitiveImps newDeps imp j s.transDeps[j]!
|
||||
|
||||
-- If `newDeps` does not cover `deps`, then we have to add back some imports until it does.
|
||||
-- To minimize new imports we pick only new imports which are not transitively implied by
|
||||
-- another new import
|
||||
let mut toAdd : Array Import := #[]
|
||||
for j in [0:s.mods.size] do
|
||||
for k in NeedsKind.all do
|
||||
-- Remove `meta` while preserving, no use-case for preserving `meta` so far.
|
||||
-- Downgrade to private unless `--add-public` is used.
|
||||
if s.transDepsOrig[i]!.has k j &&
|
||||
(s.preserve.has { k with isMeta := false, isExported := false } j ||
|
||||
s.preserve.has { k with isMeta := false, isExported := true } j) then
|
||||
deps := deps.union { k with isMeta := false, isExported := k.isExported && args.addPublic } {j}
|
||||
|
||||
-- Do transitive reduction of `needs` in `deps`.
|
||||
if !addOnly then
|
||||
for j in [0:s.mods.size] do
|
||||
let transDeps := s.transDeps[j]!
|
||||
for k in NeedsKind.all do
|
||||
if deps.has k j then
|
||||
let transDeps := addTransitiveImps .empty { k with module := .anonymous } j transDeps
|
||||
for k' in NeedsKind.all do
|
||||
deps := deps.sub k' (transDeps.sub k' {j} |>.get k')
|
||||
|
||||
if prelude?.isNone then
|
||||
deps := deps.union .pub {s.env.getModuleIdx? `Init |>.get!}
|
||||
|
||||
-- Accumulate `transDeps` which is the non-reflexive transitive closure of the still-live imports
|
||||
let mut transDeps := Needs.empty
|
||||
let mut alwaysAdd : Array Import := #[] -- to be added even if implied by other imports
|
||||
for imp in s.mods[i]!.imports do
|
||||
let j := s.env.getModuleIdx? imp.module |>.get!
|
||||
let k := NeedsKind.ofImport imp
|
||||
if deps.has k j || imp.importAll then
|
||||
transDeps := addTransitiveImps transDeps imp j s.transDeps[j]!
|
||||
deps := deps.union k {j}
|
||||
-- skip folder-nested `public (meta)? import`s but remove `meta`
|
||||
else if s.modNames[i]!.isPrefixOf imp.module then
|
||||
let imp := { imp with isMeta := false }
|
||||
let k := { k with isMeta := false }
|
||||
if args.trace then
|
||||
IO.eprintln s!"`{imp}` is preserved as folder-nested import"
|
||||
transDeps := addTransitiveImps transDeps imp j s.transDeps[j]!
|
||||
deps := deps.union k {j}
|
||||
if !s.mods[i]!.imports.contains imp then
|
||||
alwaysAdd := alwaysAdd.push imp
|
||||
|
||||
-- If `transDeps` does not cover `deps`, then we have to add back some imports until it does.
|
||||
-- To minimize new imports we pick only new imports which are not transitively implied by
|
||||
-- another new import, so we visit module indices in descending order.
|
||||
let mut keptPrefix := false
|
||||
let mut newTransDeps := transDeps
|
||||
let mut toAdd : Array Import := #[]
|
||||
for j in (0...s.mods.size).toArray.reverse do
|
||||
for k in NeedsKind.all do
|
||||
if deps.has k j && !newTransDeps.has k j && !newTransDeps.has { k with isExported := true } j then
|
||||
-- `add-public/keep-prefix` may change the import and even module we're considering
|
||||
let mut k := k
|
||||
let mut imp : Import := { k with module := s.modNames[j]! }
|
||||
let mut j := j
|
||||
if args.trace then
|
||||
IO.eprintln s!"`{imp}` is needed"
|
||||
if args.addPublic && !k.isExported &&
|
||||
-- also add as public if previously `public meta`, which could be from automatic porting
|
||||
(s.transDepsOrig[i]!.has { k with isExported := true } j || s.transDepsOrig[i]!.has { k with isExported := true, isMeta := true } j) then
|
||||
k := { k with isExported := true }
|
||||
imp := { imp with isExported := true }
|
||||
if args.trace then
|
||||
IO.eprintln s!"* upgrading to `{imp}` because of `--add-public`"
|
||||
if args.keepPrefix then
|
||||
let rec tryPrefix : Name → Option ModuleIdx
|
||||
| .str p _ => tryPrefix p <|> (do
|
||||
let j' ← s.env.getModuleIdx? p
|
||||
-- `j'` must be reachable from `i` (allow downgrading from `meta`)
|
||||
guard <| s.transDepsOrig[i]!.has k j' || s.transDepsOrig[i]!.has { k with isMeta := true } j'
|
||||
let j'transDeps := addTransitiveImps .empty p j' s.transDeps[j']!
|
||||
-- `j` must be reachable from `j'` (now downgrading must be done in the other direction)
|
||||
guard <| j'transDeps.has k j || j'transDeps.has { k with isMeta := false } j
|
||||
return j')
|
||||
| _ => none
|
||||
if let some j' := tryPrefix imp.module then
|
||||
imp := { imp with module := s.modNames[j']! }
|
||||
j := j'
|
||||
keptPrefix := true
|
||||
if args.trace then
|
||||
IO.eprintln s!"* upgrading to `{imp}` because of `--keep-prefix`"
|
||||
if !s.mods[i]!.imports.contains imp then
|
||||
toAdd := toAdd.push imp
|
||||
deps := deps.union k {j}
|
||||
newTransDeps := addTransitiveImps newTransDeps imp j s.transDeps[j]!
|
||||
|
||||
if keptPrefix then
|
||||
-- if an import was replaced by `--keep-prefix`, we did not necessarily visit the modules in
|
||||
-- dependency order anymore and so we have to redo the transitive closure checking
|
||||
newTransDeps := transDeps
|
||||
for j in (0...s.mods.size).toArray.reverse do
|
||||
for k in NeedsKind.all do
|
||||
if deps.has k j then
|
||||
let mut imp : Import := { k with module := s.modNames[j]! }
|
||||
if toAdd.contains imp && (newTransDeps.has k j || newTransDeps.has { k with isExported := true } j) then
|
||||
if args.trace then
|
||||
IO.eprintln s!"Removing `{imp}` from imports to be added because it is now implied"
|
||||
toAdd := toAdd.erase imp
|
||||
deps := deps.sub k {j}
|
||||
else
|
||||
newTransDeps := addTransitiveImps newTransDeps imp j s.transDeps[j]!
|
||||
|
||||
-- now that `toAdd` filtering is done, add `alwaysAdd`
|
||||
toAdd := alwaysAdd ++ toAdd
|
||||
|
||||
-- Any import which is still not in `deps` was unused
|
||||
let mut toRemove : Array Import := #[]
|
||||
for imp in s.mods[i]!.imports do
|
||||
let j := s.env.getModuleIdx? imp.module |>.get!
|
||||
let k := NeedsKind.ofImport imp
|
||||
if args.keepImplied && newTransDeps.has k j then
|
||||
if args.trace && !deps.has k j then
|
||||
IO.eprintln s!"`{imp}` is implied by other imports"
|
||||
else if !deps.has k j then
|
||||
if args.trace then
|
||||
IO.eprintln s!"`{imp}` is now unused"
|
||||
toRemove := toRemove.push imp
|
||||
-- A private import should also be removed if the public version has been added
|
||||
else if !k.isExported && !imp.importAll && newTransDeps.has { k with isExported := true } j then
|
||||
if args.trace then
|
||||
IO.eprintln s!"`{imp}` is already covered by `{ { imp with isExported := true } }`"
|
||||
toRemove := toRemove.push imp
|
||||
if deps.has k j && !newDeps.has k j && !newDeps.has { k with isExported := true } j then
|
||||
let imp := { k with module := s.modNames[j]! }
|
||||
toAdd := toAdd.push imp
|
||||
newDeps := addTransitiveImps newDeps imp j s.transDeps[j]!
|
||||
|
||||
-- mark and report the removals
|
||||
modify fun s => { s with
|
||||
edits := toRemove.foldl (init := s.edits) fun edits imp =>
|
||||
edits.remove s.modNames[i]! imp }
|
||||
let mut edits := toRemove.foldl (init := edits) fun edits imp =>
|
||||
edits.remove s.modNames[i]! imp
|
||||
|
||||
if !toAdd.isEmpty || !toRemove.isEmpty || args.explain then
|
||||
if !toAdd.isEmpty || !toRemove.isEmpty || explain then
|
||||
if let some path ← srcSearchPath.findModuleWithExt "lean" s.modNames[i]! then
|
||||
println! "{path}:"
|
||||
else
|
||||
@@ -623,10 +383,9 @@ def visitModule (pkg : Name) (srcSearchPath : SearchPath)
|
||||
if !toRemove.isEmpty then
|
||||
println! " remove {toRemove}"
|
||||
|
||||
if args.githubStyle then
|
||||
if githubStyle then
|
||||
try
|
||||
let ⟨path, inputCtx, stx, endHeader⟩ ← parseHeader srcSearchPath s.modNames[i]!
|
||||
let (_, _, imports) := decodeHeader stx
|
||||
let (path, inputCtx, imports, endHeader) ← parseHeader srcSearchPath s.modNames[i]!
|
||||
for stx in imports do
|
||||
if toRemove.any fun imp => imp == decodeImport stx then
|
||||
let pos := inputCtx.fileMap.toPosition stx.raw.getPos?.get!
|
||||
@@ -634,15 +393,14 @@ def visitModule (pkg : Name) (srcSearchPath : SearchPath)
|
||||
(use `lake exe shake --fix` to fix this, or `lake exe shake --update` to ignore)"
|
||||
if !toAdd.isEmpty then
|
||||
-- we put the insert message on the beginning of the last import line
|
||||
let pos := inputCtx.fileMap.toPosition endHeader.offset
|
||||
let pos := inputCtx.fileMap.toPosition endHeader
|
||||
println! "{path}:{pos.line-1}:1: warning: \
|
||||
add {toAdd} instead"
|
||||
catch _ => pure ()
|
||||
|
||||
-- mark and report the additions
|
||||
modify fun s => { s with
|
||||
edits := toAdd.foldl (init := s.edits) fun edits imp =>
|
||||
edits.add s.modNames[i]! imp }
|
||||
edits := toAdd.foldl (init := edits) fun edits imp =>
|
||||
edits.add s.modNames[i]! imp
|
||||
|
||||
if !toAdd.isEmpty then
|
||||
println! " add {toAdd}"
|
||||
@@ -657,15 +415,14 @@ def visitModule (pkg : Name) (srcSearchPath : SearchPath)
|
||||
let j := s.env.getModuleIdx? imp.module |>.get!
|
||||
newTransDepsI := addTransitiveImps newTransDepsI imp j s.transDeps[j]!
|
||||
|
||||
modify fun s => { s with transDeps := s.transDeps.set! i newTransDepsI }
|
||||
set { s with transDeps := s.transDeps.set! i newTransDepsI }
|
||||
|
||||
if args.explain then
|
||||
if explain then
|
||||
let explanation := getExplanations s.env i
|
||||
let sanitize n := if n.hasMacroScopes then (sanitizeName n).run' { options := {} } else n
|
||||
let run (imp : Import) := do
|
||||
let j := s.env.getModuleIdx? imp.module |>.get!
|
||||
let mut k := NeedsKind.ofImport imp
|
||||
if let some exp? := explanation[(j, k)]? <|> guard args.addPublic *> explanation[(j, { k with isExported := false})]? then
|
||||
if let some exp? := explanation[(j, NeedsKind.ofImport imp)]? then
|
||||
println! " note: `{imp}` required"
|
||||
if let some (n, c) := exp? then
|
||||
println! " because `{sanitize n}` refers to `{sanitize c}`"
|
||||
@@ -676,6 +433,8 @@ def visitModule (pkg : Name) (srcSearchPath : SearchPath)
|
||||
run j
|
||||
for i in toAdd do run i
|
||||
|
||||
return edits
|
||||
|
||||
/-- Convert a list of module names to a bitset of module indexes -/
|
||||
def toBitset (s : State) (ns : List Name) : Bitset :=
|
||||
ns.foldl (init := ∅) fun c name =>
|
||||
@@ -683,26 +442,40 @@ def toBitset (s : State) (ns : List Name) : Bitset :=
|
||||
| some i => c ∪ {i}
|
||||
| none => c
|
||||
|
||||
/-- The parsed CLI arguments. See `help` for more information -/
|
||||
structure Args where
|
||||
/-- `--help`: shows the help -/
|
||||
help : Bool := false
|
||||
/-- `--force`: skips the `lake build --no-build` sanity check -/
|
||||
force : Bool := false
|
||||
/-- `--gh-style`: output messages that can be parsed by `gh-problem-matcher-wrap` -/
|
||||
githubStyle : Bool := false
|
||||
/-- `--explain`: give constants explaining why each module is needed -/
|
||||
explain : Bool := false
|
||||
/-- `--fix`: apply the fixes directly -/
|
||||
fix : Bool := false
|
||||
/-- `<MODULE>..`: the list of root modules to check -/
|
||||
mods : Array Name := #[]
|
||||
|
||||
local instance : Ord Import where
|
||||
compare :=
|
||||
let _ := @lexOrd
|
||||
compareOn fun imp => (!imp.isExported, imp.module.toString)
|
||||
compare a b :=
|
||||
if a.isExported && !b.isExported then
|
||||
Ordering.lt
|
||||
else if !a.isExported && b.isExported then
|
||||
Ordering.gt
|
||||
else
|
||||
a.module.cmp b.module
|
||||
|
||||
/-- The main entry point. See `help` for more information on arguments. -/
|
||||
public def main (args : List String) : IO UInt32 := do
|
||||
def main (args : List String) : IO UInt32 := do
|
||||
initSearchPath (← findSysroot)
|
||||
-- Parse the arguments
|
||||
let rec parseArgs (args : Args) : List String → Args
|
||||
| [] => args
|
||||
| "--help" :: rest => parseArgs { args with help := true } rest
|
||||
| "--keep-implied" :: rest => parseArgs { args with keepImplied := true } rest
|
||||
| "--keep-prefix" :: rest => parseArgs { args with keepPrefix := true } rest
|
||||
| "--keep-public" :: rest => parseArgs { args with keepPublic := true } rest
|
||||
| "--add-public" :: rest => parseArgs { args with addPublic := true } rest
|
||||
| "--force" :: rest => parseArgs { args with force := true } rest
|
||||
| "--fix" :: rest => parseArgs { args with fix := true } rest
|
||||
| "--explain" :: rest => parseArgs { args with explain := true } rest
|
||||
| "--trace" :: rest => parseArgs { args with trace := true } rest
|
||||
| "--gh-style" :: rest => parseArgs { args with githubStyle := true } rest
|
||||
| "--" :: rest => { args with mods := args.mods ++ rest.map (·.toName) }
|
||||
| other :: rest => parseArgs { args with mods := args.mods.push other.toName } rest
|
||||
@@ -745,62 +518,52 @@ public def main (args : List String) : IO UInt32 := do
|
||||
let imps := mods.map ({ module := · })
|
||||
let (_, s) ← importModulesCore imps (isExported := true) |>.run
|
||||
let s := s.markAllExported
|
||||
let mut env ← finalizeImport s (isModule := true) imps {} (leakEnv := false) (loadExts := false)
|
||||
-- the one env ext we want to initialize
|
||||
let is := indirectModUseExt.toEnvExtension.getState env
|
||||
let newState ← indirectModUseExt.addImportedFn is.importedEntries { env := env, opts := {} }
|
||||
env := indirectModUseExt.toEnvExtension.setState (asyncMode := .sync) env { is with state := newState }
|
||||
let env ← finalizeImport s (isModule := true) imps {} (leakEnv := false) (loadExts := false)
|
||||
|
||||
StateT.run' (s := initStateFromEnv env) do
|
||||
|
||||
let s ← get
|
||||
-- Parse the config file
|
||||
|
||||
-- Run the calculation of the `needs` array in parallel
|
||||
let needs := s.mods.mapIdx fun i _ =>
|
||||
Task.spawn fun _ => calcNeeds s i
|
||||
|
||||
-- Parse headers in parallel
|
||||
let headers ← s.mods.mapIdxM fun i _ =>
|
||||
if !pkg.isPrefixOf s.modNames[i]! then
|
||||
pure <| Task.pure <| .ok ⟨default, default, default, default⟩
|
||||
else
|
||||
BaseIO.asTask (parseHeader srcSearchPath s.modNames[i]! |>.toBaseIO)
|
||||
Task.spawn fun _ => calcNeeds s.env i
|
||||
|
||||
if args.fix then
|
||||
println! "The following changes will be made automatically:"
|
||||
|
||||
-- Check all selected modules
|
||||
for i in [0:s.mods.size], t in needs, header in headers do
|
||||
match header.get with
|
||||
| .ok ⟨_, _, stx, _⟩ =>
|
||||
visitModule pkg srcSearchPath i t.get stx args
|
||||
| .error e =>
|
||||
println! e.toString
|
||||
let mut edits : Edits := ∅
|
||||
let mut revNeeds : Needs := default
|
||||
for i in [0:s.mods.size], t in needs do
|
||||
edits ← visitModule (addOnly := !pkg.isPrefixOf s.modNames[i]!) srcSearchPath i t.get revNeeds edits args.githubStyle args.explain
|
||||
if isExtraRevModUse s.env i then
|
||||
revNeeds := revNeeds.union .priv {i}
|
||||
|
||||
if !args.fix then
|
||||
-- return error if any issues were found
|
||||
return if (← get).edits.isEmpty then 0 else 1
|
||||
return if edits.isEmpty then 0 else 1
|
||||
|
||||
-- Apply the edits to existing files
|
||||
let mut count := 0
|
||||
for mod in s.modNames, header? in headers do
|
||||
let some (remove, add) := (← get).edits[mod]? | continue
|
||||
let count ← edits.foldM (init := 0) fun count mod (remove, add) => do
|
||||
let add : Array Import := add.qsortOrd
|
||||
|
||||
-- Parse the input file
|
||||
let .ok ⟨path, inputCtx, stx, insertion⟩ := header?.get | continue
|
||||
let (_, _, imports) := decodeHeader stx
|
||||
let (path, inputCtx, imports, insertion) ←
|
||||
try parseHeader srcSearchPath mod
|
||||
catch e => println! e.toString; return count
|
||||
let text := inputCtx.fileMap.source
|
||||
|
||||
-- Calculate the edit result
|
||||
let mut pos : String.Pos text := text.startPos
|
||||
let mut pos : String.Pos := 0
|
||||
let mut out : String := ""
|
||||
let mut seen : Std.HashSet Import := {}
|
||||
for stx in imports do
|
||||
let mod := decodeImport stx
|
||||
if remove.contains mod || seen.contains mod then
|
||||
out := out ++ text.extract pos (text.pos! stx.raw.getPos?.get!)
|
||||
out := out ++ text.extract pos stx.raw.getPos?.get!
|
||||
-- We use the end position of the syntax, but include whitespace up to the first newline
|
||||
pos := text.pos! stx.raw.getTailPos?.get! |>.find '\n' |>.next!
|
||||
pos := text.findAux (· == '\n') text.endPos stx.raw.getTailPos?.get! + ⟨1⟩
|
||||
seen := seen.insert mod
|
||||
out := out ++ text.extract pos insertion
|
||||
for mod in add do
|
||||
@@ -810,7 +573,7 @@ public def main (args : List String) : IO UInt32 := do
|
||||
out := out ++ text.extract insertion text.endPos
|
||||
|
||||
IO.FS.writeFile path out
|
||||
count := count + 1
|
||||
return count + 1
|
||||
|
||||
-- Since we throw an error upon encountering issues, we can be sure that everything worked
|
||||
-- if we reach this point of the script.
|
||||
|
||||
@@ -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
96
script/bench.sh
Executable 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -7,5 +7,3 @@ root = "Modulize"
|
||||
[[lean_exe]]
|
||||
name = "shake"
|
||||
root = "Shake"
|
||||
# needed by `Lake.loadWorkspace`
|
||||
supportInterpreter = true
|
||||
|
||||
1290
script/lean-bisect
1290
script/lean-bisect
File diff suppressed because it is too large
Load Diff
@@ -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!"
|
||||
@@ -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"
|
||||
|
||||
@@ -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,57 +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 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)")
|
||||
@@ -602,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...")
|
||||
|
||||
@@ -696,19 +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}")
|
||||
else:
|
||||
print(f" ❌ PR with title '{pr_title}' does not exist")
|
||||
print(f" Run `script/release_steps.py {toolchain} {name}` to create it")
|
||||
@@ -816,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
|
||||
|
||||
@@ -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
|
||||
@@ -127,30 +113,10 @@ repositories:
|
||||
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
|
||||
- verso
|
||||
|
||||
@@ -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
|
||||
@@ -413,14 +389,20 @@ def execute_release_steps(repo, version, config):
|
||||
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)
|
||||
|
||||
print(blue("Updating docs/lakefile.toml..."))
|
||||
run_command(f'perl -pi -e \'s/"v4\\.[0-9]+(\\.[0-9]+)?(-rc[0-9]+)?"/"' + version + '"/g\' lakefile.*', cwd=repo_path / "docs")
|
||||
|
||||
# Update lean-toolchain in docs
|
||||
print(blue("Updating docs/lean-toolchain..."))
|
||||
docs_toolchain = repo_path / "docs" / "lean-toolchain"
|
||||
with open(docs_toolchain, "w") as f:
|
||||
f.write(f"leanprover/lean4:{version}\n")
|
||||
print(green(f"Updated docs/lean-toolchain to leanprover/lean4:{version}"))
|
||||
|
||||
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)
|
||||
@@ -584,19 +566,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"))
|
||||
|
||||
@@ -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 25)
|
||||
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,15 +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()
|
||||
|
||||
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)
|
||||
@@ -89,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`")
|
||||
@@ -191,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")
|
||||
@@ -448,8 +440,8 @@ 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()
|
||||
|
||||
# get rid of unused parts of C++ stdlib
|
||||
@@ -834,13 +826,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.
|
||||
|
||||
@@ -14,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
|
||||
|
||||
@@ -7,6 +7,7 @@ Authors: Joachim Breitner
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Prelude
|
||||
public import Init.Tactics
|
||||
|
||||
public section
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
-/
|
||||
|
||||
|
||||
@@ -16,4 +16,3 @@ 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
|
||||
|
||||
@@ -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`.
|
||||
-/
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -25,12 +27,6 @@ instance [Repr ε] [Repr α] : Repr (Result ε σ α) where
|
||||
| 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,4 +10,3 @@ 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
|
||||
|
||||
@@ -7,6 +7,8 @@ module
|
||||
|
||||
prelude
|
||||
public import Init.Ext
|
||||
public import Init.SimpLemmas
|
||||
public import Init.Meta
|
||||
|
||||
public section
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,19 +7,19 @@ 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
|
||||
@@ -28,8 +28,6 @@ namespace ExceptT
|
||||
simp [run] at h
|
||||
assumption
|
||||
|
||||
@[simp, grind =] theorem run_mk (x : m (Except ε α)) : run (mk x : ExceptT ε m α) = x := rfl
|
||||
|
||||
@[simp, grind =] theorem run_pure [Monad m] (x : α) : run (pure x : ExceptT ε m α) = pure (Except.ok x) := rfl
|
||||
|
||||
@[simp, grind =] theorem run_lift [Monad.{u, v} m] (x : m α) : run (ExceptT.lift x : ExceptT ε m α) = (Except.ok <$> x : m (Except ε α)) := rfl
|
||||
@@ -60,9 +58,6 @@ theorem run_bind [Monad m] (x : ExceptT ε m α) (f : α → ExceptT ε m β)
|
||||
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 +100,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 -/
|
||||
@@ -174,9 +153,6 @@ namespace OptionT
|
||||
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
|
||||
|
||||
@@ -216,12 +192,12 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (OptionT m) where
|
||||
|
||||
@[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 [seq_eq_bind, 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 [seqLeft_eq, seq_eq_bind, 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
|
||||
@@ -238,24 +214,6 @@ instance [Monad m] [LawfulMonad m] : LawfulMonad (OptionT m) where
|
||||
(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 -/
|
||||
@@ -264,7 +222,7 @@ 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)
|
||||
(bind_pure_comp := bind_pure_comp)
|
||||
|
||||
instance : LawfulApplicative Option := inferInstance
|
||||
instance : LawfulFunctor Option := inferInstance
|
||||
@@ -277,9 +235,6 @@ namespace ReaderT
|
||||
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, 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 : ρ)
|
||||
@@ -327,22 +282,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 -/
|
||||
@@ -357,20 +296,17 @@ namespace StateT
|
||||
@[ext, grind 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 :=
|
||||
rfl
|
||||
|
||||
@[simp, grind =] theorem run'_eq [Monad m] (x : StateT σ m α) (s : σ) : run' x s = (·.1) <$> run x s :=
|
||||
rfl
|
||||
|
||||
@[simp, grind =] theorem run_pure [Monad m] (a : α) (s : σ) : (pure a : StateT σ m α).run s = pure (a, s) := rfl
|
||||
|
||||
@[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
|
||||
: (x >>= f).run s = x.run s >>= λ p => (f p.1).run p.2 := by
|
||||
simp [bind, StateT.bind, run]
|
||||
|
||||
@[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 [Functor.map, StateT.map, run, ←bind_pure_comp]
|
||||
|
||||
@[simp, grind =] theorem run_get [Monad m] (s : σ) : (get : StateT σ m σ).run s = pure (s, s) := rfl
|
||||
|
||||
@@ -379,13 +315,13 @@ namespace StateT
|
||||
@[simp, grind =] theorem run_modify [Monad m] (f : σ → σ) (s : σ) : (modify f : StateT σ m PUnit).run s = pure (⟨⟩, f s) := rfl
|
||||
|
||||
@[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 [modifyGet, MonadStateOf.modifyGet, StateT.modifyGet, run]
|
||||
|
||||
@[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
|
||||
|
||||
@@ -425,48 +361,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 +378,3 @@ instance : LawfulMonad (EStateM ε σ) := .mk'
|
||||
| .ok _ _ => rfl
|
||||
| .error _ _ => rfl)
|
||||
(map_const := fun _ _ => rfl)
|
||||
|
||||
end EStateM
|
||||
|
||||
@@ -7,6 +7,7 @@ module
|
||||
|
||||
prelude
|
||||
public import Init.Control.Lawful.Basic
|
||||
public import Init.RCases
|
||||
public import Init.ByCases
|
||||
|
||||
public section
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -69,7 +70,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 +78,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 +91,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 +101,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 +113,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -8,6 +8,7 @@ notation, basic datatypes and type classes
|
||||
module
|
||||
|
||||
prelude
|
||||
public meta import Init.Prelude
|
||||
public import Init.SizeOf
|
||||
|
||||
public section
|
||||
@@ -201,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 α β
|
||||
@@ -378,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)
|
||||
|
||||
@@ -406,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')
|
||||
|
||||
@@ -601,6 +601,17 @@ 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 }`. -/
|
||||
@@ -940,7 +951,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 :=
|
||||
@@ -1083,6 +1096,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
|
||||
|
||||
@@ -1145,7 +1166,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
|
||||
@@ -1187,13 +1207,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
|
||||
@@ -1344,12 +1368,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
|
||||
|
||||
@@ -1364,12 +1388,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
|
||||
|
||||
@@ -1428,8 +1452,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₂
|
||||
@@ -1467,8 +1491,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
|
||||
|
||||
@@ -1485,24 +1507,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 -/
|
||||
|
||||
@@ -1589,7 +1607,7 @@ 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% Substring
|
||||
gen_injective_theorems% Subtype
|
||||
gen_injective_theorems% Sum
|
||||
gen_injective_theorems% Task
|
||||
@@ -2506,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
|
||||
@@ -2516,19 +2535,16 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,6 +7,7 @@ Authors: Dany Fabian
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Classical
|
||||
public import Init.ByCases
|
||||
|
||||
@[expose] public section
|
||||
|
||||
@@ -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
|
||||
@@ -572,6 +575,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]
|
||||
@@ -746,6 +752,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 : α → β} :
|
||||
|
||||
@@ -6,6 +6,10 @@ 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.GetElem
|
||||
public import Init.Data.List.ToArrayImpl
|
||||
import all Init.Data.List.ToArrayImpl
|
||||
@@ -209,6 +213,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 +244,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 +260,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
|
||||
@@ -448,7 +466,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 +480,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 +588,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 +607,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 +752,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 +913,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 +1021,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 +1252,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 +1270,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 +1307,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 +1318,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 +1371,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 +1389,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 +1717,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 +1728,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 +1742,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 +1766,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 +1777,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
|
||||
@@ -1838,7 +1861,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 +1916,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 +1945,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 +2108,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)
|
||||
@@ -2136,3 +2162,5 @@ instance [ToString α] : ToString (Array α) where
|
||||
toString xs := String.Internal.append "#" (toString xs.toList)
|
||||
|
||||
end Array
|
||||
|
||||
export Array (mkArray)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -31,7 +32,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 +74,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]
|
||||
|
||||
@@ -97,15 +101,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 +111,6 @@ grind_pattern append_assoc => xs ++ (ys ++ zs) where
|
||||
rw [← appendList_eq_append]; unfold Array.appendList
|
||||
induction l generalizing xs <;> simp [*]
|
||||
|
||||
|
||||
|
||||
end Array
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,41 +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))
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) ∨
|
||||
@@ -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,11 +311,17 @@ 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 -/
|
||||
@@ -389,6 +410,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 +432,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, *]
|
||||
|
||||
@@ -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⟩
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,10 @@ module
|
||||
|
||||
prelude
|
||||
public import Init.Data.List.Nat.Find
|
||||
public import Init.Data.Array.Basic
|
||||
import all Init.Data.Array.Basic
|
||||
public import Init.Data.Array.Lemmas
|
||||
public import Init.Data.Array.Attach
|
||||
public import Init.Data.Array.Range
|
||||
|
||||
public section
|
||||
@@ -129,19 +132,31 @@ theorem getElem_zero_flatten {xss : Array (Array α)} (h) :
|
||||
theorem findSome?_replicate : findSome? f (replicate n a) = if n = 0 then none else f a := by
|
||||
simp [← List.toArray_replicate, List.findSome?_replicate]
|
||||
|
||||
@[deprecated findSome?_replicate (since := "2025-03-18")]
|
||||
abbrev findSome?_mkArray := @findSome?_replicate
|
||||
|
||||
@[simp] theorem findSome?_replicate_of_pos (h : 0 < n) : findSome? f (replicate n a) = f a := by
|
||||
simp [findSome?_replicate, Nat.ne_of_gt h]
|
||||
|
||||
@[deprecated findSome?_replicate_of_pos (since := "2025-03-18")]
|
||||
abbrev findSome?_mkArray_of_pos := @findSome?_replicate_of_pos
|
||||
|
||||
-- Argument is unused, but used to decide whether `simp` should unfold.
|
||||
@[simp] theorem findSome?_replicate_of_isSome (_ : (f a).isSome) :
|
||||
findSome? f (replicate n a) = if n = 0 then none else f a := by
|
||||
simp [findSome?_replicate]
|
||||
|
||||
@[deprecated findSome?_replicate_of_isSome (since := "2025-03-18")]
|
||||
abbrev findSome?_mkArray_of_isSome := @findSome?_replicate_of_isSome
|
||||
|
||||
@[simp] theorem findSome?_replicate_of_isNone (h : (f a).isNone) :
|
||||
findSome? f (replicate n a) = none := by
|
||||
rw [Option.isNone_iff_eq_none] at h
|
||||
simp [findSome?_replicate, h]
|
||||
|
||||
@[deprecated findSome?_replicate_of_isNone (since := "2025-03-18")]
|
||||
abbrev findSome?_mkArray_of_isNone := @findSome?_replicate_of_isNone
|
||||
|
||||
/-! ### find? -/
|
||||
|
||||
@[simp, grind =] theorem find?_empty : find? p #[] = none := rfl
|
||||
@@ -159,6 +174,9 @@ theorem find?_singleton {a : α} {p : α → Bool} :
|
||||
findRev? p (xs.push a) = findRev? p xs := by
|
||||
cases xs; simp [h]
|
||||
|
||||
@[deprecated findRev?_push_of_neg (since := "2025-06-12")]
|
||||
abbrev findRev?_cons_of_neg := @findRev?_push_of_neg
|
||||
|
||||
@[grind =]
|
||||
theorem finRev?_push {xs : Array α} :
|
||||
findRev? p (xs.push a) = (Option.guard p a).or (xs.findRev? p) := by
|
||||
@@ -168,6 +186,9 @@ theorem finRev?_push {xs : Array α} :
|
||||
· rw [findRev?_push_of_pos, Option.guard_eq_some_iff.mpr ⟨rfl, h⟩]
|
||||
all_goals simp [h]
|
||||
|
||||
@[deprecated finRev?_push (since := "2025-06-12")]
|
||||
abbrev findRev?_cons := @finRev?_push
|
||||
|
||||
@[simp, grind =] theorem find?_eq_none : find? p xs = none ↔ ∀ x ∈ xs, ¬ p x := by
|
||||
cases xs; simp
|
||||
|
||||
@@ -300,10 +321,16 @@ theorem find?_replicate :
|
||||
find? p (replicate n a) = if p a then some a else none := by
|
||||
simp [find?_replicate, Nat.ne_of_gt h]
|
||||
|
||||
@[deprecated find?_replicate_of_size_pos (since := "2025-03-18")]
|
||||
abbrev find?_mkArray_of_length_pos := @find?_replicate_of_size_pos
|
||||
|
||||
@[simp] theorem find?_replicate_of_pos (h : p a) :
|
||||
find? p (replicate n a) = if n = 0 then none else some a := by
|
||||
simp [find?_replicate, h]
|
||||
|
||||
@[deprecated find?_replicate_of_pos (since := "2025-03-18")]
|
||||
abbrev find?_mkArray_of_pos := @find?_replicate_of_pos
|
||||
|
||||
@[simp] theorem find?_replicate_of_neg (h : ¬ p a) : find? p (replicate n a) = none := by
|
||||
simp [find?_replicate, h]
|
||||
|
||||
@@ -559,6 +586,9 @@ theorem findIdx?_flatten {xss : Array (Array α)} {p : α → Bool} :
|
||||
simp only [List.findIdx?_toArray]
|
||||
simp
|
||||
|
||||
@[deprecated findIdx?_replicate (since := "2025-03-18")]
|
||||
abbrev findIdx?_mkArray := @findIdx?_replicate
|
||||
|
||||
theorem findIdx?_eq_findSome?_zipIdx {xs : Array α} {p : α → Bool} :
|
||||
xs.findIdx? p = xs.zipIdx.findSome? fun ⟨a, i⟩ => if p a then some i else none := by
|
||||
rcases xs with ⟨xs⟩
|
||||
|
||||
@@ -50,6 +50,6 @@ where
|
||||
getLit_eq (xs : Array α) (i : Nat) (h₁ : xs.size = n) (h₂ : i < n) : xs.getLit i h₁ h₂ = getElem xs.toList i ((id (α := xs.toList.length = n) h₁) ▸ h₂) :=
|
||||
rfl
|
||||
go (i : Nat) (hi : i ≤ xs.size) : toListLitAux xs n hsz i hi (xs.toList.drop i) = xs.toList := by
|
||||
induction i <;> simp only [List.drop, toListLitAux, getLit_eq, List.getElem_cons_drop, *]
|
||||
induction i <;> simp only [List.drop, toListLitAux, getLit_eq, List.getElem_cons_drop_succ_eq_drop, *]
|
||||
|
||||
end Array
|
||||
|
||||
@@ -7,6 +7,7 @@ module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Array.Lemmas
|
||||
public import Init.Data.List.Nat.InsertIdx
|
||||
|
||||
public section
|
||||
|
||||
@@ -53,6 +54,11 @@ theorem eraseIdx_insertIdx_self {i : Nat} {xs : Array α} (h : i ≤ xs.size) :
|
||||
rcases xs with ⟨xs⟩
|
||||
simp_all
|
||||
|
||||
@[deprecated eraseIdx_insertIdx_self (since := "2025-06-15")]
|
||||
theorem eraseIdx_insertIdx {i : Nat} {xs : Array α} (h : i ≤ xs.size) :
|
||||
(xs.insertIdx i a).eraseIdx i (by simp; omega) = xs := by
|
||||
simp [eraseIdx_insertIdx_self]
|
||||
|
||||
theorem insertIdx_eraseIdx_of_ge {as : Array α}
|
||||
(w₁ : i < as.size) (w₂ : j ≤ (as.eraseIdx i).size) (h : i ≤ j) :
|
||||
(as.eraseIdx i).insertIdx j a =
|
||||
|
||||
@@ -6,10 +6,17 @@ Authors: Mario Carneiro, Kim Morrison
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Nat.Lemmas
|
||||
public import Init.Data.List.Range
|
||||
public import Init.Data.List.Nat.TakeDrop
|
||||
public import Init.Data.List.Nat.Modify
|
||||
public import Init.Data.List.Nat.Basic
|
||||
public import Init.Data.List.Monadic
|
||||
public import Init.Data.List.OfFn
|
||||
public import Init.Data.Array.Mem
|
||||
public import Init.Data.Array.DecidableEq
|
||||
public import Init.Data.Range.Lemmas
|
||||
public import Init.TacticsExtra
|
||||
public import Init.Data.List.ToArray
|
||||
import all Init.Data.List.Control
|
||||
import all Init.Data.Array.Basic
|
||||
@@ -62,9 +69,6 @@ theorem eq_empty_of_size_eq_zero (h : xs.size = 0) : xs = #[] := by
|
||||
cases xs
|
||||
simp_all
|
||||
|
||||
grind_pattern eq_empty_of_size_eq_zero => xs.size where
|
||||
guard xs.size = 0
|
||||
|
||||
theorem ne_empty_of_size_eq_add_one (h : xs.size = n + 1) : xs ≠ #[] := by
|
||||
cases xs
|
||||
simpa using List.ne_nil_of_length_eq_add_one h
|
||||
@@ -115,8 +119,7 @@ theorem none_eq_getElem?_iff {xs : Array α} {i : Nat} : none = xs[i]? ↔ xs.si
|
||||
theorem getElem?_eq_none {xs : Array α} (h : xs.size ≤ i) : xs[i]? = none := by
|
||||
simp [h]
|
||||
|
||||
grind_pattern Array.getElem?_eq_none => xs.size, xs[i]? where
|
||||
guard xs.size ≤ i
|
||||
grind_pattern Array.getElem?_eq_none => xs.size, xs[i]?
|
||||
|
||||
@[simp] theorem getElem?_eq_getElem {xs : Array α} {i : Nat} (h : i < xs.size) : xs[i]? = some xs[i] :=
|
||||
getElem?_pos ..
|
||||
@@ -249,13 +252,12 @@ theorem back_eq_of_push_eq {a b : α} {xs ys : Array α} (h : xs.push a = ys.pus
|
||||
replace h := List.append_inj_right' h (by simp)
|
||||
simpa using h
|
||||
|
||||
theorem push_eq_push {a b : α} {xs ys : Array α} : xs.push a = ys.push b ↔ a = b ∧ xs = ys := by
|
||||
theorem pop_eq_of_push_eq {a b : α} {xs ys : Array α} (h : xs.push a = ys.push b) : xs = ys := by
|
||||
cases xs
|
||||
cases ys
|
||||
simp [And.comm]
|
||||
|
||||
theorem pop_eq_of_push_eq {a b : α} {xs ys : Array α} (h : xs.push a = ys.push b) : xs = ys :=
|
||||
(push_eq_push.1 h).2
|
||||
simp at h
|
||||
replace h := List.append_inj_left' h (by simp)
|
||||
simp [h]
|
||||
|
||||
theorem push_inj_left {a : α} {xs ys : Array α} : xs.push a = ys.push a ↔ xs = ys :=
|
||||
⟨pop_eq_of_push_eq, fun h => by simp [h]⟩
|
||||
@@ -263,6 +265,15 @@ theorem push_inj_left {a : α} {xs ys : Array α} : xs.push a = ys.push a ↔ xs
|
||||
theorem push_inj_right {a b : α} {xs : Array α} : xs.push a = xs.push b ↔ a = b :=
|
||||
⟨back_eq_of_push_eq, fun h => by simp [h]⟩
|
||||
|
||||
theorem push_eq_push {a b : α} {xs ys : Array α} : xs.push a = ys.push b ↔ a = b ∧ xs = ys := by
|
||||
constructor
|
||||
· intro h
|
||||
exact ⟨back_eq_of_push_eq h, pop_eq_of_push_eq h⟩
|
||||
· rintro ⟨rfl, rfl⟩
|
||||
rfl
|
||||
|
||||
theorem push_eq_append_singleton {as : Array α} {x : α} : as.push x = as ++ #[x] := rfl
|
||||
|
||||
theorem exists_push_of_ne_empty {xs : Array α} (h : xs ≠ #[]) :
|
||||
∃ (ys : Array α) (a : α), xs = ys.push a := by
|
||||
rcases xs with ⟨xs⟩
|
||||
@@ -313,23 +324,41 @@ theorem singleton_inj : #[a] = #[b] ↔ a = b := by
|
||||
@[simp, grind =] theorem size_replicate {n : Nat} {v : α} : (replicate n v).size = n :=
|
||||
List.length_replicate ..
|
||||
|
||||
@[deprecated size_replicate (since := "2025-03-18")]
|
||||
abbrev size_mkArray := @size_replicate
|
||||
|
||||
@[simp] theorem toList_replicate : (replicate n a).toList = List.replicate n a := by
|
||||
simp only [replicate]
|
||||
|
||||
@[deprecated toList_replicate (since := "2025-03-18")]
|
||||
abbrev toList_mkArray := @toList_replicate
|
||||
|
||||
@[simp, grind =] theorem replicate_zero : replicate 0 a = #[] := rfl
|
||||
|
||||
@[deprecated replicate_zero (since := "2025-03-18")]
|
||||
abbrev mkArray_zero := @replicate_zero
|
||||
|
||||
@[grind =]
|
||||
theorem replicate_succ : replicate (n + 1) a = (replicate n a).push a := by
|
||||
apply toList_inj.1
|
||||
simp [List.replicate_succ']
|
||||
|
||||
@[deprecated replicate_succ (since := "2025-03-18")]
|
||||
abbrev mkArray_succ := @replicate_succ
|
||||
|
||||
@[simp, grind =] theorem getElem_replicate {n : Nat} {v : α} {i : Nat} (h : i < (replicate n v).size) :
|
||||
(replicate n v)[i] = v := by simp [← getElem_toList]
|
||||
|
||||
@[deprecated getElem_replicate (since := "2025-03-18")]
|
||||
abbrev getElem_mkArray := @getElem_replicate
|
||||
|
||||
@[grind =] theorem getElem?_replicate {n : Nat} {v : α} {i : Nat} :
|
||||
(replicate n v)[i]? = if i < n then some v else none := by
|
||||
simp [getElem?_def]
|
||||
|
||||
@[deprecated getElem?_replicate (since := "2025-03-18")]
|
||||
abbrev getElem?_mkArray := @getElem?_replicate
|
||||
|
||||
/-! ### mem -/
|
||||
|
||||
@[grind ←]
|
||||
@@ -813,11 +842,6 @@ theorem contains_eq_true_of_mem [BEq α] [ReflBEq α] {a : α} {as : Array α} (
|
||||
theorem elem_iff [BEq α] [LawfulBEq α] {a : α} {xs : Array α} :
|
||||
elem a xs = true ↔ a ∈ xs := ⟨mem_of_contains_eq_true, contains_eq_true_of_mem⟩
|
||||
|
||||
@[grind =]
|
||||
theorem contains_iff_mem [BEq α] [LawfulBEq α] {a : α} {xs : Array α} :
|
||||
xs.contains a = true ↔ a ∈ xs := ⟨mem_of_contains_eq_true, contains_eq_true_of_mem⟩
|
||||
|
||||
@[deprecated contains_iff_mem (since := "2025-10-26")]
|
||||
theorem contains_iff [BEq α] [LawfulBEq α] {a : α} {xs : Array α} :
|
||||
xs.contains a = true ↔ a ∈ xs := ⟨mem_of_contains_eq_true, contains_eq_true_of_mem⟩
|
||||
|
||||
@@ -1057,6 +1081,12 @@ theorem mem_or_eq_of_mem_setIfInBounds
|
||||
cases xs
|
||||
simp
|
||||
|
||||
@[deprecated beq_empty_eq (since := "2025-04-04")]
|
||||
abbrev beq_empty_iff := @beq_empty_eq
|
||||
|
||||
@[deprecated empty_beq_eq (since := "2025-04-04")]
|
||||
abbrev empty_beq_iff := @empty_beq_eq
|
||||
|
||||
@[simp, grind =] theorem push_beq_push [BEq α] {a b : α} {xs ys : Array α} :
|
||||
(xs.push a == ys.push b) = (xs == ys && a == b) := by
|
||||
cases xs
|
||||
@@ -1077,6 +1107,9 @@ theorem size_eq_of_beq [BEq α] {xs ys : Array α} (h : xs == ys) : xs.size = ys
|
||||
rw [Bool.eq_iff_iff]
|
||||
simp +contextual
|
||||
|
||||
@[deprecated replicate_beq_replicate (since := "2025-03-18")]
|
||||
abbrev mkArray_beq_mkArray := @replicate_beq_replicate
|
||||
|
||||
private theorem beq_of_beq_singleton [BEq α] {a b : α} : #[a] == #[b] → a == b := by
|
||||
intro h
|
||||
have : isEqv #[a] #[b] BEq.beq = true := h
|
||||
@@ -1140,7 +1173,7 @@ where
|
||||
aux (i bs) :
|
||||
mapM.map f xs i bs = (xs.toList.drop i).foldlM (fun bs a => bs.push <$> f a) bs := by
|
||||
unfold mapM.map; split
|
||||
· rw [← List.getElem_cons_drop ‹_›]
|
||||
· rw [← List.getElem_cons_drop_succ_eq_drop ‹_›]
|
||||
simp only [aux (i + 1), map_eq_pure_bind, List.foldlM_cons, bind_assoc,
|
||||
pure_bind]
|
||||
rfl
|
||||
@@ -1632,15 +1665,12 @@ theorem filterMap_eq_filter {p : α → Bool} (w : stop = as.size) :
|
||||
cases as
|
||||
simp
|
||||
|
||||
@[grind =]
|
||||
theorem filterMap_filterMap {f : α → Option β} {g : β → Option γ} {xs : Array α} :
|
||||
filterMap g (filterMap f xs) = filterMap (fun x => (f x).bind g) xs := by
|
||||
cases xs
|
||||
simp [List.filterMap_filterMap]
|
||||
|
||||
grind_pattern filterMap_filterMap => filterMap g (filterMap f xs) where
|
||||
f =/= some
|
||||
g =/= some
|
||||
|
||||
@[grind =]
|
||||
theorem map_filterMap {f : α → Option β} {g : β → γ} {xs : Array α} :
|
||||
map g (filterMap f xs) = filterMap (fun x => (f x).map g) xs := by
|
||||
@@ -1695,6 +1725,9 @@ theorem forall_none_of_filterMap_eq_empty (h : filterMap f xs = #[]) : ∀ x ∈
|
||||
cases xs
|
||||
simp
|
||||
|
||||
@[deprecated filterMap_eq_empty_iff (since := "2025-04-04")]
|
||||
abbrev filterMap_eq_nil_iff := @filterMap_eq_empty_iff
|
||||
|
||||
theorem filterMap_eq_push_iff {f : α → Option β} {xs : Array α} {ys : Array β} {b : β} :
|
||||
filterMap f xs = ys.push b ↔ ∃ as a bs,
|
||||
xs = as.push a ++ bs ∧ filterMap f as = ys ∧ f a = some b ∧ (∀ x, x ∈ bs → f x = none) := by
|
||||
@@ -1762,6 +1795,11 @@ theorem toArray_append {xs : List α} {ys : Array α} :
|
||||
|
||||
theorem singleton_eq_toArray_singleton {a : α} : #[a] = [a].toArray := rfl
|
||||
|
||||
@[deprecated empty_append (since := "2025-05-26")]
|
||||
theorem empty_append_fun : ((#[] : Array α) ++ ·) = id := by
|
||||
funext ⟨l⟩
|
||||
simp
|
||||
|
||||
@[simp, grind =] theorem mem_append {a : α} {xs ys : Array α} : a ∈ xs ++ ys ↔ a ∈ xs ∨ a ∈ ys := by
|
||||
simp only [mem_def, toList_append, List.mem_append]
|
||||
|
||||
@@ -1852,9 +1890,6 @@ theorem getElem_of_append {xs ys zs : Array α} (eq : xs = ys.push a ++ zs) (h :
|
||||
|
||||
theorem push_eq_append {a : α} {as : Array α} : as.push a = as ++ #[a] := rfl
|
||||
|
||||
@[deprecated push_eq_append (since := "2025-10-26")]
|
||||
theorem push_eq_append_singleton {as : Array α} {x : α} : as.push x = as ++ #[x] := rfl
|
||||
|
||||
theorem append_inj {xs₁ xs₂ ys₁ ys₂ : Array α} (h : xs₁ ++ ys₁ = xs₂ ++ ys₂) (hl : xs₁.size = xs₂.size) :
|
||||
xs₁ = xs₂ ∧ ys₁ = ys₂ := by
|
||||
rcases xs₁ with ⟨s₁⟩
|
||||
@@ -2055,22 +2090,11 @@ theorem append_eq_map_iff {f : α → β} :
|
||||
| nil => simp
|
||||
| cons as => induction as.toList <;> simp [*]
|
||||
|
||||
@[simp] theorem flatten_toArray_map_toArray {L : List (List α)} :
|
||||
@[simp] theorem flatten_toArray_map {L : List (List α)} :
|
||||
(L.map List.toArray).toArray.flatten = L.flatten.toArray := by
|
||||
apply ext'
|
||||
simp [Function.comp_def]
|
||||
|
||||
@[deprecated flatten_toArray_map_toArray (since := "2025-10-26")]
|
||||
theorem flatten_toArray_map {L : List (List α)} :
|
||||
(L.map List.toArray).toArray.flatten = L.flatten.toArray := by
|
||||
simp
|
||||
|
||||
@[grind =]
|
||||
theorem flatten_map_toArray_toArray {L : List (List α)} :
|
||||
(L.toArray.map List.toArray).flatten = L.flatten.toArray := by
|
||||
simp
|
||||
|
||||
@[deprecated flatten_map_toArray_toArray (since := "2025-10-26")]
|
||||
theorem flatten_map_toArray {L : List (List α)} :
|
||||
(L.toArray.map List.toArray).flatten = L.flatten.toArray := by
|
||||
simp
|
||||
@@ -2117,33 +2141,32 @@ theorem forall_mem_flatten {p : α → Prop} {xss : Array (Array α)} :
|
||||
|
||||
theorem flatten_eq_flatMap {xss : Array (Array α)} : flatten xss = xss.flatMap id := by
|
||||
induction xss using array₂_induction
|
||||
rw [flatten_toArray_map_toArray, List.flatten_eq_flatMap]
|
||||
rw [flatten_toArray_map, List.flatten_eq_flatMap]
|
||||
simp [List.flatMap_map]
|
||||
|
||||
@[simp, grind _=_] theorem map_flatten {f : α → β} {xss : Array (Array α)} :
|
||||
(flatten xss).map f = (map (map f) xss).flatten := by
|
||||
induction xss using array₂_induction with
|
||||
| of xss =>
|
||||
simp only [flatten_toArray_map_toArray, List.map_toArray, List.map_flatten, List.map_map,
|
||||
simp only [flatten_toArray_map, List.map_toArray, List.map_flatten, List.map_map,
|
||||
Function.comp_def]
|
||||
rw [← Function.comp_def, ← List.map_map, flatten_toArray_map_toArray]
|
||||
rw [← Function.comp_def, ← List.map_map, flatten_toArray_map]
|
||||
|
||||
@[simp, grind =] theorem filterMap_flatten {f : α → Option β} {xss : Array (Array α)} {stop : Nat} (w : stop = xss.flatten.size) :
|
||||
filterMap f (flatten xss) 0 stop = flatten (map (filterMap f) xss) := by
|
||||
subst w
|
||||
induction xss using array₂_induction
|
||||
simp only [flatten_toArray_map_toArray, List.size_toArray, List.length_flatten,
|
||||
List.filterMap_toArray', List.filterMap_flatten, List.map_toArray, List.map_map,
|
||||
Function.comp_def]
|
||||
rw [← Function.comp_def, ← List.map_map, flatten_toArray_map_toArray]
|
||||
simp only [flatten_toArray_map, List.size_toArray, List.length_flatten, List.filterMap_toArray',
|
||||
List.filterMap_flatten, List.map_toArray, List.map_map, Function.comp_def]
|
||||
rw [← Function.comp_def, ← List.map_map, flatten_toArray_map]
|
||||
|
||||
@[simp, grind =] theorem filter_flatten {p : α → Bool} {xss : Array (Array α)} {stop : Nat} (w : stop = xss.flatten.size) :
|
||||
filter p (flatten xss) 0 stop = flatten (map (filter p) xss) := by
|
||||
subst w
|
||||
induction xss using array₂_induction
|
||||
simp only [flatten_toArray_map_toArray, List.size_toArray, List.length_flatten,
|
||||
List.filter_toArray', List.filter_flatten, List.map_toArray, List.map_map, Function.comp_def]
|
||||
rw [← Function.comp_def, ← List.map_map, flatten_toArray_map_toArray]
|
||||
simp only [flatten_toArray_map, List.size_toArray, List.length_flatten, List.filter_toArray',
|
||||
List.filter_flatten, List.map_toArray, List.map_map, Function.comp_def]
|
||||
rw [← Function.comp_def, ← List.map_map, flatten_toArray_map]
|
||||
|
||||
theorem flatten_filter_not_isEmpty {xss : Array (Array α)} :
|
||||
flatten (xss.filter fun xs => !xs.isEmpty) = xss.flatten := by
|
||||
@@ -2166,23 +2189,23 @@ theorem flatten_filter_ne_empty [DecidablePred fun xs : Array α => xs ≠ #[]]
|
||||
induction xss using array₂_induction
|
||||
rcases xs with ⟨l⟩
|
||||
have this : [l.toArray] = [l].map List.toArray := by simp
|
||||
simp only [List.push_toArray, flatten_toArray_map_toArray, List.append_toArray]
|
||||
rw [this, ← List.map_append, flatten_toArray_map_toArray]
|
||||
simp only [List.push_toArray, flatten_toArray_map, List.append_toArray]
|
||||
rw [this, ← List.map_append, flatten_toArray_map]
|
||||
simp
|
||||
|
||||
theorem flatten_flatten {xss : Array (Array (Array α))} : flatten (flatten xss) = flatten (map flatten xss) := by
|
||||
induction xss using array₃_induction with
|
||||
| of xss =>
|
||||
rw [flatten_toArray_map_toArray]
|
||||
rw [flatten_toArray_map]
|
||||
have : (xss.map (fun xs => xs.map List.toArray)).flatten = xss.flatten.map List.toArray := by
|
||||
induction xss with
|
||||
| nil => simp
|
||||
| cons xs xss ih =>
|
||||
simp only [List.map_cons, List.flatten_cons, ih, List.map_append]
|
||||
rw [this, flatten_toArray_map_toArray, List.flatten_flatten, ← List.map_toArray, Array.map_map,
|
||||
rw [this, flatten_toArray_map, List.flatten_flatten, ← List.map_toArray, Array.map_map,
|
||||
← List.map_toArray, map_map, Function.comp_def]
|
||||
simp only [Function.comp_apply, flatten_toArray_map_toArray]
|
||||
rw [List.map_toArray, ← Function.comp_def, ← List.map_map, flatten_toArray_map_toArray]
|
||||
simp only [Function.comp_apply, flatten_toArray_map]
|
||||
rw [List.map_toArray, ← Function.comp_def, ← List.map_map, flatten_toArray_map]
|
||||
|
||||
theorem flatten_eq_push_iff {xss : Array (Array α)} {ys : Array α} {y : α} :
|
||||
xss.flatten = ys.push y ↔
|
||||
@@ -2191,13 +2214,13 @@ theorem flatten_eq_push_iff {xss : Array (Array α)} {ys : Array α} {y : α} :
|
||||
induction xss using array₂_induction with
|
||||
| of xs =>
|
||||
rcases ys with ⟨ys⟩
|
||||
rw [flatten_toArray_map_toArray, List.push_toArray, mk.injEq, List.flatten_eq_append_iff]
|
||||
rw [flatten_toArray_map, List.push_toArray, mk.injEq, List.flatten_eq_append_iff]
|
||||
constructor
|
||||
· rintro (⟨as, bs, rfl, rfl, h⟩ | ⟨as, bs, c, cs, ds, rfl, rfl, h⟩)
|
||||
· rw [List.singleton_eq_flatten_iff] at h
|
||||
obtain ⟨xs, ys, rfl, h₁, h₂⟩ := h
|
||||
exact ⟨((as ++ xs).map List.toArray).toArray, #[], (ys.map List.toArray).toArray, by simp,
|
||||
by simpa using h₂, by rw [flatten_toArray_map_toArray]; simpa⟩
|
||||
by simpa using h₂, by rw [flatten_toArray_map]; simpa⟩
|
||||
· rw [List.singleton_eq_append_iff] at h
|
||||
obtain (⟨h₁, h₂⟩ | ⟨h₁, h₂⟩) := h
|
||||
· simp at h₁
|
||||
@@ -2230,8 +2253,8 @@ theorem push_eq_flatten_iff {xss : Array (Array α)} {ys : Array α} {y : α} :
|
||||
-- zs = cs ++ ds.flatten := by sorry
|
||||
|
||||
|
||||
/-- Two arrays of arrays are equal iff their flattens coincide, as well as the sizes of the
|
||||
arrays. -/
|
||||
/-- Two arrays of subarrays are equal iff their flattens coincide, as well as the sizes of the
|
||||
subarrays. -/
|
||||
theorem eq_iff_flatten_eq {xss₁ xss₂ : Array (Array α)} :
|
||||
xss₁ = xss₂ ↔ xss₁.flatten = xss₂.flatten ∧ map size xss₁ = map size xss₂ := by
|
||||
cases xss₁ using array₂_induction with
|
||||
@@ -2242,12 +2265,18 @@ theorem eq_iff_flatten_eq {xss₁ xss₂ : Array (Array α)} :
|
||||
rw [List.map_inj_right]
|
||||
simp +contextual
|
||||
|
||||
theorem flatten_toArray_map_toArray {xss : List (List α)} :
|
||||
(xss.map List.toArray).toArray.flatten = xss.flatten.toArray := by
|
||||
simp
|
||||
|
||||
/-! ### flatMap -/
|
||||
|
||||
theorem flatMap_def {xs : Array α} {f : α → Array β} : xs.flatMap f = flatten (map f xs) := by
|
||||
rcases xs with ⟨l⟩
|
||||
simp [flatten_toArray, Function.comp_def, List.flatMap_def]
|
||||
|
||||
@[simp, grind =] theorem flatMap_empty {β} {f : α → Array β} : (#[] : Array α).flatMap f = #[] := rfl
|
||||
|
||||
theorem flatMap_toList {xs : Array α} {f : α → List β} :
|
||||
xs.toList.flatMap f = (xs.flatMap (fun a => (f a).toArray)).toList := by
|
||||
rcases xs with ⟨l⟩
|
||||
@@ -2258,7 +2287,6 @@ theorem flatMap_toList {xs : Array α} {f : α → List β} :
|
||||
rcases xs with ⟨l⟩
|
||||
simp
|
||||
|
||||
@[deprecated List.flatMap_toArray_cons (since := "2025-10-29")]
|
||||
theorem flatMap_toArray_cons {β} {f : α → Array β} {a : α} {as : List α} :
|
||||
(a :: as).toArray.flatMap f = f a ++ as.toArray.flatMap f := by
|
||||
simp [flatMap]
|
||||
@@ -2269,7 +2297,6 @@ theorem flatMap_toArray_cons {β} {f : α → Array β} {a : α} {as : List α}
|
||||
intro cs
|
||||
induction as generalizing cs <;> simp_all
|
||||
|
||||
@[deprecated List.flatMap_toArray (since := "2025-10-29")]
|
||||
theorem flatMap_toArray {β} {f : α → Array β} {as : List α} :
|
||||
as.toArray.flatMap f = (as.flatMap (fun a => (f a).toList)).toArray := by
|
||||
simp
|
||||
@@ -2370,44 +2397,77 @@ theorem flatMap_eq_foldl {f : α → Array β} {xs : Array α} :
|
||||
|
||||
@[simp] theorem replicate_one : replicate 1 a = #[a] := rfl
|
||||
|
||||
@[deprecated replicate_one (since := "2025-03-18")]
|
||||
abbrev mkArray_one := @replicate_one
|
||||
|
||||
/-- Variant of `replicate_succ` that prepends `a` at the beginning of the array. -/
|
||||
theorem replicate_succ' : replicate (n + 1) a = #[a] ++ replicate n a := by
|
||||
apply Array.ext'
|
||||
simp [List.replicate_succ]
|
||||
|
||||
@[deprecated replicate_succ' (since := "2025-03-18")]
|
||||
abbrev mkArray_succ' := @replicate_succ'
|
||||
|
||||
@[simp, grind =] theorem mem_replicate {a b : α} {n} : b ∈ replicate n a ↔ n ≠ 0 ∧ b = a := by
|
||||
unfold replicate
|
||||
simp only [List.mem_toArray, List.mem_replicate]
|
||||
|
||||
@[deprecated mem_replicate (since := "2025-03-18")]
|
||||
abbrev mem_mkArray := @mem_replicate
|
||||
|
||||
@[grind →] theorem eq_of_mem_replicate {a b : α} {n} (h : b ∈ replicate n a) : b = a := (mem_replicate.1 h).2
|
||||
|
||||
@[deprecated eq_of_mem_mkArray (since := "2025-03-18")]
|
||||
abbrev eq_of_mem_mkArray := @eq_of_mem_replicate
|
||||
|
||||
theorem forall_mem_replicate {p : α → Prop} {a : α} {n} :
|
||||
(∀ b, b ∈ replicate n a → p b) ↔ n = 0 ∨ p a := by
|
||||
cases n <;> simp [mem_replicate]
|
||||
|
||||
@[deprecated forall_mem_replicate (since := "2025-03-18")]
|
||||
abbrev forall_mem_mkArray := @forall_mem_replicate
|
||||
|
||||
@[simp] theorem replicate_succ_ne_empty {n : Nat} {a : α} : replicate (n+1) a ≠ #[] := by
|
||||
simp [replicate_succ]
|
||||
|
||||
@[deprecated replicate_succ_ne_empty (since := "2025-03-18")]
|
||||
abbrev mkArray_succ_ne_empty := @replicate_succ_ne_empty
|
||||
|
||||
@[simp] theorem replicate_eq_empty_iff {n : Nat} {a : α} : replicate n a = #[] ↔ n = 0 := by
|
||||
cases n <;> simp
|
||||
|
||||
@[deprecated replicate_eq_empty_iff (since := "2025-03-18")]
|
||||
abbrev mkArray_eq_empty_iff := @replicate_eq_empty_iff
|
||||
|
||||
@[simp] theorem replicate_inj : replicate n a = replicate m b ↔ n = m ∧ (n = 0 ∨ a = b) := by
|
||||
rw [← toList_inj]
|
||||
simp
|
||||
|
||||
@[deprecated replicate_inj (since := "2025-03-18")]
|
||||
abbrev mkArray_inj := @replicate_inj
|
||||
|
||||
theorem eq_replicate_of_mem {a : α} {xs : Array α} (h : ∀ (b) (_ : b ∈ xs), b = a) : xs = replicate xs.size a := by
|
||||
rw [← toList_inj]
|
||||
simpa using List.eq_replicate_of_mem (by simpa using h)
|
||||
|
||||
@[deprecated eq_replicate_of_mem (since := "2025-03-18")]
|
||||
abbrev eq_mkArray_of_mem := @eq_replicate_of_mem
|
||||
|
||||
theorem eq_replicate_iff {a : α} {n} {xs : Array α} :
|
||||
xs = replicate n a ↔ xs.size = n ∧ ∀ (b) (_ : b ∈ xs), b = a := by
|
||||
rw [← toList_inj]
|
||||
simpa using List.eq_replicate_iff (l := xs.toList)
|
||||
|
||||
@[deprecated eq_replicate_iff (since := "2025-03-18")]
|
||||
abbrev eq_mkArray_iff := @eq_replicate_iff
|
||||
|
||||
theorem map_eq_replicate_iff {xs : Array α} {f : α → β} {b : β} :
|
||||
xs.map f = replicate xs.size b ↔ ∀ x ∈ xs, f x = b := by
|
||||
simp [eq_replicate_iff]
|
||||
|
||||
@[deprecated map_eq_replicate_iff (since := "2025-03-18")]
|
||||
abbrev map_eq_mkArray_iff := @map_eq_replicate_iff
|
||||
|
||||
@[simp] theorem map_const {xs : Array α} {b : β} : map (Function.const α b) xs = replicate xs.size b :=
|
||||
map_eq_replicate_iff.mpr fun _ _ => rfl
|
||||
|
||||
@@ -2424,86 +2484,143 @@ theorem map_const' {xs : Array α} {b : β} : map (fun _ => b) xs = replicate xs
|
||||
apply Array.ext'
|
||||
simp
|
||||
|
||||
@[deprecated set_replicate_self (since := "2025-03-18")]
|
||||
abbrev set_mkArray_self := @set_replicate_self
|
||||
|
||||
@[simp] theorem setIfInBounds_replicate_self : (replicate n a).setIfInBounds i a = replicate n a := by
|
||||
apply Array.ext'
|
||||
simp
|
||||
|
||||
@[deprecated setIfInBounds_replicate_self (since := "2025-03-18")]
|
||||
abbrev setIfInBounds_mkArray_self := @setIfInBounds_replicate_self
|
||||
|
||||
@[simp] theorem replicate_append_replicate : replicate n a ++ replicate m a = replicate (n + m) a := by
|
||||
apply Array.ext'
|
||||
simp
|
||||
|
||||
@[deprecated replicate_append_replicate (since := "2025-03-18")]
|
||||
abbrev mkArray_append_mkArray := @replicate_append_replicate
|
||||
|
||||
theorem append_eq_replicate_iff {xs ys : Array α} {a : α} :
|
||||
xs ++ ys = replicate n a ↔
|
||||
xs.size + ys.size = n ∧ xs = replicate xs.size a ∧ ys = replicate ys.size a := by
|
||||
simp [← toList_inj, List.append_eq_replicate_iff]
|
||||
|
||||
@[deprecated append_eq_replicate_iff (since := "2025-03-18")]
|
||||
abbrev append_eq_mkArray_iff := @append_eq_replicate_iff
|
||||
|
||||
theorem replicate_eq_append_iff {xs ys : Array α} {a : α} :
|
||||
replicate n a = xs ++ ys ↔
|
||||
xs.size + ys.size = n ∧ xs = replicate xs.size a ∧ ys = replicate ys.size a := by
|
||||
rw [eq_comm, append_eq_replicate_iff]
|
||||
|
||||
@[deprecated replicate_eq_append_iff (since := "2025-03-18")]
|
||||
abbrev replicate_eq_mkArray_iff := @replicate_eq_append_iff
|
||||
|
||||
@[simp] theorem map_replicate : (replicate n a).map f = replicate n (f a) := by
|
||||
apply Array.ext'
|
||||
simp
|
||||
|
||||
@[deprecated map_replicate (since := "2025-03-18")]
|
||||
abbrev map_mkArray := @map_replicate
|
||||
|
||||
@[grind =] theorem filter_replicate (w : stop = n) :
|
||||
(replicate n a).filter p 0 stop = if p a then replicate n a else #[] := by
|
||||
apply Array.ext'
|
||||
simp only [w]
|
||||
split <;> simp_all
|
||||
|
||||
@[deprecated filter_replicate (since := "2025-03-18")]
|
||||
abbrev filter_mkArray := @filter_replicate
|
||||
|
||||
@[simp] theorem filter_replicate_of_pos (w : stop = n) (h : p a) :
|
||||
(replicate n a).filter p 0 stop = replicate n a := by
|
||||
simp [filter_replicate, h, w]
|
||||
|
||||
@[deprecated filter_replicate_of_pos (since := "2025-03-18")]
|
||||
abbrev filter_mkArray_of_pos := @filter_replicate_of_pos
|
||||
|
||||
@[simp] theorem filter_replicate_of_neg (w : stop = n) (h : ¬ p a) :
|
||||
(replicate n a).filter p 0 stop = #[] := by
|
||||
simp [filter_replicate, h, w]
|
||||
|
||||
@[deprecated filter_replicate_of_neg (since := "2025-03-18")]
|
||||
abbrev filter_mkArray_of_neg := @filter_replicate_of_neg
|
||||
|
||||
theorem filterMap_replicate {f : α → Option β} (w : stop = n := by simp) :
|
||||
(replicate n a).filterMap f 0 stop = match f a with | none => #[] | .some b => replicate n b := by
|
||||
apply Array.ext'
|
||||
simp only [w, size_replicate, toList_filterMap', toList_replicate, List.filterMap_replicate]
|
||||
split <;> simp_all
|
||||
|
||||
@[deprecated filterMap_replicate (since := "2025-03-18")]
|
||||
abbrev filterMap_mkArray := @filterMap_replicate
|
||||
|
||||
-- This is not a useful `simp` lemma because `b` is unknown.
|
||||
theorem filterMap_replicate_of_some {f : α → Option β} (h : f a = some b) :
|
||||
(replicate n a).filterMap f = replicate n b := by
|
||||
simp [filterMap_replicate, h]
|
||||
|
||||
@[deprecated filterMap_replicate_of_some (since := "2025-03-18")]
|
||||
abbrev filterMap_mkArray_of_some := @filterMap_replicate_of_some
|
||||
|
||||
@[simp] theorem filterMap_replicate_of_isSome {f : α → Option β} (h : (f a).isSome) :
|
||||
(replicate n a).filterMap f = replicate n (Option.get _ h) := by
|
||||
match w : f a, h with
|
||||
| some b, _ => simp [filterMap_replicate, w]
|
||||
|
||||
@[deprecated filterMap_replicate_of_isSome (since := "2025-03-18")]
|
||||
abbrev filterMap_mkArray_of_isSome := @filterMap_replicate_of_isSome
|
||||
|
||||
@[simp] theorem filterMap_replicate_of_none {f : α → Option β} (h : f a = none) :
|
||||
(replicate n a).filterMap f = #[] := by
|
||||
simp [filterMap_replicate, h]
|
||||
|
||||
@[deprecated filterMap_replicate_of_none (since := "2025-03-18")]
|
||||
abbrev filterMap_mkArray_of_none := @filterMap_replicate_of_none
|
||||
|
||||
@[simp] theorem flatten_replicate_empty : (replicate n (#[] : Array α)).flatten = #[] := by
|
||||
rw [← toList_inj]
|
||||
simp
|
||||
|
||||
@[deprecated flatten_replicate_empty (since := "2025-03-18")]
|
||||
abbrev flatten_mkArray_empty := @flatten_replicate_empty
|
||||
|
||||
@[simp] theorem flatten_replicate_singleton : (replicate n #[a]).flatten = replicate n a := by
|
||||
rw [← toList_inj]
|
||||
simp
|
||||
|
||||
@[deprecated flatten_replicate_singleton (since := "2025-03-18")]
|
||||
abbrev flatten_mkArray_singleton := @flatten_replicate_singleton
|
||||
|
||||
@[simp] theorem flatten_replicate_replicate : (replicate n (replicate m a)).flatten = replicate (n * m) a := by
|
||||
rw [← toList_inj]
|
||||
simp
|
||||
|
||||
@[deprecated flatten_replicate_replicate (since := "2025-03-18")]
|
||||
abbrev flatten_mkArray_replicate := @flatten_replicate_replicate
|
||||
|
||||
theorem flatMap_replicate {f : α → Array β} : (replicate n a).flatMap f = (replicate n (f a)).flatten := by
|
||||
rw [← toList_inj]
|
||||
simp [List.flatMap_replicate]
|
||||
|
||||
@[deprecated flatMap_replicate (since := "2025-03-18")]
|
||||
abbrev flatMap_mkArray := @flatMap_replicate
|
||||
|
||||
@[simp] theorem isEmpty_replicate : (replicate n a).isEmpty = decide (n = 0) := by
|
||||
rw [← List.toArray_replicate, List.isEmpty_toArray]
|
||||
simp
|
||||
|
||||
@[deprecated isEmpty_replicate (since := "2025-03-18")]
|
||||
abbrev isEmpty_mkArray := @isEmpty_replicate
|
||||
|
||||
@[simp] theorem sum_replicate_nat {n : Nat} {a : Nat} : (replicate n a).sum = n * a := by
|
||||
rw [← List.toArray_replicate, List.sum_toArray]
|
||||
simp
|
||||
|
||||
@[deprecated sum_replicate_nat (since := "2025-03-18")]
|
||||
abbrev sum_mkArray_nat := @sum_replicate_nat
|
||||
|
||||
/-! ### Preliminaries about `swap` needed for `reverse`. -/
|
||||
|
||||
@[grind =]
|
||||
@@ -2545,8 +2662,8 @@ theorem getElem?_swap {xs : Array α} {i j : Nat} (hi hj) {k : Nat} : (xs.swap i
|
||||
split <;> rename_i h₃
|
||||
· simp only [← h₃, Nat.not_le.2 (Nat.lt_succ_self _), Nat.le_refl, false_and]
|
||||
exact (List.getElem?_reverse' (Eq.trans (by simp +arith) h)).symm
|
||||
simp only [Nat.succ_le_iff, Nat.lt_iff_le_and_ne.trans (and_iff_left h₃),
|
||||
Nat.lt_succ_iff.symm.trans (Nat.lt_iff_le_and_ne.trans (and_iff_left (Ne.symm h₂)))]
|
||||
simp only [Nat.succ_le, Nat.lt_iff_le_and_ne.trans (and_iff_left h₃),
|
||||
Nat.lt_succ.symm.trans (Nat.lt_iff_le_and_ne.trans (and_iff_left (Ne.symm h₂)))]
|
||||
· rw [H]; split <;> rename_i h₂
|
||||
· cases Nat.le_antisymm (Nat.not_lt.1 h₁) (Nat.le_trans h₂.1 h₂.2)
|
||||
cases Nat.le_antisymm h₂.1 h₂.2
|
||||
@@ -2561,7 +2678,7 @@ theorem getElem?_swap {xs : Array α} {i j : Nat} (hi hj) {k : Nat} : (xs.swap i
|
||||
split
|
||||
· rfl
|
||||
· rename_i h
|
||||
simp only [← show k < _ + 1 ↔ _ from Nat.lt_succ_iff (n := xs.size - 1), this, Nat.zero_le,
|
||||
simp only [← show k < _ + 1 ↔ _ from Nat.lt_succ (n := xs.size - 1), this, Nat.zero_le,
|
||||
true_and, Nat.not_lt] at h
|
||||
rw [List.getElem?_eq_none_iff.2 ‹_›, List.getElem?_eq_none_iff.2 (xs.toList.length_reverse ▸ ‹_›)]
|
||||
|
||||
@@ -2690,6 +2807,9 @@ theorem flatten_reverse {xss : Array (Array α)} :
|
||||
rw [← toList_inj]
|
||||
simp
|
||||
|
||||
@[deprecated reverse_replicate (since := "2025-03-18")]
|
||||
abbrev reverse_mkArray := @reverse_replicate
|
||||
|
||||
/-! ### extract -/
|
||||
|
||||
theorem extract_loop_zero {xs ys : Array α} {start : Nat} : extract.loop xs 0 start ys = ys := by
|
||||
@@ -2709,8 +2829,8 @@ theorem extract_loop_eq_aux {xs ys : Array α} {size start : Nat} :
|
||||
| zero => rw [extract_loop_zero, extract_loop_zero, append_empty]
|
||||
| succ size ih =>
|
||||
if h : start < xs.size then
|
||||
rw [extract_loop_succ (h := h), ih, push_eq_append]
|
||||
rw [extract_loop_succ (h := h), ih (ys := #[].push _), push_eq_append, empty_append]
|
||||
rw [extract_loop_succ (h := h), ih, push_eq_append_singleton]
|
||||
rw [extract_loop_succ (h := h), ih (ys := #[].push _), push_eq_append_singleton, empty_append]
|
||||
rw [append_assoc]
|
||||
else
|
||||
rw [extract_loop_of_ge (h := Nat.le_of_not_lt h)]
|
||||
@@ -3247,6 +3367,14 @@ rather than `(arr.push a).size` as the argument.
|
||||
l.foldl (fun xs x => xs.push x) xs = xs ++ l.toArray := by
|
||||
simpa using List.foldl_push_eq_append (f := id)
|
||||
|
||||
@[deprecated _root_.List.foldl_push_eq_append' (since := "2025-05-18")]
|
||||
theorem _root_.List.foldl_push {l : List α} {as : Array α} : l.foldl Array.push as = as ++ l.toArray := by
|
||||
induction l generalizing as <;> simp [*]
|
||||
|
||||
@[deprecated _root_.List.foldr_push_eq_append' (since := "2025-05-18")]
|
||||
theorem _root_.List.foldr_push {l : List α} {as : Array α} : l.foldr (fun a bs => push bs a) as = as ++ l.reverse.toArray := by
|
||||
rw [List.foldr_eq_foldl_reverse, List.foldl_push_eq_append']
|
||||
|
||||
-- TODO: a multi-pattern is being selected there because E-matching does not go inside lambdas.
|
||||
@[simp, grind! ←] theorem foldr_append_eq_append {xs : Array α} {f : α → Array β} {ys : Array β} :
|
||||
xs.foldr (f · ++ ·) ys = (xs.map f).flatten ++ ys := by
|
||||
@@ -3319,16 +3447,6 @@ theorem foldr_filterMap {f : α → Option β} {g : β → γ → γ} {xs : Arra
|
||||
(xs.filterMap f).foldr g init = xs.foldr (fun x y => match f x with | some b => g b y | none => y) init := by
|
||||
simp [foldr_filterMap']
|
||||
|
||||
theorem foldl_flatMap {f : α → Array β} {g : γ → β → γ} {xs : Array α} {init : γ} :
|
||||
(xs.flatMap f).foldl g init = xs.foldl (fun acc x => (f x).foldl g acc) init := by
|
||||
rcases xs with ⟨l⟩
|
||||
simp [List.foldl_flatMap]
|
||||
|
||||
theorem foldr_flatMap {f : α → Array β} {g : β → γ → γ} {xs : Array α} {init : γ} :
|
||||
(xs.flatMap f).foldr g init = xs.foldr (fun x acc => (f x).foldr g acc) init := by
|
||||
rcases xs with ⟨l⟩
|
||||
simp [List.foldr_flatMap]
|
||||
|
||||
theorem foldl_map_hom' {g : α → β} {f : α → α → α} {f' : β → β → β} {a : α} {xs : Array α}
|
||||
{stop : Nat} (h : ∀ x y, f' (g x) (g y) = g (f x y)) (w : stop = xs.size) :
|
||||
(xs.map g).foldl f' (g a) 0 stop = g (xs.foldl f a) := by
|
||||
@@ -3546,6 +3664,11 @@ theorem mem_of_back? {xs : Array α} {a : α} (h : xs.back? = some a) : a ∈ xs
|
||||
rcases ys with ⟨ys⟩
|
||||
simp only [List.append_toArray, List.back_toArray, List.getLast_append, List.isEmpty_iff,
|
||||
List.isEmpty_toArray]
|
||||
split
|
||||
· rw [dif_pos]
|
||||
simpa only [List.isEmpty_toArray]
|
||||
· rw [dif_neg]
|
||||
simpa only [List.isEmpty_toArray]
|
||||
|
||||
theorem back_append_right {xs ys : Array α} (h : 0 < ys.size) :
|
||||
(xs ++ ys).back (by simp; omega) = ys.back h := by
|
||||
@@ -3596,9 +3719,15 @@ theorem back?_replicate {a : α} {n : Nat} :
|
||||
rw [replicate_eq_toArray_replicate]
|
||||
simp only [List.back?_toArray, List.getLast?_replicate]
|
||||
|
||||
@[deprecated back?_replicate (since := "2025-03-18")]
|
||||
abbrev back?_mkArray := @back?_replicate
|
||||
|
||||
@[simp] theorem back_replicate {xs : Array α} (w : 0 < n) : (replicate n xs).back (by simpa using w) = xs := by
|
||||
simp [back_eq_getElem]
|
||||
|
||||
@[deprecated back_replicate (since := "2025-03-18")]
|
||||
abbrev back_mkArray := @back_replicate
|
||||
|
||||
/-! ## Additional operations -/
|
||||
|
||||
/-! ### leftpad -/
|
||||
@@ -3616,6 +3745,9 @@ theorem size_rightpad {n : Nat} {a : α} {xs : Array α} :
|
||||
|
||||
theorem elem_push_self [BEq α] [LawfulBEq α] {xs : Array α} {a : α} : (xs.push a).elem a = true := by simp
|
||||
|
||||
@[deprecated elem_push_self (since := "2025-04-04")]
|
||||
abbrev elem_cons_self := @elem_push_self
|
||||
|
||||
theorem contains_eq_any_beq [BEq α] {xs : Array α} {a : α} : xs.contains a = xs.any (a == ·) := by
|
||||
rcases xs with ⟨xs⟩
|
||||
simp [List.contains_eq_any_beq]
|
||||
@@ -3629,6 +3761,11 @@ theorem contains_iff_exists_mem_beq [BEq α] {xs : Array α} {a : α} :
|
||||
-- With `LawfulBEq α`, it would be better to use `contains_iff_mem` directly.
|
||||
grind_pattern contains_iff_exists_mem_beq => xs.contains a
|
||||
|
||||
@[grind _=_]
|
||||
theorem contains_iff_mem [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
|
||||
xs.contains a ↔ a ∈ xs := by
|
||||
simp
|
||||
|
||||
@[simp, grind =]
|
||||
theorem contains_toList [BEq α] {xs : Array α} {x : α} :
|
||||
xs.toList.contains x = xs.contains x := by
|
||||
@@ -3688,6 +3825,9 @@ theorem pop_append {xs ys : Array α} :
|
||||
@[simp, grind =] theorem pop_replicate {n : Nat} {a : α} : (replicate n a).pop = replicate (n - 1) a := by
|
||||
ext <;> simp
|
||||
|
||||
@[deprecated pop_replicate (since := "2025-03-18")]
|
||||
abbrev pop_mkArray := @pop_replicate
|
||||
|
||||
/-! ## Logic -/
|
||||
|
||||
/-! ### any / all -/
|
||||
@@ -3917,10 +4057,16 @@ theorem all_filterMap {xs : Array α} {f : α → Option β} {p : β → Bool} :
|
||||
(replicate n a).any f = if n = 0 then false else f a := by
|
||||
induction n <;> simp_all [replicate_succ']
|
||||
|
||||
@[deprecated any_replicate (since := "2025-03-18")]
|
||||
abbrev any_mkArray := @any_replicate
|
||||
|
||||
@[simp] theorem all_replicate {n : Nat} {a : α} :
|
||||
(replicate n a).all f = if n = 0 then true else f a := by
|
||||
induction n <;> simp_all +contextual [replicate_succ']
|
||||
|
||||
@[deprecated all_replicate (since := "2025-03-18")]
|
||||
abbrev all_mkArray := @all_replicate
|
||||
|
||||
/-! ### modify -/
|
||||
|
||||
@[simp, grind =] theorem size_modify {xs : Array α} {i : Nat} {f : α → α} : (xs.modify i f).size = xs.size := by
|
||||
@@ -3957,29 +4103,28 @@ theorem getElem_modify_of_ne {xs : Array α} {i : Nat} (h : i ≠ j)
|
||||
|
||||
/-! ### swap -/
|
||||
|
||||
@[grind =]
|
||||
theorem getElem_swap {xs : Array α} {i j : Nat} (hi hj) {k : Nat} (hk : k < (xs.swap i j hi hj).size) :
|
||||
(xs.swap i j hi hj)[k] = if k = i then xs[j] else if k = j then xs[i] else xs[k]'(by simp_all) := by
|
||||
simp only [swap_def, getElem_set, eq_comm (a := k)]
|
||||
split <;> split <;> simp_all
|
||||
|
||||
@[simp] theorem getElem_swap_right {xs : Array α} {i j : Nat} {hi hj} :
|
||||
(xs.swap i j hi hj)[j]'(by simpa using hj) = xs[i] := by
|
||||
simp +contextual [getElem_swap]
|
||||
simp [swap_def]
|
||||
|
||||
@[simp] theorem getElem_swap_left {xs : Array α} {i j : Nat} {hi hj} :
|
||||
(xs.swap i j hi hj)[i]'(by simpa using hi) = xs[j] := by
|
||||
simp [getElem_swap]
|
||||
simp +contextual [swap_def, getElem_set]
|
||||
|
||||
@[simp] theorem getElem_swap_of_ne {xs : Array α} {i j : Nat} {hi hj}
|
||||
{h : k < (xs.swap i j hi hj).size} (hi' : k ≠ i) (hj' : k ≠ j) :
|
||||
(xs.swap i j hi hj)[k] = xs[k]'(by simp_all) := by
|
||||
simp [getElem_swap, hi', hj']
|
||||
@[simp] theorem getElem_swap_of_ne {xs : Array α} {i j : Nat} {hi hj} (hp : k < xs.size)
|
||||
(hi' : k ≠ i) (hj' : k ≠ j) : (xs.swap i j hi hj)[k]'(xs.size_swap .. |>.symm ▸ hp) = xs[k] := by
|
||||
simp [swap_def, getElem_set, hi'.symm, hj'.symm]
|
||||
|
||||
@[deprecated getElem_swap (since := "2025-10-10")]
|
||||
theorem getElem_swap' {xs : Array α} {i j : Nat} {hi hj} {k : Nat} (hk : k < xs.size) :
|
||||
(xs.swap i j hi hj)[k]'(by simp_all) = if k = i then xs[j] else if k = j then xs[i] else xs[k] :=
|
||||
getElem_swap _ _ _
|
||||
(xs.swap i j hi hj)[k]'(by simp_all) = if k = i then xs[j] else if k = j then xs[i] else xs[k] := by
|
||||
split
|
||||
· simp_all only [getElem_swap_left]
|
||||
· split <;> simp_all
|
||||
|
||||
@[grind =]
|
||||
theorem getElem_swap {xs : Array α} {i j : Nat} (hi hj) {k : Nat} (hk : k < (xs.swap i j hi hj).size) :
|
||||
(xs.swap i j hi hj)[k] = if k = i then xs[j] else if k = j then xs[i] else xs[k]'(by simp_all) := by
|
||||
apply getElem_swap'
|
||||
|
||||
@[simp] theorem swap_swap {xs : Array α} {i j : Nat} (hi hj) :
|
||||
(xs.swap i j hi hj).swap i j ((xs.size_swap ..).symm ▸ hi) ((xs.size_swap ..).symm ▸ hj) = xs := by
|
||||
@@ -4000,66 +4145,8 @@ theorem swap_comm {xs : Array α} {i j : Nat} (hi hj) : xs.swap i j hi hj = xs.s
|
||||
· split <;> simp_all
|
||||
· split <;> simp_all
|
||||
|
||||
/-! ### swapIfInBounds -/
|
||||
|
||||
@[grind =] theorem swapIfInBounds_def {xs : Array α} {i j : Nat} :
|
||||
xs.swapIfInBounds i j = if h₁ : i < xs.size then
|
||||
if h₂ : j < xs.size then swap xs i j else xs else xs := rfl
|
||||
|
||||
@[simp, grind =] theorem size_swapIfInBounds {xs : Array α} {i j : Nat} :
|
||||
(xs.swapIfInBounds i j).size = xs.size := by
|
||||
unfold swapIfInBounds; split <;> (try split) <;> simp [size_swap]
|
||||
|
||||
@[grind =] theorem getElem_swapIfInBounds {xs : Array α} {i j k : Nat}
|
||||
(hk : k < (xs.swapIfInBounds i j).size) :
|
||||
(xs.swapIfInBounds i j)[k] =
|
||||
if h₁ : k = i ∧ j < xs.size then xs[j]'h₁.2 else if h₂ : k = j ∧ i < xs.size then xs[i]'h₂.2
|
||||
else xs[k]'(by simp_all) := by
|
||||
rw [size_swapIfInBounds] at hk
|
||||
unfold swapIfInBounds
|
||||
split <;> rename_i hi
|
||||
· split <;> rename_i hj
|
||||
· simp only [hi, hj, and_true]
|
||||
exact getElem_swap _ _ _
|
||||
· simp only [hi, hj, and_true, and_false, dite_false]
|
||||
split <;> simp_all
|
||||
· simp only [hi, and_false, dite_false]
|
||||
split <;> simp_all
|
||||
|
||||
@[simp]
|
||||
theorem getElem_swapIfInBounds_of_size_le_left {xs : Array α} {i j k : Nat} (hi : xs.size ≤ i)
|
||||
(hk : k < (xs.swapIfInBounds i j).size) :
|
||||
(xs.swapIfInBounds i j)[k] = xs[k]'(Nat.lt_of_lt_of_eq hk size_swapIfInBounds) := by
|
||||
have h₁ : k ≠ i := Nat.ne_of_lt <| Nat.lt_of_lt_of_le hk <|
|
||||
Nat.le_trans (Nat.le_of_eq (size_swapIfInBounds)) hi
|
||||
have h₂ : ¬ (i < xs.size) := Nat.not_lt_of_le hi
|
||||
simp [getElem_swapIfInBounds, h₁, h₂]
|
||||
|
||||
@[simp]
|
||||
theorem getElem_swapIfInBounds_of_size_le_right {xs : Array α} {i j k : Nat} (hj : xs.size ≤ j)
|
||||
(hk : k < (xs.swapIfInBounds i j).size) :
|
||||
(xs.swapIfInBounds i j)[k] = xs[k]'(Nat.lt_of_lt_of_eq hk size_swapIfInBounds) := by
|
||||
have h₁ : ¬ (j < xs.size) := Nat.not_lt_of_le hj
|
||||
have h₂ : k ≠ j := Nat.ne_of_lt <| Nat.lt_of_lt_of_le hk <|
|
||||
Nat.le_trans (Nat.le_of_eq (size_swapIfInBounds)) hj
|
||||
simp [getElem_swapIfInBounds, h₁, h₂]
|
||||
|
||||
@[simp]
|
||||
theorem getElem_swapIfInBounds_left {xs : Array α} {i j : Nat} (hj : j < xs.size)
|
||||
(hi : i < (xs.swapIfInBounds i j).size) : (xs.swapIfInBounds i j)[i] = xs[j] := by
|
||||
simp [getElem_swapIfInBounds, hj]
|
||||
|
||||
@[simp]
|
||||
theorem getElem_swapIfInBounds_right {xs : Array α} {i j : Nat} (hi : i < xs.size)
|
||||
(hj : j < (xs.swapIfInBounds i j).size) :
|
||||
(xs.swapIfInBounds i j)[j] = xs[i] := by
|
||||
simp +contextual [getElem_swapIfInBounds, hi]
|
||||
|
||||
@[simp]
|
||||
theorem getElem_swapIfInBounds_of_ne_of_ne {xs : Array α} {i j k : Nat} (hi : k ≠ i) (hj : k ≠ j)
|
||||
(hk : k < (xs.swapIfInBounds i j).size) :
|
||||
(xs.swapIfInBounds i j)[k] = xs[k]'(Nat.lt_of_lt_of_eq hk size_swapIfInBounds) := by
|
||||
simp [getElem_swapIfInBounds, hi, hj]
|
||||
(xs.swapIfInBounds i j).size = xs.size := by unfold swapIfInBounds; split <;> (try split) <;> simp [size_swap]
|
||||
|
||||
/-! ### swapAt -/
|
||||
|
||||
@@ -4154,11 +4241,17 @@ theorem replace_extract {xs : Array α} {i : Nat} :
|
||||
(replicate n a).replace a b = #[b] ++ replicate (n - 1) a := by
|
||||
cases n <;> simp_all [replicate_succ', replace_append]
|
||||
|
||||
@[deprecated replace_replicate_self (since := "2025-03-18")]
|
||||
abbrev replace_mkArray_self := @replace_replicate_self
|
||||
|
||||
@[simp] theorem replace_replicate_ne {a b c : α} (h : !b == a) :
|
||||
(replicate n a).replace b c = replicate n a := by
|
||||
rw [replace_of_not_mem]
|
||||
simp_all
|
||||
|
||||
@[deprecated replace_replicate_ne (since := "2025-03-18")]
|
||||
abbrev replace_mkArray_ne := @replace_replicate_ne
|
||||
|
||||
end replace
|
||||
|
||||
/-! ### toListRev -/
|
||||
@@ -4321,9 +4414,13 @@ theorem size_uset {xs : Array α} {v : α} {i : USize} (h : i.toNat < xs.size) :
|
||||
theorem getElem!_eq_getD [Inhabited α] {xs : Array α} {i} : xs[i]! = xs.getD i default := by
|
||||
rfl
|
||||
|
||||
theorem getElem_eq_getD {xs : Array α} {i} {h : i < xs.size} (fallback : α) :
|
||||
xs[i]'h = xs.getD i fallback := by
|
||||
rw [getD_eq_getD_getElem?, getElem_eq_getElem?_get, Option.get_eq_getD]
|
||||
/-! # mem -/
|
||||
|
||||
@[deprecated mem_toList_iff (since := "2025-05-26")]
|
||||
theorem mem_toList {a : α} {xs : Array α} : a ∈ xs.toList ↔ a ∈ xs := mem_def.symm
|
||||
|
||||
@[deprecated not_mem_empty (since := "2025-03-25")]
|
||||
theorem not_mem_nil (a : α) : ¬ a ∈ #[] := nofun
|
||||
|
||||
/-! # get lemmas -/
|
||||
|
||||
@@ -4336,7 +4433,6 @@ theorem getElem_fin_eq_getElem_toList {xs : Array α} {i : Fin xs.size} : xs[i]
|
||||
@[simp] theorem ugetElem_eq_getElem {xs : Array α} {i : USize} (h : i.toNat < xs.size) :
|
||||
xs[i] = xs[i.toNat] := rfl
|
||||
|
||||
@[deprecated getElem?_eq_none (since := "2025-10-26")]
|
||||
theorem getElem?_size_le {xs : Array α} {i : Nat} (h : xs.size ≤ i) : xs[i]? = none := by
|
||||
simp [getElem?_neg, h]
|
||||
|
||||
@@ -4356,7 +4452,6 @@ theorem getElem?_push_lt {xs : Array α} {x : α} {i : Nat} (h : i < xs.size) :
|
||||
(xs.push x)[i]? = some xs[i] := by
|
||||
rw [getElem?_pos (xs.push x) i (size_push _ ▸ Nat.lt_succ_of_lt h), getElem_push_lt]
|
||||
|
||||
@[deprecated getElem?_push_size (since := "2025-10-26")]
|
||||
theorem getElem?_push_eq {xs : Array α} {x : α} : (xs.push x)[xs.size]? = some x := by
|
||||
rw [getElem?_pos (xs.push x) xs.size (size_push _ ▸ Nat.lt_succ_self xs.size), getElem_push_eq]
|
||||
|
||||
@@ -4375,6 +4470,12 @@ theorem getElem?_push_eq {xs : Array α} {x : α} : (xs.push x)[xs.size]? = some
|
||||
cases xs
|
||||
simp
|
||||
|
||||
/-! ### contains -/
|
||||
|
||||
@[deprecated contains_iff (since := "2025-04-07")]
|
||||
abbrev contains_def [DecidableEq α] {a : α} {xs : Array α} : xs.contains a ↔ a ∈ xs :=
|
||||
contains_iff
|
||||
|
||||
/-! ### isPrefixOf -/
|
||||
|
||||
@[simp, grind =] theorem isPrefixOf_toList [BEq α] {xs ys : Array α} :
|
||||
@@ -4488,8 +4589,7 @@ theorem uset_toArray {l : List α} {i : USize} {a : α} {h : i.toNat < l.toArray
|
||||
apply ext'
|
||||
simp
|
||||
|
||||
@[deprecated Array.flatten_map_toArray_toArray (since := "2025-10-26")]
|
||||
theorem flatten_toArray {L : List (List α)} :
|
||||
@[simp, grind =] theorem flatten_toArray {L : List (List α)} :
|
||||
(L.toArray.map List.toArray).flatten = L.flatten.toArray := by
|
||||
apply ext'
|
||||
simp
|
||||
|
||||
@@ -6,6 +6,9 @@ Author: Kim Morrison
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Core
|
||||
import Init.Data.Array.Basic
|
||||
import Init.Data.Nat.Lemmas
|
||||
public import Init.Data.Range.Polymorphic.Iterators
|
||||
public import Init.Data.Range.Polymorphic.Nat
|
||||
import Init.Data.Iterators.Consumers
|
||||
|
||||
@@ -10,7 +10,9 @@ import all Init.Data.Array.Lex.Basic
|
||||
public import Init.Data.Array.Lex.Basic
|
||||
public import Init.Data.Array.Lemmas
|
||||
public import Init.Data.List.Lex
|
||||
import Init.Data.Range.Polymorphic.Lemmas
|
||||
import Init.Data.Range.Polymorphic.NatLemmas
|
||||
import Init.Data.Order.Lemmas
|
||||
|
||||
public section
|
||||
|
||||
@@ -34,18 +36,7 @@ grind_pattern _root_.List.le_toArray => l₁.toArray ≤ l₂.toArray
|
||||
grind_pattern lt_toList => xs.toList < ys.toList
|
||||
grind_pattern le_toList => xs.toList ≤ ys.toList
|
||||
|
||||
@[simp]
|
||||
protected theorem not_lt [LT α] {xs ys : Array α} : ¬ xs < ys ↔ ys ≤ xs := Iff.rfl
|
||||
|
||||
@[deprecated Array.not_lt (since := "2025-10-26")]
|
||||
protected theorem not_lt_iff_ge [LT α] {xs ys : Array α} : ¬ xs < ys ↔ ys ≤ xs := Iff.rfl
|
||||
|
||||
@[simp]
|
||||
protected theorem not_le [LT α] {xs ys : Array α} :
|
||||
¬ xs ≤ ys ↔ ys < xs :=
|
||||
Classical.not_not
|
||||
|
||||
@[deprecated Array.not_le (since := "2025-10-26")]
|
||||
protected theorem not_le_iff_gt [LT α] {xs ys : Array α} :
|
||||
¬ xs ≤ ys ↔ ys < xs :=
|
||||
Classical.not_not
|
||||
@@ -73,11 +64,19 @@ private theorem cons_lex_cons [BEq α] {lt : α → α → Bool} {a b : α} {xs
|
||||
(lt a b || a == b && xs.lex ys lt) := by
|
||||
simp only [lex, size_append, List.size_toArray, List.length_cons, List.length_nil, Nat.zero_add,
|
||||
Nat.add_min_add_left, Nat.add_lt_add_iff_left, Std.Rco.forIn'_eq_forIn'_toList]
|
||||
rw [cons_lex_cons.forIn'_congr_aux (Nat.toList_rco_eq_cons (by omega)) rfl (fun _ _ _ => rfl)]
|
||||
simp only [bind_pure_comp, map_pure, Nat.toList_rco_succ_succ, Nat.add_comm 1]
|
||||
cases h : lt a b
|
||||
· cases h' : a == b <;> simp [bne, *]
|
||||
· simp [*]
|
||||
conv =>
|
||||
lhs; congr; congr
|
||||
rw [cons_lex_cons.forIn'_congr_aux Std.Rco.toList_eq_if rfl (fun _ _ _ => rfl)]
|
||||
simp only [bind_pure_comp, map_pure]
|
||||
rw [cons_lex_cons.forIn'_congr_aux (if_pos (by omega)) rfl (fun _ _ _ => rfl)]
|
||||
simp only [Std.toList_Roo_eq_toList_Rco_of_isSome_succ? (lo := 0) (h := rfl),
|
||||
Std.PRange.UpwardEnumerable.succ?, Nat.add_comm 1, Std.PRange.Nat.toList_Rco_succ_succ,
|
||||
Option.get_some, List.forIn'_cons, List.size_toArray, List.length_cons, List.length_nil,
|
||||
Nat.lt_add_one, getElem_append_left, List.getElem_toArray, List.getElem_cons_zero]
|
||||
cases lt a b
|
||||
· rw [bne]
|
||||
cases a == b <;> simp
|
||||
· simp
|
||||
|
||||
@[simp, grind =] theorem _root_.List.lex_toArray [BEq α] {lt : α → α → Bool} {l₁ l₂ : List α} :
|
||||
l₁.toArray.lex l₂.toArray lt = l₁.lex l₂ lt := by
|
||||
@@ -143,7 +142,7 @@ protected theorem lt_of_le_of_lt [LE α] [LT α] [LawfulOrderLT α] [IsLinearOrd
|
||||
@[deprecated Array.lt_of_le_of_lt (since := "2025-08-01")]
|
||||
protected theorem lt_of_le_of_lt' [LT α]
|
||||
[i₁ : Std.Asymm (· < · : α → α → Prop)]
|
||||
[i₂ : Std.Trichotomous (· < · : α → α → Prop)]
|
||||
[i₂ : Std.Antisymm (¬ · < · : α → α → Prop)]
|
||||
[i₃ : Trans (¬ · < · : α → α → Prop) (¬ · < ·) (¬ · < ·)]
|
||||
{xs ys zs : Array α} (h₁ : xs ≤ ys) (h₂ : ys < zs) : xs < zs :=
|
||||
letI := LE.ofLT α
|
||||
@@ -157,7 +156,7 @@ protected theorem le_trans [LE α] [LT α] [LawfulOrderLT α] [IsLinearOrder α]
|
||||
@[deprecated Array.le_trans (since := "2025-08-01")]
|
||||
protected theorem le_trans' [LT α]
|
||||
[i₁ : Std.Asymm (· < · : α → α → Prop)]
|
||||
[i₂ : Std.Trichotomous (· < · : α → α → Prop)]
|
||||
[i₂ : Std.Antisymm (¬ · < · : α → α → Prop)]
|
||||
[i₃ : Trans (¬ · < · : α → α → Prop) (¬ · < ·) (¬ · < ·)]
|
||||
{xs ys zs : Array α} (h₁ : xs ≤ ys) (h₂ : ys ≤ zs) : xs ≤ zs :=
|
||||
letI := LE.ofLT α
|
||||
@@ -181,6 +180,12 @@ protected theorem le_total [LT α]
|
||||
[i : Std.Asymm (· < · : α → α → Prop)] (xs ys : Array α) : xs ≤ ys ∨ ys ≤ xs :=
|
||||
List.le_total xs.toList ys.toList
|
||||
|
||||
@[simp] protected theorem not_lt [LT α]
|
||||
{xs ys : Array α} : ¬ xs < ys ↔ ys ≤ xs := Iff.rfl
|
||||
|
||||
@[simp] protected theorem not_le [LT α]
|
||||
{xs ys : Array α} : ¬ ys ≤ xs ↔ xs < ys := Classical.not_not
|
||||
|
||||
protected theorem le_of_lt [LT α]
|
||||
[i : Std.Asymm (· < · : α → α → Prop)]
|
||||
{xs ys : Array α} (h : xs < ys) : xs ≤ ys :=
|
||||
@@ -188,7 +193,7 @@ protected theorem le_of_lt [LT α]
|
||||
|
||||
protected theorem le_iff_lt_or_eq [LT α]
|
||||
[Std.Irrefl (· < · : α → α → Prop)]
|
||||
[Std.Trichotomous (· < · : α → α → Prop)]
|
||||
[Std.Antisymm (¬ · < · : α → α → Prop)]
|
||||
[Std.Asymm (· < · : α → α → Prop)]
|
||||
{xs ys : Array α} : xs ≤ ys ↔ xs < ys ∨ xs = ys := by
|
||||
simpa using List.le_iff_lt_or_eq (l₁ := xs.toList) (l₂ := ys.toList)
|
||||
@@ -277,7 +282,7 @@ protected theorem lt_iff_exists [LT α] {xs ys : Array α} :
|
||||
|
||||
protected theorem le_iff_exists [LT α]
|
||||
[Std.Asymm (· < · : α → α → Prop)]
|
||||
[Std.Trichotomous (· < · : α → α → Prop)] {xs ys : Array α} :
|
||||
[Std.Antisymm (¬ · < · : α → α → Prop)] {xs ys : Array α} :
|
||||
xs ≤ ys ↔
|
||||
(xs = ys.take xs.size) ∨
|
||||
(∃ (i : Nat) (h₁ : i < xs.size) (h₂ : i < ys.size),
|
||||
@@ -296,7 +301,7 @@ theorem append_left_lt [LT α] {xs ys zs : Array α} (h : ys < zs) :
|
||||
|
||||
theorem append_left_le [LT α]
|
||||
[Std.Asymm (· < · : α → α → Prop)]
|
||||
[Std.Trichotomous (· < · : α → α → Prop)]
|
||||
[Std.Antisymm (¬ · < · : α → α → Prop)]
|
||||
{xs ys zs : Array α} (h : ys ≤ zs) :
|
||||
xs ++ ys ≤ xs ++ zs := by
|
||||
cases xs
|
||||
@@ -319,9 +324,9 @@ protected theorem map_lt [LT α] [LT β]
|
||||
|
||||
protected theorem map_le [LT α] [LT β]
|
||||
[Std.Asymm (· < · : α → α → Prop)]
|
||||
[Std.Trichotomous (· < · : α → α → Prop)]
|
||||
[Std.Antisymm (¬ · < · : α → α → Prop)]
|
||||
[Std.Asymm (· < · : β → β → Prop)]
|
||||
[Std.Trichotomous (· < · : β → β → Prop)]
|
||||
[Std.Antisymm (¬ · < · : β → β → Prop)]
|
||||
{xs ys : Array α} {f : α → β} (w : ∀ x y, x < y → f x < f y) (h : xs ≤ ys) :
|
||||
map f xs ≤ map f ys := by
|
||||
cases xs
|
||||
|
||||
@@ -6,7 +6,10 @@ Authors: Mario Carneiro, 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.Array.Attach
|
||||
public import Init.Data.Array.OfFn
|
||||
public import Init.Data.List.MapIdx
|
||||
import all Init.Data.List.MapIdx
|
||||
@@ -296,6 +299,9 @@ theorem mapFinIdx_eq_replicate_iff {xs : Array α} {f : (i : Nat) → α → (h
|
||||
rw [← toList_inj]
|
||||
simp [List.mapFinIdx_eq_replicate_iff]
|
||||
|
||||
@[deprecated mapFinIdx_eq_replicate_iff (since := "2025-03-18")]
|
||||
abbrev mapFinIdx_eq_mkArray_iff := @mapFinIdx_eq_replicate_iff
|
||||
|
||||
@[simp, grind =] theorem mapFinIdx_reverse {xs : Array α} {f : (i : Nat) → α → (h : i < xs.reverse.size) → β} :
|
||||
xs.reverse.mapFinIdx f = (xs.mapFinIdx (fun i a h => f (xs.size - 1 - i) a (by simp; omega))).reverse := by
|
||||
rcases xs with ⟨l⟩
|
||||
@@ -435,6 +441,9 @@ theorem mapIdx_eq_replicate_iff {xs : Array α} {f : Nat → α → β} {b : β}
|
||||
rw [← toList_inj]
|
||||
simp [List.mapIdx_eq_replicate_iff]
|
||||
|
||||
@[deprecated mapIdx_eq_replicate_iff (since := "2025-03-18")]
|
||||
abbrev mapIdx_eq_mkArray_iff := @mapIdx_eq_replicate_iff
|
||||
|
||||
@[simp, grind =] theorem mapIdx_reverse {xs : Array α} {f : Nat → α → β} :
|
||||
xs.reverse.mapIdx f = (mapIdx (fun i => f (xs.size - 1 - i)) xs).reverse := by
|
||||
rcases xs with ⟨xs⟩
|
||||
|
||||
@@ -6,6 +6,8 @@ Authors: Leonardo de Moura, Joachim Breitner
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Array.Basic
|
||||
public import Init.Data.Nat.Linear
|
||||
public import Init.Data.List.BasicAux
|
||||
|
||||
public section
|
||||
|
||||
@@ -6,9 +6,13 @@ Authors: Kim Morrison
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.List.Control
|
||||
import all Init.Data.List.Control
|
||||
public import Init.Data.Array.Basic
|
||||
import all Init.Data.Array.Basic
|
||||
public import Init.Data.Array.Lemmas
|
||||
public import Init.Data.Array.Attach
|
||||
public import Init.Data.List.Monadic
|
||||
|
||||
public section
|
||||
|
||||
@@ -39,6 +43,10 @@ theorem map_toList_inj [Monad m] [LawfulMonad m]
|
||||
@[simp, grind =] theorem idRun_mapM {xs : Array α} {f : α → Id β} : (xs.mapM f).run = xs.map (f · |>.run) :=
|
||||
mapM_pure
|
||||
|
||||
@[deprecated idRun_mapM (since := "2025-05-21")]
|
||||
theorem mapM_id {xs : Array α} {f : α → Id β} : xs.mapM f = xs.map f :=
|
||||
mapM_pure
|
||||
|
||||
@[simp, grind =] theorem mapM_map [Monad m] [LawfulMonad m] {f : α → β} {g : β → m γ} {xs : Array α} :
|
||||
(xs.map f).mapM g = xs.mapM (g ∘ f) := by
|
||||
rcases xs with ⟨xs⟩
|
||||
@@ -197,6 +205,13 @@ theorem idRun_forIn'_yield_eq_foldl
|
||||
xs.attach.foldl (fun b ⟨a, h⟩ => f a h b |>.run) init := by
|
||||
simp
|
||||
|
||||
@[deprecated idRun_forIn'_yield_eq_foldl (since := "2025-05-21")]
|
||||
theorem forIn'_yield_eq_foldl
|
||||
{xs : Array α} (f : (a : α) → a ∈ xs → β → β) (init : β) :
|
||||
forIn' (m := Id) xs init (fun a m b => .yield (f a m b)) =
|
||||
xs.attach.foldl (fun b ⟨a, h⟩ => f a h b) init :=
|
||||
forIn'_pure_yield_eq_foldl _ _
|
||||
|
||||
@[simp, grind =] theorem forIn'_map [Monad m] [LawfulMonad m]
|
||||
{xs : Array α} (g : α → β) (f : (b : β) → b ∈ xs.map g → γ → m (ForInStep γ)) :
|
||||
forIn' (xs.map g) init f = forIn' xs init fun a h y => f (g a) (mem_map_of_mem h) y := by
|
||||
@@ -238,6 +253,13 @@ theorem idRun_forIn_yield_eq_foldl
|
||||
xs.foldl (fun b a => f a b |>.run) init := by
|
||||
simp
|
||||
|
||||
@[deprecated idRun_forIn_yield_eq_foldl (since := "2025-05-21")]
|
||||
theorem forIn_yield_eq_foldl
|
||||
{xs : Array α} (f : α → β → β) (init : β) :
|
||||
forIn (m := Id) xs init (fun a b => .yield (f a b)) =
|
||||
xs.foldl (fun b a => f a b) init :=
|
||||
forIn_pure_yield_eq_foldl _ _
|
||||
|
||||
@[simp, grind =] theorem forIn_map [Monad m] [LawfulMonad m]
|
||||
{xs : Array α} {g : α → β} {f : β → γ → m (ForInStep γ)} :
|
||||
forIn (xs.map g) init f = forIn xs init fun a y => f (g a) y := by
|
||||
|
||||
@@ -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.Array.Monadic
|
||||
public import Init.Data.List.OfFn
|
||||
public import Init.Data.List.FinRange
|
||||
|
||||
public section
|
||||
|
||||
@@ -7,6 +7,7 @@ module
|
||||
|
||||
prelude
|
||||
public import Init.Data.List.Nat.Perm
|
||||
public import Init.Data.Array.Basic
|
||||
import all Init.Data.Array.Basic
|
||||
public import Init.Data.Array.Lemmas
|
||||
|
||||
@@ -84,6 +85,9 @@ theorem Perm.size_eq {xs ys : Array α} (p : xs ~ ys) : xs.size = ys.size := by
|
||||
simp only [perm_iff_toList_perm] at p
|
||||
simpa using p.length_eq
|
||||
|
||||
@[deprecated Perm.size_eq (since := "2025-04-17")]
|
||||
abbrev Perm.length_eq := @Perm.size_eq
|
||||
|
||||
theorem Perm.mem_iff {a : α} {xs ys : Array α} (p : xs ~ ys) : a ∈ xs ↔ a ∈ ys := by
|
||||
rcases xs with ⟨xs⟩
|
||||
rcases ys with ⟨ys⟩
|
||||
@@ -104,7 +108,7 @@ grind_pattern Perm.append => xs ~ ys, as ~ bs, ys ++ bs
|
||||
|
||||
theorem Perm.push (x : α) {xs ys : Array α} (p : xs ~ ys) :
|
||||
xs.push x ~ ys.push x := by
|
||||
rw [push_eq_append]
|
||||
rw [push_eq_append_singleton]
|
||||
exact p.append .rfl
|
||||
|
||||
grind_pattern Perm.push => xs ~ ys, xs.push x
|
||||
|
||||
@@ -6,10 +6,14 @@ Authors: Kim Morrison
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Array.Lemmas
|
||||
public import Init.Data.Array.Basic
|
||||
import all Init.Data.Array.Basic
|
||||
public import Init.Data.Array.OfFn
|
||||
import all Init.Data.Array.OfFn
|
||||
public import Init.Data.Array.MapIdx
|
||||
public import Init.Data.Array.Zip
|
||||
public import Init.Data.List.Nat.Range
|
||||
|
||||
public section
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user