Showing preview only (1,386K chars total). Download the full file or copy to clipboard to get everything.
Repository: leanprover/std4
Branch: main
Commit: 48ff08a0e30a
Files: 270
Total size: 1.2 MB
Directory structure:
gitextract_6ram4obd/
├── .docker/
│ └── gitpod/
│ └── Dockerfile
├── .github/
│ └── workflows/
│ ├── build.yml
│ ├── docs-deploy.yml
│ ├── docs-release.yml
│ ├── labels-from-comments.yml
│ ├── labels-from-status.yml
│ ├── merge_conflicts.yml
│ ├── nightly_bump_and_merge.yml
│ ├── nightly_detect_failure.yml
│ ├── nightly_merge_master.yml
│ └── test_mathlib.yml
├── .gitignore
├── .gitpod.yml
├── .vscode/
│ ├── copyright.code-snippets
│ └── settings.json
├── Batteries/
│ ├── Classes/
│ │ ├── Cast.lean
│ │ ├── Deprecated.lean
│ │ ├── Order.lean
│ │ ├── RatCast.lean
│ │ └── SatisfiesM.lean
│ ├── CodeAction/
│ │ ├── Attr.lean
│ │ ├── Basic.lean
│ │ ├── Deprecated.lean
│ │ ├── Match.lean
│ │ └── Misc.lean
│ ├── CodeAction.lean
│ ├── Control/
│ │ ├── AlternativeMonad.lean
│ │ ├── ForInStep/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── ForInStep.lean
│ │ ├── LawfulMonadState.lean
│ │ ├── Lemmas.lean
│ │ ├── Monad.lean
│ │ ├── Nondet/
│ │ │ └── Basic.lean
│ │ └── OptionT.lean
│ ├── Data/
│ │ ├── Array/
│ │ │ ├── Basic.lean
│ │ │ ├── Init/
│ │ │ │ └── Lemmas.lean
│ │ │ ├── Lemmas.lean
│ │ │ ├── Match.lean
│ │ │ ├── Merge.lean
│ │ │ ├── Monadic.lean
│ │ │ ├── Pairwise.lean
│ │ │ └── Scan.lean
│ │ ├── Array.lean
│ │ ├── AssocList.lean
│ │ ├── BinaryHeap/
│ │ │ └── Basic.lean
│ │ ├── BinaryHeap.lean
│ │ ├── BinomialHeap/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── BinomialHeap.lean
│ │ ├── BitVec/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── BitVec.lean
│ │ ├── Bool.lean
│ │ ├── ByteArray.lean
│ │ ├── ByteSlice.lean
│ │ ├── Char/
│ │ │ ├── AsciiCasing.lean
│ │ │ └── Basic.lean
│ │ ├── Char.lean
│ │ ├── DList/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── DList.lean
│ │ ├── Fin/
│ │ │ ├── Basic.lean
│ │ │ ├── Fold.lean
│ │ │ ├── Lemmas.lean
│ │ │ └── OfBits.lean
│ │ ├── Fin.lean
│ │ ├── FloatArray.lean
│ │ ├── HashMap/
│ │ │ └── Basic.lean
│ │ ├── HashMap.lean
│ │ ├── Int.lean
│ │ ├── List/
│ │ │ ├── ArrayMap.lean
│ │ │ ├── Basic.lean
│ │ │ ├── Count.lean
│ │ │ ├── Init/
│ │ │ │ └── Lemmas.lean
│ │ │ ├── Lemmas.lean
│ │ │ ├── Matcher.lean
│ │ │ ├── Monadic.lean
│ │ │ ├── Pairwise.lean
│ │ │ ├── Perm.lean
│ │ │ └── Scan.lean
│ │ ├── List.lean
│ │ ├── MLList/
│ │ │ ├── Basic.lean
│ │ │ ├── Heartbeats.lean
│ │ │ └── IO.lean
│ │ ├── MLList.lean
│ │ ├── NameSet.lean
│ │ ├── Nat/
│ │ │ ├── Basic.lean
│ │ │ ├── Bisect.lean
│ │ │ ├── Bitwise/
│ │ │ │ └── Lemmas.lean
│ │ │ ├── Bitwise.lean
│ │ │ ├── Gcd.lean
│ │ │ └── Lemmas.lean
│ │ ├── Nat.lean
│ │ ├── PairingHeap.lean
│ │ ├── RBMap/
│ │ │ ├── Alter.lean
│ │ │ ├── Basic.lean
│ │ │ ├── Depth.lean
│ │ │ ├── Lemmas.lean
│ │ │ └── WF.lean
│ │ ├── RBMap.lean
│ │ ├── Random/
│ │ │ └── MersenneTwister.lean
│ │ ├── Random.lean
│ │ ├── Range/
│ │ │ └── Lemmas.lean
│ │ ├── Range.lean
│ │ ├── Rat/
│ │ │ └── Float.lean
│ │ ├── Rat.lean
│ │ ├── RunningStats.lean
│ │ ├── Stream.lean
│ │ ├── String/
│ │ │ ├── AsciiCasing.lean
│ │ │ ├── Basic.lean
│ │ │ ├── Legacy.lean
│ │ │ ├── Lemmas.lean
│ │ │ └── Matcher.lean
│ │ ├── String.lean
│ │ ├── UInt.lean
│ │ ├── UnionFind/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── UnionFind.lean
│ │ ├── Vector/
│ │ │ ├── Basic.lean
│ │ │ ├── Lemmas.lean
│ │ │ └── Monadic.lean
│ │ └── Vector.lean
│ ├── Lean/
│ │ ├── AttributeExtra.lean
│ │ ├── EStateM.lean
│ │ ├── Except.lean
│ │ ├── Expr.lean
│ │ ├── Float.lean
│ │ ├── HashMap.lean
│ │ ├── HashSet.lean
│ │ ├── IO/
│ │ │ └── Process.lean
│ │ ├── Json.lean
│ │ ├── LawfulMonad.lean
│ │ ├── LawfulMonadLift.lean
│ │ ├── Meta/
│ │ │ ├── Basic.lean
│ │ │ ├── DiscrTree.lean
│ │ │ ├── Expr.lean
│ │ │ ├── Inaccessible.lean
│ │ │ ├── InstantiateMVars.lean
│ │ │ ├── SavedState.lean
│ │ │ ├── Simp.lean
│ │ │ └── UnusedNames.lean
│ │ ├── MonadBacktrack.lean
│ │ ├── NameMapAttribute.lean
│ │ ├── PersistentHashMap.lean
│ │ ├── PersistentHashSet.lean
│ │ ├── Position.lean
│ │ ├── SatisfiesM.lean
│ │ ├── Syntax.lean
│ │ ├── System/
│ │ │ └── IO.lean
│ │ ├── TagAttribute.lean
│ │ └── Util/
│ │ └── EnvSearch.lean
│ ├── Linter/
│ │ ├── UnnecessarySeqFocus.lean
│ │ └── UnreachableTactic.lean
│ ├── Linter.lean
│ ├── Logic.lean
│ ├── Tactic/
│ │ ├── Alias.lean
│ │ ├── Basic.lean
│ │ ├── Case.lean
│ │ ├── Congr.lean
│ │ ├── Exact.lean
│ │ ├── GeneralizeProofs.lean
│ │ ├── HelpCmd.lean
│ │ ├── Init.lean
│ │ ├── Instances.lean
│ │ ├── Lemma.lean
│ │ ├── Lint/
│ │ │ ├── Basic.lean
│ │ │ ├── Frontend.lean
│ │ │ ├── Misc.lean
│ │ │ ├── Simp.lean
│ │ │ └── TypeClass.lean
│ │ ├── Lint.lean
│ │ ├── NoMatch.lean
│ │ ├── OpenPrivate.lean
│ │ ├── PermuteGoals.lean
│ │ ├── PrintDependents.lean
│ │ ├── PrintOpaques.lean
│ │ ├── PrintPrefix.lean
│ │ ├── SeqFocus.lean
│ │ ├── ShowUnused.lean
│ │ ├── SqueezeScope.lean
│ │ ├── Trans.lean
│ │ └── Unreachable.lean
│ └── Util/
│ ├── Cache.lean
│ ├── ExtendedBinder.lean
│ ├── LibraryNote.lean
│ ├── Panic.lean
│ ├── Pickle.lean
│ └── ProofWanted.lean
├── Batteries.lean
├── BatteriesTest/
│ ├── ArrayMap.lean
│ ├── Char.lean
│ ├── GeneralizeProofs.lean
│ ├── Internal/
│ │ ├── DummyLabelAttr.lean
│ │ ├── DummyLibraryNote.lean
│ │ └── DummyLibraryNote2.lean
│ ├── MLList.lean
│ ├── OpenPrivateDefs.lean
│ ├── String.lean
│ ├── absurd.lean
│ ├── alias.lean
│ ├── array.lean
│ ├── array_scan.lean
│ ├── by_contra.lean
│ ├── case.lean
│ ├── congr.lean
│ ├── conv_equals.lean
│ ├── except.lean
│ ├── exfalso.lean
│ ├── float.lean
│ ├── help_cmd.lean
│ ├── import_lean.lean
│ ├── instances.lean
│ ├── isIndependentOf.lean
│ ├── kmp_matcher.lean
│ ├── lemma_cmd.lean
│ ├── library_note.lean
│ ├── lintTC.lean
│ ├── lintTrace.lean
│ ├── lint_coinductive.lean
│ ├── lint_docBlame.lean
│ ├── lint_docBlameThm.lean
│ ├── lint_dupNamespace.lean
│ ├── lint_lean.lean
│ ├── lint_simpNF.lean
│ ├── lint_simpNF_respectTransparency.lean
│ ├── lint_unreachableTactic.lean
│ ├── linterVisibility.lean
│ ├── lintsimp.lean
│ ├── lintunused.lean
│ ├── list_enumeration.lean
│ ├── list_sublists.lean
│ ├── mersenne_twister.lean
│ ├── nondet.lean
│ ├── norm_cast.lean
│ ├── omega/
│ │ └── benchmark.lean
│ ├── on_goal.lean
│ ├── openPrivate.lean
│ ├── print_opaques.lean
│ ├── print_prefix.lean
│ ├── proof_wanted.lean
│ ├── register_label_attr.lean
│ ├── rfl.lean
│ ├── satisfying.lean
│ ├── seq_focus.lean
│ ├── show_term.lean
│ ├── show_unused.lean
│ ├── simp_trace.lean
│ ├── simpa.lean
│ ├── solve_by_elim.lean
│ ├── trans.lean
│ ├── tryThis.lean
│ ├── vector.lean
│ └── where.lean
├── LICENSE
├── README.md
├── Shake/
│ └── Main.lean
├── bors.toml
├── docs/
│ └── lakefile.toml
├── lake-manifest.json
├── lakefile.toml
├── lean-toolchain
└── scripts/
├── check_imports.lean
├── create-adaptation-pr.sh
├── lintWhitespace.sh
├── merge-lean-testing-pr.sh
├── nolints.json
├── noshake.json
├── runLinter.lean
└── updateBatteries.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .docker/gitpod/Dockerfile
================================================
# This is the Dockerfile for leanprover-community/batteries
# This file is mostly copied from [mathlib4](https://github.com/leanprover-community/mathlib4/blob/master/.docker/gitpod/Dockerfile)
# gitpod doesn't support multiple FROM statements, (or rather, you can't copy from one to another)
# so we just install everything in one go
FROM ubuntu:jammy
USER root
RUN apt-get update && apt-get install sudo git curl bash-completion python3-requests gcc make -y && apt-get clean
RUN useradd -l -u 33333 -G sudo -md /home/gitpod -s /bin/bash -p gitpod gitpod \
# passwordless sudo for users in the 'sudo' group
&& sed -i.bkp -e 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' /etc/sudoers
USER gitpod
WORKDIR /home/gitpod
SHELL ["/bin/bash", "-c"]
# gitpod bash prompt
RUN { echo && echo "PS1='\[\033[01;32m\]\u\[\033[00m\] \[\033[01;34m\]\w\[\033[00m\]\$(__git_ps1 \" (%s)\") $ '" ; } >> .bashrc
# install elan
RUN curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain none
# install whichever toolchain batteries is currently using
RUN . ~/.profile && elan toolchain install $(curl https://raw.githubusercontent.com/leanprover-community/batteries/main/lean-toolchain)
# install neovim (for any lean.nvim user), via tarball since the appimage doesn't work for some reason, and jammy's version is ancient
RUN curl -s -L https://github.com/neovim/neovim/releases/download/stable/nvim-linux64.tar.gz | tar xzf - && sudo mv nvim-linux64 /opt/nvim
ENV PATH="/home/gitpod/.local/bin:/home/gitpod/.elan/bin:/opt/nvim/bin:${PATH}"
# fix the infoview when the container is used on gitpod:
ENV VSCODE_API_VERSION="1.50.0"
# ssh to github once to bypass the unknown fingerprint warning
RUN ssh -o StrictHostKeyChecking=no github.com || true
# run sudo once to suppress usage info
RUN sudo echo finished
================================================
FILE: .github/workflows/build.yml
================================================
on:
push:
branches-ignore:
# ignore tmp branches used by bors
- 'staging.tmp*'
- 'trying.tmp*'
- 'staging*.tmp'
pull_request:
name: ci
concurrency:
group: build-${{ github.sha }}
cancel-in-progress: true
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: lean-action
name: build, test, and lint batteries
uses: leanprover/lean-action@v1
with:
build-args: '--wfail'
- name: Check that all files are imported
run: lake env lean scripts/check_imports.lean
- name: Check for forbidden character ↦
if: always()
run: |
if grep -r -n --include=\*.lean -e '↦' . ; then
echo "Error: Found forbidden character ↦"
exit 1
fi
- name: Check for 'namespace Mathlib'
if: always()
run: |
if grep -r -n --include=\*.lean -e 'namespace Mathlib' . ; then
echo "Error: Found 'namespace Mathlib'"
exit 1
fi
- name: Check for long lines
if: always()
run: |
! (find Batteries -name "*.lean" -type f -exec grep -E -H -n '^.{101,}$' {} \; | grep -v -E 'https?://')
- name: Check for trailing whitespace
if: always()
run: |
scripts/lintWhitespace.sh
- name: Don't 'import Lean', use precise imports
if: always()
run: |
! (find . -name "*.lean" ! -path "./BatteriesTest/import_lean.lean" -type f -print0 | xargs -0 grep -E -n '^import Lean$')
================================================
FILE: .github/workflows/docs-deploy.yml
================================================
name: Deploy Docs
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *' # daily (UTC 10:00)
permissions:
contents: write
jobs:
deploy-docs:
runs-on: ubuntu-latest
if: github.repository_owner == 'leanprover-community'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Lean
uses: leanprover/lean-action@v1
with:
test: false
lint: false
use-github-cache: true
- name: Build Docs
working-directory: docs
run: lake build --keep-toolchain -q Batteries:docs
- name: Deploy Docs
run: |
git config user.name "leanprover-community-batteries-bot"
git config user.email "leanprover-community-batteries-bot@users.noreply.github.com"
git checkout -b docs
git add docs/doc docs/doc-data
git commit -m "chore: generate docs"
git push origin docs --force
================================================
FILE: .github/workflows/docs-release.yml
================================================
name: Release Docs
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+"
permissions:
contents: write
jobs:
build-docs:
runs-on: ubuntu-latest
if: github.repository_owner == 'leanprover-community'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Lean
uses: leanprover/lean-action@v1
with:
test: false
lint: false
use-github-cache: true
- name: Build Docs
working-directory: docs
run: lake build --keep-toolchain -q Batteries:docs
- name: Compress Docs
working-directory: docs
env:
TAG_NAME: ${{ github.ref_name }}
run: |
tar -czf docs-${TAG_NAME}.tar.gz doc doc-data
zip -rq docs-${TAG_NAME}.zip doc doc-data
- name: Release Docs
uses: softprops/action-gh-release@v2
with:
prerelease: ${{ contains(github.ref, 'rc') }}
make_latest: ${{ !contains(github.ref, 'rc') }}
files: |
docs/docs-${{ github.ref_name }}.tar.gz
docs/docs-${{ github.ref_name }}.zip
fail_on_unmatched_files: true
================================================
FILE: .github/workflows/labels-from-comments.yml
================================================
# This workflow allows any user to add one of the `awaiting-review`, `awaiting-author`, or `WIP` labels,
# by commenting on the PR or issue.
# Other labels from this set are removed automatically at the same time.
name: Label PR based on Comment
on:
issue_comment:
types: [created]
jobs:
update-label:
if: github.event.issue.pull_request != null && (github.event.comment.body == 'awaiting-review' || github.event.comment.body == 'awaiting-author' || github.event.comment.body == 'WIP')
runs-on: ubuntu-latest
steps:
- name: Remove all relevant labels
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo, number: issue_number } = context.issue;
// Remove the labels if they exist
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'awaiting-review' }).catch(() => {});
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'awaiting-author' }).catch(() => {});
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'WIP' }).catch(() => {});
- name: Add label based on comment
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo, number: issue_number } = context.issue;
const commentBody = context.payload.comment.body;
if (commentBody == 'awaiting-review') {
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['awaiting-review'] });
} else if (commentBody == 'awaiting-author') {
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['awaiting-author'] });
} else if (commentBody == 'WIP') {
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['WIP'] });
}
# - name: Delete the comment
# uses: actions/github-script@v6
# with:
# github-token: ${{ secrets.GITHUB_TOKEN }}
# script: |
# const { owner, repo } = context.repo;
# await github.rest.issues.deleteComment({ owner, repo, comment_id: context.payload.comment.id });
================================================
FILE: .github/workflows/labels-from-status.yml
================================================
# This workflow assigns `awaiting-review` or `WIP` labels to new PRs, and it removes
# `awaiting-review`, `awaiting-author`, or `WIP` label from closed PRs.
# It does not modify labels for open PRs that already have one of the `awaiting-review`,
# `awaiting-author`, or `WIP` labels.
name: Label PR from status change
permissions:
contents: read
pull-requests: write
on:
pull_request_target:
types:
- closed
- opened
- reopened
- converted_to_draft
- ready_for_review
branches:
- main
jobs:
auto-label:
if: github.repository_owner == 'leanprover-community'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Unlabel closed PR
if: github.event.pull_request.state == 'closed'
uses: actions-ecosystem/action-remove-labels@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: |
WIP
awaiting-author
awaiting-review
- name: Label unlabeled draft PR as WIP
if: |
github.event.pull_request.state == 'open' &&
github.event.pull_request.draft &&
! contains(github.event.pull_request.labels.*.name, 'awaiting-author') &&
! contains(github.event.pull_request.labels.*.name, 'awaiting-review') &&
! contains(github.event.pull_request.labels.*.name, 'WIP')
uses: actions-ecosystem/action-add-labels@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: WIP
- name: Label unlabeled other PR as awaiting-review
if: |
github.event.pull_request.state == 'open' &&
! github.event.pull_request.draft &&
! contains(github.event.pull_request.labels.*.name, 'awaiting-author') &&
! contains(github.event.pull_request.labels.*.name, 'awaiting-review') &&
! contains(github.event.pull_request.labels.*.name, 'WIP')
uses: actions-ecosystem/action-add-labels@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: awaiting-review
================================================
FILE: .github/workflows/merge_conflicts.yml
================================================
name: Merge conflicts
on:
schedule:
- cron: '*/60 * * * *' # run every 60 minutes
jobs:
main:
if: github.repository_owner == 'leanprover-community'
runs-on: ubuntu-latest
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.MATHLIB_MERGE_CONFLICTS_APP_ID }}
private-key: ${{ secrets.MATHLIB_MERGE_CONFLICTS_PRIVATE_KEY }}
# The create-github-app-token README states that this token is masked and will not be logged accidentally.
- name: check if prs are dirty
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
with:
dirtyLabel: "merge-conflict"
repoToken: ${{ steps.app-token.outputs.token }}
================================================
FILE: .github/workflows/nightly_bump_and_merge.yml
================================================
name: Bump toolchain and merge pr-testing branches
# This workflow combines the former `nightly_bump_toolchain.yml` and `discover-lean-pr-testing.yml`
# into a single workflow. This ensures that when the toolchain is bumped, any relevant
# lean-pr-testing branches are merged in the same push, avoiding spurious CI failures
# on the intermediate state (bumped toolchain without the adaptations).
on:
schedule:
- cron: '45 9/3 * * *'
# 10:45AM CET/1:45AM PT (and then every 3 hours thereafter),
# This should be 2 hours and 45 minutes after lean4 starts building the nightly.
# Mathlib's `nightly-testing` branch is bumped 15 minutes later.
workflow_dispatch:
jobs:
bump-and-merge:
runs-on: ubuntu-latest
if: github.repository_owner == 'leanprover-community'
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.MATHLIB_NIGHTLY_TESTING_APP_ID }}
private-key: ${{ secrets.MATHLIB_NIGHTLY_TESTING_PRIVATE_KEY }}
# The create-github-app-token README states that this token is masked and will not be logged accidentally.
- name: Checkout nightly-testing branch
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: nightly-testing
fetch-depth: 0 # Fetch all branches and history
token: ${{ steps.app-token.outputs.token }}
- name: Set up Git
run: |
git config --global user.name "mathlib-nightly-testing[bot]"
git config --global user.email "mathlib-nightly-testing[bot]@users.noreply.github.com"
- name: Configure Lean
uses: leanprover/lean-action@f807b338d95de7813c5c50d018f1c23c9b93b4ec # 2025-04-24
with:
auto-config: false
use-github-cache: false
use-mathlib-cache: false
# ============================================================================
# Phase 1: Bump the toolchain (commit locally, don't push yet)
# ============================================================================
- name: Get old toolchain version
id: old-toolchain
run: |
# Capture the current toolchain BEFORE we modify anything
OLD=$(cut -f2 -d: lean-toolchain)
echo "old=$OLD"
echo "old=$OLD" >> "$GITHUB_OUTPUT"
- name: Get latest release tag from leanprover/lean4-nightly
id: get-latest-release
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
RELEASE_TAG=$(gh api -X GET repos/leanprover/lean4-nightly/releases \
-f per_page=1 --jq '.[0].tag_name')
if [ -z "$RELEASE_TAG" ] || [ "$RELEASE_TAG" = "null" ]; then
echo "::error::Could not determine latest lean4-nightly release"
exit 1
fi
echo "RELEASE_TAG=$RELEASE_TAG"
echo "RELEASE_TAG=$RELEASE_TAG" >> "$GITHUB_ENV"
echo "new=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
- name: Update lean-toolchain file
run: |
echo "leanprover/lean4:${RELEASE_TAG}" > lean-toolchain
- name: Commit toolchain bump (without pushing)
id: commit-bump
run: |
git add lean-toolchain
# Don't fail if there's nothing to commit (toolchain already up to date)
if git commit -m "chore: bump to ${RELEASE_TAG}"; then
echo "bumped=true" >> "$GITHUB_OUTPUT"
else
echo "bumped=false" >> "$GITHUB_OUTPUT"
echo "Toolchain already at ${RELEASE_TAG}, no bump needed"
fi
# ============================================================================
# Phase 2: Find and merge pr-testing branches
# ============================================================================
- name: Clone lean4-nightly and get PRs
id: get-prs
if: steps.commit-bump.outputs.bumped == 'true'
run: |
OLD="${{ steps.old-toolchain.outputs.old }}"
NEW="${{ steps.get-latest-release.outputs.new }}"
echo "Finding PRs between $OLD and $NEW"
NIGHTLY_URL="https://github.com/leanprover/lean4-nightly.git"
# Create a temporary directory for cloning
cd "$(mktemp -d)" || exit 1
# Clone the repository with a depth of 1
git clone --depth 1 "$NIGHTLY_URL"
# Navigate to the cloned repository
cd lean4-nightly || exit 1
# Fetch the $OLD tag
git fetch --depth=1 origin tag "$OLD" --no-tags
# Fetch the $NEW tag
git fetch origin tag "$NEW" --no-tags
# Get all commit SHAs between the $OLD and $NEW toolchains
COMMIT_SHAS=$(git log --format="%H" "$OLD..$NEW")
# Initialize an empty string to collect PR numbers
PRS=""
# For each commit, query the GitHub API to get associated PRs
for commit_sha in $COMMIT_SHAS; do
echo "Checking commit $commit_sha for associated PRs..."
# Query GitHub API for PRs associated with this commit
pr_numbers=$(curl -s -H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/leanprover/lean4/commits/$commit_sha/pulls" | \
jq -r '.[] | select(.merged_at != null) | .number | tostring' 2>/dev/null || echo "")
# Add each PR number to our list (duplicates will be handled later)
for pr_num in $pr_numbers; do
if [[ "$pr_num" =~ ^[0-9]+$ ]]; then
PRS="$PRS $pr_num"
echo "Found PR #$pr_num associated with commit $commit_sha"
fi
done
done
# Remove duplicates and trim whitespace
PRS=$(echo "$PRS" | tr ' ' '\n' | sort -u | tr '\n' ' ' | xargs)
# Output the PRs
echo "Found PRs: $PRS"
printf "prs<<EOF\n%s\nEOF" "$PRS" >> "$GITHUB_OUTPUT"
- name: Find matching pr-testing branches
id: find-branches
if: steps.commit-bump.outputs.bumped == 'true'
run: |
PRS="${{ steps.get-prs.outputs.prs }}"
echo "=== PRS ========================="
echo "$PRS"
# CRITICAL: If no PRs were found, skip branch matching entirely.
if [ -z "$PRS" ]; then
echo "No PRs found between old and new nightlies. Skipping branch discovery."
echo "branches_exist=false" >> "$GITHUB_ENV"
printf "branches<<EOF\n\nEOF" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "$PRS" | tr ' ' '\n' > prs.txt
echo "=== prs.txt ====================="
cat prs.txt
MATCHING_BRANCHES=$(git branch -r | grep -f prs.txt | grep "lean-pr-testing" || true)
echo "=== MATCHING_BRANCHES ==========="
echo "$MATCHING_BRANCHES"
echo "================================="
# Initialize an empty variable to store branches with relevant diffs
RELEVANT_BRANCHES=""
# Loop through each matching branch
for BRANCH in $MATCHING_BRANCHES; do
echo " === Testing $BRANCH for relevance."
# Get the diff filenames
DIFF_FILES=$(git diff --name-only "origin/nightly-testing...$BRANCH")
# Check if the diff contains files other than the specified ones
# Note: Batteries uses lakefile.toml, not lakefile.lean
if echo "$DIFF_FILES" | grep -v -e 'lake-manifest.json' -e 'lakefile.toml' -e 'lean-toolchain'; then
# Extract the actual branch name
ACTUAL_BRANCH=${BRANCH#origin/}
# Append the branch details to RELEVANT_BRANCHES
RELEVANT_BRANCHES="$RELEVANT_BRANCHES""$ACTUAL_BRANCH"$' '
fi
done
# Output the relevant branches
echo "=== RELEVANT_BRANCHES ==========="
echo "'$RELEVANT_BRANCHES'"
printf "branches<<EOF\n%s\nEOF" "$RELEVANT_BRANCHES" >> "$GITHUB_OUTPUT"
# Check if there are relevant branches
if [ -z "${RELEVANT_BRANCHES}" ]; then
echo "branches_exist=false" >> "$GITHUB_ENV"
else
echo "branches_exist=true" >> "$GITHUB_ENV"
fi
- name: Execute merge script for each branch
id: execute-merges
if: steps.commit-bump.outputs.bumped == 'true' && env.branches_exist == 'true'
run: |
BRANCHES="${{ steps.find-branches.outputs.branches }}"
# Initialize arrays to track results
SUCCESSFUL_MERGES=""
FAILED_MERGES=""
# Ensure the merge script is executable
chmod +x scripts/merge-lean-testing-pr.sh
# Process each branch
for BRANCH in $BRANCHES; do
# Extract PR number from branch name
PR_NUMBER=$(echo "$BRANCH" | grep -oP '\d+$')
# Make sure we're on nightly-testing branch before doing fetch operations
git checkout nightly-testing
# Fetch all tags in the repository
git fetch --tags
# Fetch the PR branch
git fetch origin "$BRANCH"
# Find the most recent nightly-testing-YYYY-MM-DD tag that is an ancestor of the branch
git checkout origin/"$BRANCH" || {
echo "Failed to checkout branch origin/$BRANCH, skipping"
continue
}
# Find tags that are ancestors of this branch with the right format
LATEST_TAG=$(git tag --merged HEAD | grep "nightly-testing-[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}" | sort -r | head -n 1)
echo "Latest tag found for $BRANCH: ${LATEST_TAG:-none}"
# Return to nightly-testing branch
git checkout nightly-testing
# Default to nightly-testing if no tag is found
if [ -z "$LATEST_TAG" ]; then
COMPARE_BASE="nightly-testing"
else
COMPARE_BASE="$LATEST_TAG"
fi
GITHUB_DIFF="https://github.com/leanprover-community/batteries/compare/$COMPARE_BASE...lean-pr-testing-$PR_NUMBER"
echo "Attempting to merge branch: $BRANCH (PR #$PR_NUMBER)"
echo "Using diff URL: $GITHUB_DIFF (comparing with $COMPARE_BASE)"
# Reset to a clean state before running merge script
git reset --hard HEAD
# Run the merge script and capture exit code
# Note: The merge script does its own commit but NOT push
if ./scripts/merge-lean-testing-pr.sh "$PR_NUMBER"; then
echo "Successfully merged $BRANCH"
SUCCESSFUL_MERGES="$SUCCESSFUL_MERGES$PR_NUMBER|$GITHUB_DIFF|$BRANCH "
else
echo "Failed to merge $BRANCH"
FAILED_MERGES="$FAILED_MERGES$PR_NUMBER|$GITHUB_DIFF|$BRANCH "
# Clean up - reset to a clean state
git reset --hard HEAD
git checkout nightly-testing
fi
done
# Output the results
echo "successful_merges=$SUCCESSFUL_MERGES" >> "$GITHUB_OUTPUT"
echo "failed_merges=$FAILED_MERGES" >> "$GITHUB_OUTPUT"
# ============================================================================
# Phase 3: Push everything and notify
# ============================================================================
- name: Push all changes
if: steps.commit-bump.outputs.bumped == 'true'
run: |
# This pushes the toolchain bump commit plus any successful merge commits
git push origin nightly-testing
- name: Prepare Zulip message
id: zulip-message
if: steps.commit-bump.outputs.bumped == 'true' && env.branches_exist == 'true'
run: |
SUCCESSFUL_MERGES="${{ steps.execute-merges.outputs.successful_merges }}"
FAILED_MERGES="${{ steps.execute-merges.outputs.failed_merges }}"
# Start building the message
MESSAGE=""
# Report successful merges
if [ -n "$SUCCESSFUL_MERGES" ]; then
MESSAGE+=$'### Successfully merged branches into Batteries\' \'nightly-testing\':\n\n'
for MERGE_INFO in $SUCCESSFUL_MERGES; do
IFS='|' read -r PR_NUMBER GITHUB_DIFF _ <<< "$MERGE_INFO"
MESSAGE+=$(printf -- '- [lean-pr-testing-%s](%s) (adaptations for lean#%s)' "$PR_NUMBER" "$GITHUB_DIFF" "$PR_NUMBER")$'\n\n'
done
MESSAGE+=$'\n'
else
MESSAGE+=$'No branches were successfully merged into Batteries\' \'nightly-testing\'. \n\n'
fi
# Report failed merges
if [ -n "$FAILED_MERGES" ]; then
MESSAGE+=$'### Failed merges:\n\nThe following branches need to be merged manually into Batteries\' \'nightly-testing\':\n\n'
for MERGE_INFO in $FAILED_MERGES; do
IFS='|' read -r PR_NUMBER GITHUB_DIFF _ <<< "$MERGE_INFO"
MESSAGE+=$(printf '- [lean-pr-testing-%s](%s) (adaptations for lean#%s)' "$PR_NUMBER" "$GITHUB_DIFF" "$PR_NUMBER")$'\n\n'
MESSAGE+=$'```bash\n'
MESSAGE+=$(printf 'scripts/merge-lean-testing-pr.sh %s' "$PR_NUMBER")$'\n'
MESSAGE+=$'```\n\n'
done
else
MESSAGE+=$'All branches were successfully merged!\n'
fi
# Output the message using the correct GitHub Actions syntax
printf 'msg<<EOF\n%s\nEOF' "$MESSAGE" | tee "$GITHUB_OUTPUT"
- name: Send message on Zulip
if: steps.commit-bump.outputs.bumped == 'true' && env.branches_exist == 'true'
uses: zulip/github-actions-zulip/send-message@e4c8f27c732ba9bd98ac6be0583096dea82feea5 # v1.0.2
with:
api-key: ${{ secrets.ZULIP_API_KEY }}
email: 'github-mathlib4-bot@leanprover.zulipchat.com'
organization-url: 'https://leanprover.zulipchat.com'
to: 'nightly-testing-batteries'
type: 'stream'
topic: 'Mergeable lean testing PRs (Batteries)'
content: |
${{ steps.zulip-message.outputs.msg }}
================================================
FILE: .github/workflows/nightly_detect_failure.yml
================================================
name: Post to zulip if the nightly-testing branch is failing.
on:
workflow_run:
workflows: ["ci"]
types:
- completed
jobs:
handle_failure:
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.head_branch == 'nightly-testing' }}
runs-on: ubuntu-latest
steps:
- name: Send message on Zulip
uses: zulip/github-actions-zulip/send-message@e4c8f27c732ba9bd98ac6be0583096dea82feea5 # v1.0.2
with:
api-key: ${{ secrets.ZULIP_API_KEY }}
email: 'github-mathlib4-bot@leanprover.zulipchat.com'
organization-url: 'https://leanprover.zulipchat.com'
to: 'nightly-testing-batteries'
type: 'stream'
topic: 'Batteries status updates'
content: |
❌ The latest CI for Batteries' [nightly-testing branch](https://github.com/${{ github.repository }}/tree/nightly-testing) has [failed](https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}) ([${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }})).
You can `git fetch; git checkout nightly-testing` and push a fix.
handle_success:
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'nightly-testing' }}
runs-on: ubuntu-latest
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.MATHLIB_NIGHTLY_TESTING_APP_ID }}
private-key: ${{ secrets.MATHLIB_NIGHTLY_TESTING_PRIVATE_KEY }}
# The create-github-app-token README states that this token is masked and will not be logged accidentally.
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: nightly-testing # checkout nightly-testing branch
fetch-depth: 0 # checkout all branches so that we can push from `nightly-testing` to `nightly-testing-YYYY-MM-DD`
token: ${{ steps.app-token.outputs.token }}
- name: Update the nightly-testing-YYYY-MM-DD branch
run: |
toolchain="$(<lean-toolchain)"
if [[ $toolchain =~ leanprover/lean4:nightly-([a-zA-Z0-9_-]+) ]]; then
version=${BASH_REMATCH[1]}
printf 'NIGHTLY=%s\n' "${version}" >> "${GITHUB_ENV}"
# Check if the remote tag exists
if git ls-remote --tags --exit-code origin "nightly-testing-$version" >/dev/null; then
printf 'Tag nightly-testing-%s already exists on the remote.' "${version}"
else
# If the tag does not exist, create and push the tag to remote
printf 'Creating tag %s from the current state of the nightly-testing branch.' "nightly-testing-${version}"
git tag "nightly-testing-${version}"
git push origin "nightly-testing-${version}"
fi
hash="$(git rev-parse "nightly-testing-${version}")"
printf 'SHA=%s\n' "${hash}" >> "${GITHUB_ENV}"
else
echo "Error: The file lean-toolchain does not contain the expected pattern."
exit 1
fi
# Now post a success message to zulip, if the last message there is not a success message.
# https://chat.openai.com/share/87656d2c-c804-4583-91aa-426d4f1537b3
- name: Install Zulip API client
run: pip install zulip
- name: Check last message and post if necessary
env:
ZULIP_EMAIL: 'github-mathlib4-bot@leanprover.zulipchat.com'
ZULIP_API_KEY: ${{ secrets.ZULIP_API_KEY }}
ZULIP_SITE: 'https://leanprover.zulipchat.com'
SHA: ${{ env.SHA }}
run: |
import os
import zulip
client = zulip.Client(email=os.getenv('ZULIP_EMAIL'), api_key=os.getenv('ZULIP_API_KEY'), site=os.getenv('ZULIP_SITE'))
# Get the last message from the bot in the 'status updates' topic.
# We narrow by sender to ignore human replies in between.
bot_email = 'github-mathlib4-bot@leanprover.zulipchat.com'
request = {
'anchor': 'newest',
'num_before': 1,
'num_after': 0,
'narrow': [
{'operator': 'stream', 'operand': 'nightly-testing-batteries'},
{'operator': 'topic', 'operand': 'Batteries status updates'},
{'operator': 'sender', 'operand': bot_email}
],
'apply_markdown': False # Otherwise the content test below fails.
}
response = client.get_messages(request)
messages = response['messages']
if not messages or messages[0]['content'] != f"✅ The latest CI for Batteries' [nightly-testing branch](https://github.com/${{ github.repository }}/tree/nightly-testing) has succeeded! ([{os.getenv('SHA')}](https://github.com/${{ github.repository }}/commit/{os.getenv('SHA')}))":
# Post the success message
request = {
'type': 'stream',
'to': 'nightly-testing-batteries',
'topic': 'Batteries status updates',
'content': f"✅ The latest CI for Batteries' [nightly-testing branch](https://github.com/${{ github.repository }}/tree/nightly-testing) has succeeded! ([{os.getenv('SHA')}](https://github.com/${{ github.repository }}/commit/{os.getenv('SHA')}))"
}
result = client.send_message(request)
print(result)
shell: python
# Next, determine if we should remind the humans to create a new PR to the `bump/v4.X.0` branch.
- name: Check for matching bump/nightly-YYYY-MM-DD branch
id: check_branch
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const branchName = `bump/nightly-${process.env.NIGHTLY}`;
console.log(`Looking for branch: ${branchName}`);
// Use paginate to get all branches
const branches = await github.paginate(github.rest.repos.listBranches, {
owner: context.repo.owner,
repo: context.repo.repo
});
const exists = branches.some(branch => branch.name === branchName);
if (exists) {
console.log(`Branch ${branchName} exists.`);
return true;
} else {
console.log(`Branch ${branchName} does not exist.`);
return false;
}
result-encoding: string
- name: Exit if matching branch exists
if: steps.check_branch.outputs.result == 'true'
run: |
echo "Matching bump/nightly-YYYY-MM-DD branch found, no further action needed."
exit 0
- name: Fetch latest bump branch name
id: latest_bump_branch
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
result-encoding: string
script: |
const branches = await github.paginate(github.rest.repos.listBranches, {
owner: context.repo.owner,
repo: context.repo.repo
});
const bumpBranches = branches
.map(branch => branch.name)
.filter(name => name.match(/^bump\/v4\.\d+\.0$/))
.sort((a, b) => b.localeCompare(a, undefined, {numeric: true, sensitivity: 'base'}));
if (!bumpBranches.length) {
throw new Exception("Did not find any bump/v4.x.0 branch")
}
const latestBranch = bumpBranches[0];
return latestBranch;
- name: Fetch lean-toolchain from latest bump branch
id: bump_version
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
try {
const response = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: 'lean-toolchain',
ref: '${{ steps.latest_bump_branch.outputs.result }}'
});
const content = Buffer.from(response.data.content, 'base64').toString();
const match = content.match(/leanprover\/lean4:nightly-(\d{4}-\d{2}-\d{2})/);
if (!match) {
core.setFailed('Toolchain pattern did not match');
core.setOutput('toolchain_content', content);
return null;
}
return match[1];
} catch (error) {
core.setFailed(error.message);
return null;
}
- name: Send warning message on Zulip if pattern doesn't match
if: failure()
uses: zulip/github-actions-zulip/send-message@e4c8f27c732ba9bd98ac6be0583096dea82feea5 # v1.0.2
with:
api-key: ${{ secrets.ZULIP_API_KEY }}
email: 'github-mathlib4-bot@leanprover.zulipchat.com'
organization-url: 'https://leanprover.zulipchat.com'
to: 'nightly-testing-batteries'
type: 'stream'
topic: 'Batteries status updates'
content: |
⚠️ Warning: The lean-toolchain file in the latest bump branch does not match the expected pattern 'leanprover/lean4:nightly-YYYY-MM-DD'.
Current content: ${{ steps.bump_version.outputs.toolchain_content }}
This needs to be fixed for the nightly testing process to work correctly.
- name: Setup for automatic PR creation
if: steps.check_branch.outputs.result == 'false'
env:
BUMP_VERSION: ${{ steps.bump_version.outputs.result }}
BUMP_BRANCH: ${{ steps.latest_bump_branch.outputs.result }}
SHA: ${{ env.SHA }}
run: |
echo "Installing zulip CLI..."
pip install zulip
echo "Configuring git identity for mathlib4-bot..."
git config --global user.name "mathlib4-bot"
git config --global user.email "github-mathlib4-bot@leanprover.zulipchat.com"
echo "Setting up zulip credentials..."
{
echo "[api]"
echo "email=github-mathlib4-bot@leanprover.zulipchat.com"
echo "key=${{ secrets.ZULIP_API_KEY }}"
echo "site=https://leanprover.zulipchat.com"
} > ~/.zuliprc
chmod 600 ~/.zuliprc
echo "Setup complete"
- name: Clean workspace and checkout Batteries
if: steps.check_branch.outputs.result == 'false'
run: |
sudo rm -rf -- *
# Regenerate the app token just before use.
# GitHub App tokens expire after 1 hour, and the preceding steps can take longer than that.
- name: Regenerate app token for Batteries checkout
if: steps.check_branch.outputs.result == 'false'
id: app-token-2
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.MATHLIB_NIGHTLY_TESTING_APP_ID }}
private-key: ${{ secrets.MATHLIB_NIGHTLY_TESTING_PRIVATE_KEY }}
- name: Checkout Batteries repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
if: steps.check_branch.outputs.result == 'false'
with:
ref: nightly-testing # checkout nightly-testing branch (shouldn't matter which)
fetch-depth: 0 # checkout all branches
token: ${{ steps.app-token-2.outputs.token }}
- name: Attempt automatic PR creation
id: auto_pr
if: steps.check_branch.outputs.result == 'false'
continue-on-error: true
env:
BUMP_VERSION: ${{ steps.bump_version.outputs.result }}
BUMP_BRANCH: ${{ steps.latest_bump_branch.outputs.result }}
SHA: ${{ env.SHA }}
GH_TOKEN: ${{ steps.app-token-2.outputs.token }}
ZULIP_API_KEY: ${{ secrets.ZULIP_API_KEY }}
run: |
echo "Current version: ${NIGHTLY}"
echo "Target bump branch: ${BUMP_BRANCH}"
echo "Using commit SHA: ${SHA}"
current_version="${NIGHTLY}"
bump_branch_suffix="${BUMP_BRANCH#bump/}"
echo "Running create-adaptation-pr.sh with:"
echo " bumpversion: ${bump_branch_suffix}"
echo " nightlydate: ${current_version}"
echo " nightlysha: ${SHA}"
./scripts/create-adaptation-pr.sh --bumpversion="${bump_branch_suffix}" --nightlydate="${current_version}" --nightlysha="${SHA}" --auto=yes
- name: Fallback to manual instructions
if: steps.auto_pr.outcome == 'failure' && steps.check_branch.outputs.result == 'false'
env:
BUMP_VERSION: ${{ steps.bump_version.outputs.result }}
BUMP_BRANCH: ${{ steps.latest_bump_branch.outputs.result }}
SHA: ${{ env.SHA }}
ZULIP_API_KEY: ${{ secrets.ZULIP_API_KEY }}
REPOSITORY: ${{ github.repository }}
CURRENT_RUN_ID: ${{ github.run_id }}
shell: python
run: |
import os
import re
import zulip
client = zulip.Client(config_file="~/.zuliprc")
current_version = os.getenv('NIGHTLY')
bump_version = os.getenv('BUMP_VERSION')
bump_branch = os.getenv('BUMP_BRANCH')
sha = os.getenv('SHA')
repository = os.getenv('REPOSITORY')
current_run_id = os.getenv('CURRENT_RUN_ID')
print(f'Current version: {current_version}, Bump version: {bump_version}, SHA: {sha}')
if current_version > bump_version:
print('Lean toolchain in `nightly-testing` is ahead of the bump branch.')
# Get the last message from the bot in the 'Batteries bump branch reminders' topic.
# We narrow by sender to ignore human replies in between.
bot_email = 'github-mathlib4-bot@leanprover.zulipchat.com'
request = {
'anchor': 'newest',
'num_before': 1,
'num_after': 0,
'narrow': [
{'operator': 'stream', 'operand': 'nightly-testing-batteries'},
{'operator': 'topic', 'operand': 'Batteries bump branch reminders'},
{'operator': 'sender', 'operand': bot_email}
],
'apply_markdown': False # Otherwise the content test below fails.
}
response = client.get_messages(request)
messages = response['messages']
last_bot_message = messages[0] if messages else None
bump_branch_suffix = bump_branch.replace('bump/', '')
failed_link = f"https://github.com/{repository}/actions/runs/{current_run_id}"
payload = f"🛠️: Automatic PR creation [failed]({failed_link}). Please create a new bump/nightly-{current_version} branch from nightly-testing (specifically {sha}), and then PR that to {bump_branch}. "
payload += "To do so semi-automatically, run the following script from Batteries root:\n\n"
payload += f"```bash\n./scripts/create-adaptation-pr.sh --bumpversion={bump_branch_suffix} --nightlydate={current_version} --nightlysha={sha}\n```\n"
# Check if we already posted a message for this nightly date and bump branch.
# We extract these fields from the last bot message rather than comparing substrings,
# since the message also contains a run ID that differs between workflow runs.
should_post = True
if last_bot_message:
last_content = last_bot_message['content']
# Extract nightly date and bump branch from last bot message
date_match = re.search(r'bump/nightly-(\d{4}-\d{2}-\d{2})', last_content)
branch_match = re.search(r'PR that to (bump/v[\d.]+)', last_content)
if date_match and branch_match:
last_date = date_match.group(1)
last_branch = branch_match.group(1)
if last_date == current_version and last_branch == bump_branch:
should_post = False
print(f'Already posted for nightly {current_version} and {bump_branch}')
if should_post:
if last_bot_message:
print("###### Last bot message:")
print(last_bot_message['content'])
print("###### Current message:")
print(payload)
# Post the reminder message
request = {
'type': 'stream',
'to': 'nightly-testing-batteries',
'topic': 'Batteries bump branch reminders',
'content': payload
}
result = client.send_message(request)
print(result)
else:
print('No action needed.')
================================================
FILE: .github/workflows/nightly_merge_master.yml
================================================
# This job merges every commit to `main` into `nightly-testing`, resolving merge conflicts in favor of `nightly-testing`.
name: Merge main to nightly
on:
push:
branches:
- main
jobs:
merge-to-nightly:
if: github.repository_owner == 'leanprover-community'
runs-on: ubuntu-latest
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.MATHLIB_NIGHTLY_TESTING_APP_ID }}
private-key: ${{ secrets.MATHLIB_NIGHTLY_TESTING_PRIVATE_KEY }}
# The create-github-app-token README states that this token is masked and will not be logged accidentally.
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Configure Git User
run: |
git config user.name "mathlib-nightly-testing[bot]"
git config user.email "mathlib-nightly-testing[bot]@users.noreply.github.com"
- name: Merge main to nightly favoring nightly changes
run: |
git checkout nightly-testing
git merge main --strategy-option ours --no-commit --allow-unrelated-histories
git commit -m "Merge main into nightly-testing"
git push origin nightly-testing
================================================
FILE: .github/workflows/test_mathlib.yml
================================================
# Test Mathlib against a Batteries PR
name: Test Mathlib
on:
workflow_run:
workflows: [ci]
types: [completed]
jobs:
on-success:
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' && github.repository == 'leanprover-community/batteries'
steps:
- name: Checkout PR
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Get PR info
id: pr-info
run: |
echo "pullRequestNumber=$(gh pr list --search $SHA --json number -q '.[0].number' || echo '')" >> $GITHUB_OUTPUT
echo "targetBranch=$(gh pr list --search $SHA --json baseRefName -q '.[0].baseRefName' || echo '')" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SHA: ${{ github.event.workflow_run.head_sha }}
- name: Generate app token
if: steps.pr-info.outputs.pullRequestNumber != '' && steps.pr-info.outputs.targetBranch == 'main'
id: app-token
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
with:
app-id: ${{ secrets.MATHLIB_NIGHTLY_TESTING_APP_ID }}
private-key: ${{ secrets.MATHLIB_NIGHTLY_TESTING_PRIVATE_KEY }}
owner: leanprover-community
repositories: mathlib4,mathlib4-nightly-testing
- name: Checkout mathlib4 repository
if: steps.pr-info.outputs.pullRequestNumber != '' && steps.pr-info.outputs.targetBranch == 'main'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: leanprover-community/mathlib4
token: ${{ steps.app-token.outputs.token }}
ref: master
fetch-depth: 0
- name: Add nightly-testing remote
if: steps.pr-info.outputs.pullRequestNumber != '' && steps.pr-info.outputs.targetBranch == 'main'
run: |
git remote add nightly-testing https://github.com/leanprover-community/mathlib4-nightly-testing.git
git fetch nightly-testing
- name: Install elan
if: steps.pr-info.outputs.pullRequestNumber != '' && steps.pr-info.outputs.targetBranch == 'main'
run: |
set -o pipefail
curl -sSfL https://github.com/leanprover/elan/releases/download/v3.0.0/elan-x86_64-unknown-linux-gnu.tar.gz | tar xz
./elan-init -y --default-toolchain none
echo "$HOME/.elan/bin" >> "${GITHUB_PATH}"
- name: Check if branch exists
if: steps.pr-info.outputs.pullRequestNumber != '' && steps.pr-info.outputs.targetBranch == 'main'
id: check_mathlib_tag
env:
PR_NUMBER: ${{ steps.pr-info.outputs.pullRequestNumber }}
HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
run: |
git config user.name "mathlib-nightly-testing[bot]"
git config user.email "mathlib-nightly-testing[bot]@users.noreply.github.com"
echo "PR info: $HEAD_REPO $HEAD_BRANCH"
BASE=master
echo "Using base tag: $BASE"
EXISTS="$(git ls-remote --heads nightly-testing batteries-pr-testing-$PR_NUMBER | wc -l)"
echo "Branch exists: $EXISTS"
if [ "$EXISTS" = "0" ]; then
echo "Branch does not exist, creating it."
git switch -c batteries-pr-testing-$PR_NUMBER "$BASE"
# Modify the lakefile.lean with the fork and branch name
sed -i "s,require \"leanprover-community\" / \"batteries\" @ git \".\+\",require \"leanprover-community\" / \"batteries\" from git \"https://github.com/$HEAD_REPO\" @ \"$HEAD_BRANCH\",g" lakefile.lean
lake update batteries
git add lakefile.lean lake-manifest.json
git commit -m "Update Batteries branch for testing https://github.com/leanprover-community/batteries/pull/$PR_NUMBER"
else
echo "Branch already exists, merging $BASE and bumping Batteries."
git switch batteries-pr-testing-$PR_NUMBER
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
lake update batteries
git add lake-manifest.json
git commit --allow-empty -m "Trigger CI for https://github.com/leanprover-community/batteries/pull/$PR_NUMBER"
fi
- name: Push changes
if: steps.pr-info.outputs.pullRequestNumber != '' && steps.pr-info.outputs.targetBranch == 'main'
env:
PR_NUMBER: ${{ steps.pr-info.outputs.pullRequestNumber }}
run: |
git push nightly-testing batteries-pr-testing-$PR_NUMBER
================================================
FILE: .gitignore
================================================
# Prior to v4.3.0-rc2 lake stored files in these locations.
# We'll leave them in the `.gitignore` for a while for users switching between toolchains.
/build/
/lake-packages/
/lakefile.olean
# After v4.3.0-rc2 lake stores its files here:
/.lake/
================================================
FILE: .gitpod.yml
================================================
image:
file: .docker/gitpod/Dockerfile
vscode:
extensions:
- leanprover.lean4
tasks:
- init: |
elan self update
lake build
================================================
FILE: .vscode/copyright.code-snippets
================================================
{
"Copyright header for batteries": {
"scope": "lean4",
"prefix": "copyright",
"body": [
"/-",
"Copyright (c) ${CURRENT_YEAR} $1. All rights reserved.",
"Released under Apache 2.0 license as described in the file LICENSE.",
"Authors: $1",
"-/"
]
}
}
================================================
FILE: .vscode/settings.json
================================================
{
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.rulers" : [100],
"files.encoding": "utf8",
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"search.usePCRE2": true
}
================================================
FILE: Batteries/Classes/Cast.lean
================================================
/-
Copyright (c) 2014 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Gabriel Ebner
-/
module
public import Batteries.Util.LibraryNote
@[expose] public section
library_note «coercion into rings» /--
Coercions such as `Nat.castCoe` that go from a concrete structure such as
`Nat` to an arbitrary ring `R` should be set up as follows:
```lean
instance : CoeTail Nat R where coe := ...
instance : CoeHTCT Nat R where coe := ...
```
It needs to be `CoeTail` instead of `Coe` because otherwise type-class
inference would loop when constructing the transitive coercion `Nat → Nat → Nat → ...`.
Sometimes we also need to declare the `CoeHTCT` instance
if we need to shadow another coercion
(e.g. `Nat.cast` should be used over `Int.ofNat`).
-/
================================================
FILE: Batteries/Classes/Deprecated.lean
================================================
/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
module
public import Batteries.Classes.Order
@[expose] public section
/-! Deprecated Batteries comparison classes
Examples are to ensure that old instances have equivalent new instances.
-/
set_option linter.deprecated false
namespace Batteries
/-- `OrientedCmp cmp` asserts that `cmp` is determined by the relation `cmp x y = .lt`. -/
@[deprecated Std.OrientedCmp (since := "2025-07-01")]
class OrientedCmp (cmp : α → α → Ordering) : Prop where
/-- The comparator operation is symmetric, in the sense that if `cmp x y` equals `.lt` then
`cmp y x = .gt` and vice versa. -/
symm (x y) : (cmp x y).swap = cmp y x
attribute [deprecated Std.OrientedOrd.eq_swap (since := "2025-07-01")] OrientedCmp.symm
namespace OrientedCmp
@[deprecated Std.OrientedCmp.gt_iff_lt (since := "2025-07-01")]
theorem cmp_eq_gt [OrientedCmp cmp] : cmp x y = .gt ↔ cmp y x = .lt := by
rw [← Ordering.swap_inj, symm]; exact .rfl
@[deprecated Std.OrientedCmp.le_iff_ge (since := "2025-07-01")]
theorem cmp_ne_gt [OrientedCmp cmp] : cmp x y ≠ .gt ↔ cmp y x ≠ .lt := not_congr cmp_eq_gt
@[deprecated Std.OrientedCmp.eq_comm (since := "2025-07-01")]
theorem cmp_eq_eq_symm [OrientedCmp cmp] : cmp x y = .eq ↔ cmp y x = .eq := by
rw [← Ordering.swap_inj, symm]; exact .rfl
@[deprecated Std.ReflCmp.compare_self (since := "2025-07-01")]
theorem cmp_refl [OrientedCmp cmp] : cmp x x = .eq :=
match e : cmp x x with
| .lt => nomatch e.symm.trans (cmp_eq_gt.2 e)
| .eq => rfl
| .gt => nomatch (cmp_eq_gt.1 e).symm.trans e
@[deprecated Std.OrientedCmp.not_lt_of_lt (since := "2025-07-01")]
theorem lt_asymm [OrientedCmp cmp] (h : cmp x y = .lt) : cmp y x ≠ .lt :=
fun h' => nomatch h.symm.trans (cmp_eq_gt.2 h')
@[deprecated Std.OrientedCmp.not_gt_of_gt (since := "2025-07-01")]
theorem gt_asymm [OrientedCmp cmp] (h : cmp x y = .gt) : cmp y x ≠ .gt :=
mt cmp_eq_gt.1 <| lt_asymm <| cmp_eq_gt.1 h
end OrientedCmp
/-- `TransCmp cmp` asserts that `cmp` induces a transitive relation. -/
@[deprecated Std.TransCmp (since := "2025-07-01")]
class TransCmp (cmp : α → α → Ordering) : Prop extends OrientedCmp cmp where
/-- The comparator operation is transitive. -/
le_trans : cmp x y ≠ .gt → cmp y z ≠ .gt → cmp x z ≠ .gt
attribute [deprecated Std.TransCmp.le_trans (since := "2025-07-01")] TransCmp.le_trans
namespace TransCmp
variable [TransCmp cmp]
open OrientedCmp Decidable
@[deprecated Std.TransCmp.ge_trans (since := "2025-07-01")]
theorem ge_trans (h₁ : cmp x y ≠ .lt) (h₂ : cmp y z ≠ .lt) : cmp x z ≠ .lt := by
have := @TransCmp.le_trans _ cmp _ z y x
simp [cmp_eq_gt] at *; exact this h₂ h₁
@[deprecated Std.TransCmp.lt_of_le_of_lt (since := "2025-07-01")]
theorem le_lt_trans (h₁ : cmp x y ≠ .gt) (h₂ : cmp y z = .lt) : cmp x z = .lt :=
byContradiction fun h₃ => ge_trans (mt cmp_eq_gt.2 h₁) h₃ h₂
@[deprecated Std.TransCmp.lt_of_lt_of_le (since := "2025-07-01")]
theorem lt_le_trans (h₁ : cmp x y = .lt) (h₂ : cmp y z ≠ .gt) : cmp x z = .lt :=
byContradiction fun h₃ => ge_trans h₃ (mt cmp_eq_gt.2 h₂) h₁
@[deprecated Std.TransCmp.lt_trans (since := "2025-07-01")]
theorem lt_trans (h₁ : cmp x y = .lt) (h₂ : cmp y z = .lt) : cmp x z = .lt :=
le_lt_trans (gt_asymm <| cmp_eq_gt.2 h₁) h₂
@[deprecated Std.TransCmp.gt_trans (since := "2025-07-01")]
theorem gt_trans (h₁ : cmp x y = .gt) (h₂ : cmp y z = .gt) : cmp x z = .gt := by
rw [cmp_eq_gt] at h₁ h₂ ⊢; exact lt_trans h₂ h₁
@[deprecated Std.TransCmp.congr_left (since := "2025-07-01")]
theorem cmp_congr_left (xy : cmp x y = .eq) : cmp x z = cmp y z :=
match yz : cmp y z with
| .lt => byContradiction (ge_trans (nomatch ·.symm.trans (cmp_eq_eq_symm.1 xy)) · yz)
| .gt => byContradiction (le_trans (nomatch ·.symm.trans (cmp_eq_eq_symm.1 xy)) · yz)
| .eq => match xz : cmp x z with
| .lt => nomatch ge_trans (nomatch ·.symm.trans xy) (nomatch ·.symm.trans yz) xz
| .gt => nomatch le_trans (nomatch ·.symm.trans xy) (nomatch ·.symm.trans yz) xz
| .eq => rfl
@[deprecated Std.TransCmp.congr_left (since := "2025-07-01")]
theorem cmp_congr_left' (xy : cmp x y = .eq) : cmp x = cmp y :=
funext fun _ => cmp_congr_left xy
@[deprecated Std.TransCmp.congr_right (since := "2025-07-01")]
theorem cmp_congr_right (yz : cmp y z = .eq) : cmp x y = cmp x z := by
rw [← Ordering.swap_inj, symm, symm, cmp_congr_left yz]
end TransCmp
instance [inst : OrientedCmp cmp] : OrientedCmp (flip cmp) where
symm _ _ := inst.symm ..
example [inst : Std.OrientedCmp cmp] : Std.OrientedCmp (flip cmp) := inferInstance
instance [inst : TransCmp cmp] : TransCmp (flip cmp) where
le_trans h1 h2 := inst.le_trans h2 h1
example [inst : Std.TransCmp cmp] : Std.TransCmp (flip cmp) := inferInstance
/-- `BEqCmp cmp` asserts that `cmp x y = .eq` and `x == y` coincide. -/
@[deprecated Std.LawfulBEqCmp (since := "2025-07-01")]
class BEqCmp [BEq α] (cmp : α → α → Ordering) : Prop where
/-- `cmp x y = .eq` holds iff `x == y` is true. -/
cmp_iff_beq : cmp x y = .eq ↔ x == y
attribute [deprecated Std.LawfulBEqCmp.compare_eq_iff_beq
(since := "2025-07-01")] BEqCmp.cmp_iff_beq
@[deprecated Std.LawfulEqCmp.compare_eq_iff_eq (since := "2025-07-01")]
theorem BEqCmp.cmp_iff_eq [BEq α] [LawfulBEq α] [BEqCmp (α := α) cmp] : cmp x y = .eq ↔ x = y := by
simp [BEqCmp.cmp_iff_beq]
/-- `LTCmp cmp` asserts that `cmp x y = .lt` and `x < y` coincide. -/
@[deprecated Std.LawfulLTCmp (since := "2025-07-01")]
class LTCmp [LT α] (cmp : α → α → Ordering) : Prop extends OrientedCmp cmp where
/-- `cmp x y = .lt` holds iff `x < y` is true. -/
cmp_iff_lt : cmp x y = .lt ↔ x < y
attribute [deprecated Std.LawfulLTCmp.eq_lt_iff_lt (since := "2025-07-01")] LTCmp.cmp_iff_lt
@[deprecated Std.LawfulLTCmp.eq_gt_iff_gt (since := "2025-07-01")]
theorem LTCmp.cmp_iff_gt [LT α] [LTCmp (α := α) cmp] : cmp x y = .gt ↔ y < x := by
rw [OrientedCmp.cmp_eq_gt, LTCmp.cmp_iff_lt]
/-- `LECmp cmp` asserts that `cmp x y ≠ .gt` and `x ≤ y` coincide. -/
@[deprecated Std.LawfulLECmp (since := "2025-07-01")]
class LECmp [LE α] (cmp : α → α → Ordering) : Prop extends OrientedCmp cmp where
/-- `cmp x y ≠ .gt` holds iff `x ≤ y` is true. -/
cmp_iff_le : cmp x y ≠ .gt ↔ x ≤ y
attribute [deprecated Std.LawfulLECmp.ne_gt_iff_le (since := "2025-07-01")] LECmp.cmp_iff_le
@[deprecated Std.LawfulLECmp.ne_lt_iff_ge (since := "2025-07-01")]
theorem LECmp.cmp_iff_ge [LE α] [LECmp (α := α) cmp] : cmp x y ≠ .lt ↔ y ≤ x := by
rw [← OrientedCmp.cmp_ne_gt, LECmp.cmp_iff_le]
/-- `LawfulCmp cmp` asserts that the `LE`, `LT`, `BEq` instances are all coherent with each other
and with `cmp`, describing a strict weak order (a linear order except for antisymmetry). -/
@[deprecated Std.LawfulBCmp (since := "2025-07-01")]
class LawfulCmp [LE α] [LT α] [BEq α] (cmp : α → α → Ordering) : Prop extends
TransCmp cmp, BEqCmp cmp, LTCmp cmp, LECmp cmp
/-- `OrientedOrd α` asserts that the `Ord` instance satisfies `OrientedCmp`. -/
@[deprecated Std.OrientedOrd (since := "2025-07-01")]
abbrev OrientedOrd (α) [Ord α] := OrientedCmp (α := α) compare
/-- `TransOrd α` asserts that the `Ord` instance satisfies `TransCmp`. -/
@[deprecated Std.TransOrd (since := "2025-07-01")]
abbrev TransOrd (α) [Ord α] := TransCmp (α := α) compare
/-- `BEqOrd α` asserts that the `Ord` and `BEq` instances are coherent via `BEqCmp`. -/
@[deprecated Std.LawfulBEqOrd (since := "2025-07-01")]
abbrev BEqOrd (α) [BEq α] [Ord α] := BEqCmp (α := α) compare
/-- `LTOrd α` asserts that the `Ord` instance satisfies `LTCmp`. -/
@[deprecated Std.LawfulLTOrd (since := "2025-07-01")]
abbrev LTOrd (α) [LT α] [Ord α] := LTCmp (α := α) compare
/-- `LEOrd α` asserts that the `Ord` instance satisfies `LECmp`. -/
@[deprecated Std.LawfulLEOrd (since := "2025-07-01")]
abbrev LEOrd (α) [LE α] [Ord α] := LECmp (α := α) compare
/-- `LawfulOrd α` asserts that the `Ord` instance satisfies `LawfulCmp`. -/
@[deprecated Std.LawfulBOrd (since := "2025-07-01")]
abbrev LawfulOrd (α) [LE α] [LT α] [BEq α] [Ord α] := LawfulCmp (α := α) compare
@[deprecated Std.TransCmp.compareOfLessAndEq_of_irrefl_of_trans_of_antisymm
(since := "2025-07-01")]
protected theorem TransCmp.compareOfLessAndEq
[LT α] [DecidableRel (LT.lt (α := α))] [DecidableEq α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(lt_antisymm : ∀ {x y : α}, ¬x < y → ¬y < x → x = y) :
TransCmp (α := α) (compareOfLessAndEq · ·) := by
have : OrientedCmp (α := α) (compareOfLessAndEq · ·) := by
refine { symm := fun x y => ?_ }
simp [compareOfLessAndEq]; split <;> [rename_i xy; split <;> [subst y; rename_i xy ne]]
· rw [if_neg, if_neg]; rfl
· rintro rfl; exact lt_irrefl _ xy
· exact fun yx => lt_irrefl _ (lt_trans xy yx)
· rw [if_neg ‹_›, if_pos rfl]; rfl
· split <;> [rfl; rename_i yx]
cases ne (lt_antisymm xy yx)
refine { this with le_trans := fun {x y z} yx zy => ?_ }
rw [Ne, this.cmp_eq_gt, compareOfLessAndEq_eq_lt] at yx zy ⊢
intro zx
if xy : x < y then exact zy (lt_trans zx xy)
else exact zy (lt_antisymm yx xy ▸ zx)
@[deprecated Std.TransCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
(since := "2025-07-01")]
theorem TransCmp.compareOfLessAndEq_of_le
[LT α] [LE α] [DecidableRel (LT.lt (α := α))] [DecidableEq α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(not_lt : ∀ {x y : α}, ¬x < y → y ≤ x)
(le_antisymm : ∀ {x y : α}, x ≤ y → y ≤ x → x = y) :
TransCmp (α := α) (compareOfLessAndEq · ·) :=
.compareOfLessAndEq lt_irrefl lt_trans fun xy yx => le_antisymm (not_lt yx) (not_lt xy)
@[deprecated Std.LawfulBEqCmp.compareOfLessAndEq_of_lt_irrefl (since := "2025-07-01")]
protected theorem BEqCmp.compareOfLessAndEq
[LT α] [DecidableRel (LT.lt (α := α))] [DecidableEq α] [BEq α] [LawfulBEq α]
(lt_irrefl : ∀ x : α, ¬x < x) :
BEqCmp (α := α) (compareOfLessAndEq · ·) where
cmp_iff_beq {x y} := by
simp [compareOfLessAndEq]
split <;> [skip; split] <;> simp [*]
rintro rfl; exact lt_irrefl _ ‹_›
@[deprecated Std.LawfulLTCmp.compareOfLessAndEq_of_irrefl_of_trans_of_antisymm
(since := "2025-07-01")]
protected theorem LTCmp.compareOfLessAndEq
[LT α] [DecidableRel (LT.lt (α := α))] [DecidableEq α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(lt_antisymm : ∀ {x y : α}, ¬x < y → ¬y < x → x = y) :
LTCmp (α := α) (compareOfLessAndEq · ·) :=
{ TransCmp.compareOfLessAndEq lt_irrefl lt_trans lt_antisymm with
cmp_iff_lt := compareOfLessAndEq_eq_lt }
@[deprecated Std.LawfulLTCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
(since := "2025-07-01")]
protected theorem LTCmp.compareOfLessAndEq_of_le
[LT α] [DecidableRel (LT.lt (α := α))] [DecidableEq α] [LE α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(not_lt : ∀ {x y : α}, ¬x < y → y ≤ x)
(le_antisymm : ∀ {x y : α}, x ≤ y → y ≤ x → x = y) :
LTCmp (α := α) (compareOfLessAndEq · ·) :=
{ TransCmp.compareOfLessAndEq_of_le lt_irrefl lt_trans not_lt le_antisymm with
cmp_iff_lt := compareOfLessAndEq_eq_lt }
@[deprecated Std.LawfulLECmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
(since := "2025-07-01")]
protected theorem LECmp.compareOfLessAndEq
[LT α] [DecidableRel (LT.lt (α := α))] [DecidableEq α] [LE α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(not_lt : ∀ {x y : α}, ¬x < y ↔ y ≤ x)
(le_antisymm : ∀ {x y : α}, x ≤ y → y ≤ x → x = y) :
LECmp (α := α) (compareOfLessAndEq · ·) :=
have := TransCmp.compareOfLessAndEq_of_le lt_irrefl lt_trans not_lt.1 le_antisymm
{ this with
cmp_iff_le := (this.cmp_ne_gt).trans <| (not_congr compareOfLessAndEq_eq_lt).trans not_lt }
@[deprecated Std.LawfulCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
(since := "2025-07-01")]
protected theorem LawfulCmp.compareOfLessAndEq
[LT α] [DecidableRel (LT.lt (α := α))] [DecidableEq α] [BEq α] [LawfulBEq α] [LE α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(not_lt : ∀ {x y : α}, ¬x < y ↔ y ≤ x)
(le_antisymm : ∀ {x y : α}, x ≤ y → y ≤ x → x = y) :
LawfulCmp (α := α) (compareOfLessAndEq · ·) :=
{ TransCmp.compareOfLessAndEq_of_le lt_irrefl lt_trans not_lt.1 le_antisymm,
LTCmp.compareOfLessAndEq_of_le lt_irrefl lt_trans not_lt.1 le_antisymm,
LECmp.compareOfLessAndEq lt_irrefl lt_trans not_lt le_antisymm,
BEqCmp.compareOfLessAndEq lt_irrefl with }
@[deprecated Std.LawfulLTCmp.eq_compareOfLessAndEq (since := "2025-07-01")]
theorem LTCmp.eq_compareOfLessAndEq
[LT α] [DecidableEq α] [BEq α] [LawfulBEq α] [BEqCmp cmp] [LTCmp cmp]
(x y : α) [Decidable (x < y)] : cmp x y = compareOfLessAndEq x y := by
simp [compareOfLessAndEq]
split <;> rename_i h1 <;> [skip; split <;> rename_i h2]
· exact LTCmp.cmp_iff_lt.2 h1
· exact BEqCmp.cmp_iff_eq.2 h2
· cases e : cmp x y
· cases h1 (LTCmp.cmp_iff_lt.1 e)
· cases h2 (BEqCmp.cmp_iff_eq.1 e)
· rfl
instance [inst₁ : OrientedCmp cmp₁] [inst₂ : OrientedCmp cmp₂] :
OrientedCmp (compareLex cmp₁ cmp₂) where
symm _ _ := by simp [compareLex, Ordering.swap_then]; rw [inst₁.symm, inst₂.symm]
example [inst₁ : Std.OrientedCmp cmp₁] [inst₂ : Std.OrientedCmp cmp₂] :
Std.OrientedCmp (compareLex cmp₁ cmp₂) := inferInstance
instance [inst₁ : TransCmp cmp₁] [inst₂ : TransCmp cmp₂] :
TransCmp (compareLex cmp₁ cmp₂) where
le_trans {a b c} h1 h2 := by
simp only [compareLex, ne_eq, Ordering.then_eq_gt, not_or, not_and] at h1 h2 ⊢
refine ⟨inst₁.le_trans h1.1 h2.1, fun e1 e2 => ?_⟩
match ab : cmp₁ a b with
| .gt => exact h1.1 ab
| .eq => exact inst₂.le_trans (h1.2 ab) (h2.2 (inst₁.cmp_congr_left ab ▸ e1)) e2
| .lt => exact h2.1 <| (inst₁.cmp_eq_gt).2 (inst₁.cmp_congr_left e1 ▸ ab)
example [inst₁ : Std.TransCmp cmp₁] [inst₂ : Std.TransCmp cmp₂] :
Std.TransCmp (compareLex cmp₁ cmp₂) := inferInstance
instance [Ord β] [OrientedOrd β] (f : α → β) : OrientedCmp (compareOn f) where
symm _ _ := OrientedCmp.symm (α := β) ..
example [Ord β] [Std.OrientedOrd β] (f : α → β) : Std.OrientedCmp (compareOn f) :=
inferInstance
instance [Ord β] [TransOrd β] (f : α → β) : TransCmp (compareOn f) where
le_trans := TransCmp.le_trans (α := β)
example [Ord β] [Std.TransOrd β] (f : α → β) : Std.TransCmp (compareOn f) :=
inferInstance
section «non-canonical instances»
-- Note: the following instances seem to cause lean to fail, see:
-- https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Typeclass.20inference.20crashes/near/432836360
/-- Local instance for `OrientedOrd lexOrd`. -/
@[deprecated "instance exists" (since := "2025-07-01")]
theorem OrientedOrd.instLexOrd [Ord α] [Ord β]
[OrientedOrd α] [OrientedOrd β] : @OrientedOrd (α × β) lexOrd := by
rw [OrientedOrd, lexOrd_def]; infer_instance
/-- Local instance for `TransOrd lexOrd`. -/
@[deprecated "instance exists" (since := "2025-07-01")]
theorem TransOrd.instLexOrd [Ord α] [Ord β]
[TransOrd α] [TransOrd β] : @TransOrd (α × β) lexOrd := by
rw [TransOrd, lexOrd_def]; infer_instance
/-- Local instance for `OrientedOrd ord.opposite`. -/
@[deprecated Std.OrientedOrd.opposite (since := "2025-07-01")]
theorem OrientedOrd.instOpposite [ord : Ord α] [inst : OrientedOrd α] :
@OrientedOrd _ ord.opposite where symm _ _ := inst.symm ..
/-- Local instance for `TransOrd ord.opposite`. -/
@[deprecated Std.TransOrd.opposite (since := "2025-07-01")]
theorem TransOrd.instOpposite [ord : Ord α] [inst : TransOrd α] : @TransOrd _ ord.opposite :=
{ OrientedOrd.instOpposite with le_trans := fun h1 h2 => inst.le_trans h2 h1 }
/-- Local instance for `OrientedOrd (ord.on f)`. -/
@[deprecated Std.OrientedOrd.instOn (since := "2025-07-01")]
theorem OrientedOrd.instOn [ord : Ord β] [OrientedOrd β] (f : α → β) : @OrientedOrd _ (ord.on f) :=
inferInstanceAs (@OrientedCmp _ (compareOn f))
/-- Local instance for `TransOrd (ord.on f)`. -/
@[deprecated Std.TransOrd.instOn (since := "2025-07-01")]
theorem TransOrd.instOn [ord : Ord β] [TransOrd β] (f : α → β) : @TransOrd _ (ord.on f) :=
inferInstanceAs (@TransCmp _ (compareOn f))
/-- Local instance for `OrientedOrd (oα.lex oβ)`. -/
@[deprecated "instance exists" (since := "2025-07-01")]
theorem OrientedOrd.instOrdLex [oα : Ord α] [oβ : Ord β] [OrientedOrd α] [OrientedOrd β] :
@OrientedOrd _ (oα.lex oβ) := OrientedOrd.instLexOrd
/-- Local instance for `TransOrd (oα.lex oβ)`. -/
@[deprecated "instance exists" (since := "2025-07-01")]
theorem TransOrd.instOrdLex [oα : Ord α] [oβ : Ord β] [TransOrd α] [TransOrd β] :
@TransOrd _ (oα.lex oβ) := TransOrd.instLexOrd
/-- Local instance for `OrientedOrd (oα.lex' oβ)`. -/
@[deprecated Std.OrientedOrd.instOrdLex' (since := "2025-07-01")]
theorem OrientedOrd.instOrdLex' (ord₁ ord₂ : Ord α) [@OrientedOrd _ ord₁] [@OrientedOrd _ ord₂] :
@OrientedOrd _ (ord₁.lex' ord₂) :=
inferInstanceAs (OrientedCmp (compareLex ord₁.compare ord₂.compare))
/-- Local instance for `TransOrd (oα.lex' oβ)`. -/
@[deprecated Std.TransOrd.instOrdLex' (since := "2025-07-01")]
theorem TransOrd.instOrdLex' (ord₁ ord₂ : Ord α) [@TransOrd _ ord₁] [@TransOrd _ ord₂] :
@TransOrd _ (ord₁.lex' ord₂) :=
inferInstanceAs (TransCmp (compareLex ord₁.compare ord₂.compare))
end «non-canonical instances»
================================================
FILE: Batteries/Classes/Order.lean
================================================
/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
module
public import Batteries.Tactic.Basic
public import Batteries.Tactic.SeqFocus
@[expose] public section
theorem lexOrd_def [Ord α] [Ord β] :
(lexOrd : Ord (α × β)).compare = compareLex (compareOn (·.1)) (compareOn (·.2)) := rfl
/-- Pull back a comparator by a function `f`, by applying the comparator to both arguments. -/
@[inline] def Ordering.byKey (f : α → β) (cmp : β → β → Ordering) (a b : α) : Ordering :=
cmp (f a) (f b)
namespace Batteries
/-- `TotalBLE le` asserts that `le` has a total order, that is, `le a b ∨ le b a`. -/
class TotalBLE (le : α → α → Bool) : Prop where
/-- `le` is total: either `le a b` or `le b a`. -/
total : le a b ∨ le b a
theorem compareOfLessAndEq_eq_lt {x y : α} [LT α] [Decidable (x < y)] [DecidableEq α] :
compareOfLessAndEq x y = .lt ↔ x < y := by
simp [compareOfLessAndEq]
split <;> simp
end Batteries
/-! Batteries features not in core Std -/
namespace Std
open Batteries (compareOfLessAndEq_eq_lt)
namespace OrientedCmp
variable {cmp : α → α → Ordering} [OrientedCmp cmp]
theorem le_iff_ge : cmp x y ≠ .gt ↔ cmp y x ≠ .lt :=
not_congr OrientedCmp.gt_iff_lt
end OrientedCmp
namespace TransCmp
variable {cmp : α → α → Ordering} [TransCmp cmp]
theorem le_trans : cmp x y ≠ .gt → cmp y z ≠ .gt → cmp x z ≠ .gt := by
simp only [ne_eq, ← Ordering.isLE_iff_ne_gt]; exact isLE_trans
theorem lt_of_lt_of_le : cmp x y = .lt → cmp y z ≠ .gt → cmp x z = .lt := by
simp only [ne_eq, ← Ordering.isLE_iff_ne_gt]; exact lt_of_lt_of_isLE
theorem lt_of_le_of_lt : cmp x y ≠ .gt → cmp y z = .lt → cmp x z = .lt := by
simp only [ne_eq, ← Ordering.isLE_iff_ne_gt]; exact lt_of_isLE_of_lt
theorem ge_trans : cmp x y ≠ .lt → cmp y z ≠ .lt → cmp x z ≠ .lt := by
simp only [ne_eq, ← Ordering.isGE_iff_ne_lt]; exact isGE_trans
theorem gt_of_gt_of_ge : cmp x y = .gt → cmp y z ≠ .lt → cmp x z = .gt := by
simp only [ne_eq, ← Ordering.isGE_iff_ne_lt]; exact gt_of_gt_of_isGE
theorem gt_of_ge_of_gt : cmp x y ≠ .lt → cmp y z = .gt → cmp x z = .gt := by
simp only [ne_eq, ← Ordering.isGE_iff_ne_lt]; exact gt_of_isGE_of_gt
end TransCmp
/-- `LawfulLTCmp cmp` asserts that `cmp x y = .lt` and `x < y` coincide. -/
class LawfulLTCmp [LT α] (cmp : α → α → Ordering) : Prop extends OrientedCmp cmp where
/-- `cmp x y = .lt` holds iff `x < y` is true. -/
eq_lt_iff_lt : cmp x y = .lt ↔ x < y
theorem LawfulLTCmp.eq_gt_iff_gt [LT α] [LawfulLTCmp (α := α) cmp] :
cmp x y = .gt ↔ y < x := by rw [OrientedCmp.gt_iff_lt, eq_lt_iff_lt]
/-- `LawfulLECmp cmp` asserts that `(cmp x y).isLE` and `x ≤ y` coincide. -/
class LawfulLECmp [LE α] (cmp : α → α → Ordering) : Prop extends OrientedCmp cmp where
/-- `cmp x y ≠ .gt` holds iff `x ≤ y` is true. -/
isLE_iff_le : (cmp x y).isLE ↔ x ≤ y
theorem LawfulLECmp.isGE_iff_ge [LE α] [LawfulLECmp (α := α) cmp] :
(cmp x y).isGE ↔ y ≤ x := by rw [← Ordering.isLE_swap, ← OrientedCmp.eq_swap, isLE_iff_le]
theorem LawfulLECmp.ne_gt_iff_le [LE α] [LawfulLECmp (α := α) cmp] :
cmp x y ≠ .gt ↔ x ≤ y := by rw [← isLE_iff_le (cmp := cmp), Ordering.isLE_iff_ne_gt]
theorem LawfulLECmp.ne_lt_iff_ge [LE α] [LawfulLECmp (α := α) cmp] :
cmp x y ≠ .lt ↔ y ≤ x := by rw [← isGE_iff_ge (cmp := cmp), Ordering.isGE_iff_ne_lt]
/-- `LawfulBCmp cmp` asserts that the `LE`, `LT`, `BEq` are all coherent with each other
and with `cmp`, describing a strict weak order (a linear order except for antisymmetry). -/
class LawfulBCmp [LE α] [LT α] [BEq α] (cmp : α → α → Ordering) : Prop extends
TransCmp cmp, LawfulBEqCmp cmp, LawfulLTCmp cmp, LawfulLECmp cmp
/-- `LawfulBCmp cmp` asserts that the `LE`, `LT`, `Eq` are all coherent with each other
and with `cmp`, describing a linear order. -/
class LawfulCmp [LE α] [LT α] (cmp : α → α → Ordering) : Prop extends
TransCmp cmp, LawfulEqCmp cmp, LawfulLTCmp cmp, LawfulLECmp cmp
/-- Class for types where the ordering function is compatible with the `LT`. -/
abbrev LawfulLTOrd (α) [LT α] [Ord α] := LawfulLTCmp (α := α) compare
/-- Class for types where the ordering function is compatible with the `LE`. -/
abbrev LawfulLEOrd (α) [LE α] [Ord α] := LawfulLECmp (α := α) compare
/-- Class for types where the ordering function is compatible with the `LE`, `LT` and `BEq`. -/
abbrev LawfulBOrd (α) [LE α] [LT α] [BEq α] [Ord α] := LawfulBCmp (α := α) compare
/-- Class for types where the ordering function is compatible with the `LE`, `LT` and `Eq`. -/
abbrev LawfulOrd (α) [LE α] [LT α] [Ord α] := LawfulCmp (α := α) compare
instance [inst : Std.OrientedCmp cmp] : Std.OrientedCmp (flip cmp) where
eq_swap := inst.eq_swap
instance [inst : Std.TransCmp cmp] : Std.TransCmp (flip cmp) where
isLE_trans h1 h2 := inst.isLE_trans h2 h1
instance (f : α → β) (cmp : β → β → Ordering) [Std.OrientedCmp cmp] :
Std.OrientedCmp (Ordering.byKey f cmp) where
eq_swap {a b} := Std.OrientedCmp.eq_swap (a := f a) (b := f b)
instance (f : α → β) (cmp : β → β → Ordering) [Std.TransCmp cmp] :
Std.TransCmp (Ordering.byKey f cmp) where
isLE_trans h₁ h₂ := Std.TransCmp.isLE_trans (α := β) h₁ h₂
instance [inst₁ : OrientedCmp cmp₁] [inst₂ : OrientedCmp cmp₂] :
OrientedCmp (compareLex cmp₁ cmp₂) := inferInstance
instance [inst₁ : TransCmp cmp₁] [inst₂ : TransCmp cmp₂] :
TransCmp (compareLex cmp₁ cmp₂) := inferInstance
instance [Ord β] [OrientedOrd β] (f : α → β) : OrientedCmp (compareOn f) := inferInstance
instance [Ord β] [TransOrd β] (f : α → β) : TransCmp (compareOn f) := inferInstance
theorem OrientedOrd.instOn [ord : Ord β] [OrientedOrd β] (f : α → β) : @OrientedOrd _ (ord.on f) :=
inferInstanceAs (@OrientedCmp _ (compareOn f))
theorem TransOrd.instOn [ord : Ord β] [TransOrd β] (f : α → β) : @TransOrd _ (ord.on f) :=
inferInstanceAs (@TransCmp _ (compareOn f))
theorem OrientedOrd.instOrdLex' (ord₁ ord₂ : Ord α) [@OrientedOrd _ ord₁] [@OrientedOrd _ ord₂] :
@OrientedOrd _ (ord₁.lex' ord₂) :=
inferInstanceAs (OrientedCmp (compareLex ord₁.compare ord₂.compare))
theorem TransOrd.instOrdLex' (ord₁ ord₂ : Ord α) [@TransOrd _ ord₁] [@TransOrd _ ord₂] :
@TransOrd _ (ord₁.lex' ord₂) :=
inferInstanceAs (TransCmp (compareLex ord₁.compare ord₂.compare))
theorem LawfulLTCmp.eq_compareOfLessAndEq
[LT α] [DecidableEq α] [LawfulEqCmp cmp] [LawfulLTCmp cmp]
(x y : α) [Decidable (x < y)] : cmp x y = compareOfLessAndEq x y := by
simp only [compareOfLessAndEq]
split <;> rename_i h1 <;> [skip; split <;> rename_i h2]
· exact LawfulLTCmp.eq_lt_iff_lt.2 h1
· exact LawfulEqCmp.compare_eq_iff_eq.2 h2
· cases e : cmp x y
· cases h1 (LawfulLTCmp.eq_lt_iff_lt.1 e)
· cases h2 (LawfulEqCmp.compare_eq_iff_eq.1 e)
· rfl
theorem ReflCmp.compareOfLessAndEq_of_lt_irrefl [LT α] [DecidableLT α] [DecidableEq α]
(lt_irrefl : ∀ x : α, ¬ x < x) :
ReflCmp (α := α) (compareOfLessAndEq · ·) where
compare_self {x} := by simp [compareOfLessAndEq, if_neg (lt_irrefl x)]
theorem LawfulBEqCmp.compareOfLessAndEq_of_lt_irrefl
[LT α] [DecidableLT α] [DecidableEq α] [BEq α] [LawfulBEq α]
(lt_irrefl : ∀ x : α, ¬x < x) :
LawfulBEqCmp (α := α) (compareOfLessAndEq · ·) where
compare_eq_iff_beq {x y} := by
simp [compareOfLessAndEq]
split <;> [skip; split] <;> simp [*]
rintro rfl; exact lt_irrefl _ ‹_›
-- redundant? See `compareOfLessAndEq_of_lt_trans_of_lt_iff` in core
theorem TransCmp.compareOfLessAndEq_of_irrefl_of_trans_of_antisymm
[LT α] [DecidableLT α] [DecidableEq α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(lt_antisymm : ∀ {x y : α}, ¬x < y → ¬y < x → x = y) :
TransCmp (α := α) (compareOfLessAndEq · ·) :=
TransOrd.compareOfLessAndEq_of_lt_trans_of_lt_iff lt_trans <| by
intros
constructor
· intro h₁
constructor
· intro h₂
apply lt_irrefl
exact lt_trans h₁ h₂
· intro | rfl => exact lt_irrefl _ h₁
· intro ⟨h₁, h₂⟩
by_contra h₃
apply h₂
exact lt_antisymm h₃ h₁
-- redundant? See `compareOfLessAndEq_of_antisymm_of_trans_of_total_of_not_le` in core
theorem TransCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
[LT α] [LE α] [DecidableLT α] [DecidableEq α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(not_lt : ∀ {x y : α}, ¬x < y → y ≤ x)
(le_antisymm : ∀ {x y : α}, x ≤ y → y ≤ x → x = y) :
TransCmp (α := α) (compareOfLessAndEq · ·) :=
.compareOfLessAndEq_of_irrefl_of_trans_of_antisymm
lt_irrefl lt_trans fun xy yx => le_antisymm (not_lt yx) (not_lt xy)
-- make redundant?
theorem LawfulLTCmp.compareOfLessAndEq_of_irrefl_of_trans_of_antisymm
[LT α] [DecidableLT α] [DecidableEq α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(lt_antisymm : ∀ {x y : α}, ¬x < y → ¬y < x → x = y) :
LawfulLTCmp (α := α) (compareOfLessAndEq · ·) :=
{ TransCmp.compareOfLessAndEq_of_irrefl_of_trans_of_antisymm
lt_irrefl lt_trans lt_antisymm with
eq_lt_iff_lt := Batteries.compareOfLessAndEq_eq_lt }
-- make redundant?
theorem LawfulLTCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
[LT α] [DecidableLT α] [DecidableEq α] [LE α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(not_lt : ∀ {x y : α}, ¬x < y → y ≤ x)
(le_antisymm : ∀ {x y : α}, x ≤ y → y ≤ x → x = y) :
LawfulLTCmp (α := α) (compareOfLessAndEq · ·) :=
{ TransCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
lt_irrefl lt_trans not_lt le_antisymm with
eq_lt_iff_lt := Batteries.compareOfLessAndEq_eq_lt }
-- make redundant?
theorem LawfulLECmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
[LT α] [DecidableLT α] [DecidableEq α] [LE α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(not_lt : ∀ {x y : α}, ¬x < y ↔ y ≤ x)
(le_antisymm : ∀ {x y : α}, x ≤ y → y ≤ x → x = y) :
LawfulLECmp (α := α) (compareOfLessAndEq · ·) :=
have := TransCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
lt_irrefl lt_trans not_lt.1 le_antisymm
{ this with
isLE_iff_le := by
intro x y
simp only [Ordering.isLE_iff_ne_gt, ← not_lt]
apply not_congr
rw [this.gt_iff_lt, Batteries.compareOfLessAndEq_eq_lt] }
theorem LawfulCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
[LT α] [LE α] [DecidableLT α] [DecidableLE α] [DecidableEq α]
(lt_irrefl : ∀ x : α, ¬x < x)
(lt_trans : ∀ {x y z : α}, x < y → y < z → x < z)
(not_lt : ∀ {x y : α}, ¬x < y ↔ y ≤ x)
(le_antisymm : ∀ {x y : α}, x ≤ y → y ≤ x → x = y) :
LawfulCmp (α := α) (compareOfLessAndEq · ·) :=
have instT := TransCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
lt_irrefl lt_trans not_lt.1 le_antisymm
have instLT := LawfulLTCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
lt_irrefl lt_trans not_lt.1 le_antisymm
have instLE := LawfulLECmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
lt_irrefl lt_trans not_lt le_antisymm
have le_refl (x : α) : x ≤ x := by rw [← not_lt]; exact lt_irrefl _
have not_le {x y : α} : ¬x ≤ y ↔ y < x := by simp [← not_lt]
{ instT, instLT, instLE with
eq_of_compare {_ _}:= by rw [compareOfLessAndEq_eq_eq le_refl not_le]; exact id
}
instance : LawfulOrd Nat :=
LawfulCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
Nat.lt_irrefl Nat.lt_trans Nat.not_lt Nat.le_antisymm
instance : LawfulOrd Int :=
LawfulCmp.compareOfLessAndEq_of_irrefl_of_trans_of_not_lt_of_antisymm
Int.lt_irrefl Int.lt_trans Int.not_lt Int.le_antisymm
instance : LawfulOrd Bool := by
apply LawfulCmp.mk <;> decide
instance : LawfulOrd (Fin n) where
eq_swap := OrientedCmp.eq_swap (α := Nat) (cmp := compare) ..
eq_lt_iff_lt := LawfulLTCmp.eq_lt_iff_lt (α := Nat) (cmp := compare)
isLE_iff_le := LawfulLECmp.isLE_iff_le (α := Nat) (cmp := compare)
isLE_trans := TransCmp.isLE_trans (α := Nat) (cmp := compare)
end Std
================================================
FILE: Batteries/Classes/RatCast.lean
================================================
/-
Copyright (c) 2014 Robert Lewis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Robert Lewis, Leonardo de Moura, Johannes Hölzl, Mario Carneiro, Gabriel Ebner
-/
module
@[expose] public section
/-- Type class for the canonical homomorphism `Rat → K`. -/
class RatCast (K : Type u) where
/-- The canonical homomorphism `Rat → K`. -/
protected ratCast : Rat → K
instance : RatCast Rat where ratCast n := n
/-- Canonical homomorphism from `Rat` to a division ring `K`.
This is just the bare function in order to aid in creating instances of `DivisionRing`. -/
@[coe, reducible, match_pattern] protected def Rat.cast {K : Type u} [RatCast K] : Rat → K :=
RatCast.ratCast
-- see note [coercion into rings]
instance [RatCast K] : CoeTail Rat K where coe := Rat.cast
-- see note [coercion into rings]
instance [RatCast K] : CoeHTCT Rat K where coe := Rat.cast
================================================
FILE: Batteries/Classes/SatisfiesM.lean
================================================
/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Kim Morrison
-/
module
public import Batteries.Lean.EStateM
public import Batteries.Lean.Except
@[expose] public section
/-!
## SatisfiesM
The `SatisfiesM` predicate works over an arbitrary (lawful) monad / applicative / functor,
and enables Hoare-like reasoning over monadic expressions. For example, given a monadic
function `f : α → m β`, to say that the return value of `f` satisfies `Q` whenever
the input satisfies `P`, we write `∀ a, P a → SatisfiesM Q (f a)`.
For any monad equipped with `MonadSatisfying m`
one can lift `SatisfiesM` to a monadic value in `Subtype`,
using `satisfying x h : m {a // p a}`, where `x : m α` and `h : SatisfiesM p x`.
This includes `Option`, `ReaderT`, `StateT`, and `ExceptT`, and the Lean monad stack.
(Although it is not entirely clear one should treat the Lean monad stack as lawful,
even though Lean accepts this.)
## Notes
`SatisfiesM` is not yet a satisfactory solution for verifying the behaviour of large scale monadic
programs. Such a solution would allow ergonomic reasoning about large `do` blocks,
with convenient mechanisms for introducing invariants and loop conditions as needed.
It is possible that in the future `SatiesfiesM` will become part of such a solution,
presumably requiring more syntactic support (and smarter `do` blocks) from Lean.
Or it may be that such a solution will look different!
This is an open research program, and for now one should not be overly ambitious using `SatisfiesM`.
In particular lemmas about pure operations on data structures in `Batteries` except for `HashMap`
should avoid `SatisfiesM` for now, so that it is easy to migrate to other approaches in future.
-/
/--
`SatisfiesM p (x : m α)` lifts propositions over a monad. It asserts that `x` may as well
have the type `x : m {a // p a}`, because there exists some `m {a // p a}` whose image is `x`.
So `p` is the postcondition of the monadic value.
-/
def SatisfiesM {m : Type u → Type v} [Functor m] (p : α → Prop) (x : m α) : Prop :=
∃ x' : m {a // p a}, Subtype.val <$> x' = x
namespace SatisfiesM
/-- If `p` is always true, then every `x` satisfies it. -/
theorem of_true [Functor m] [LawfulFunctor m] {x : m α}
(h : ∀ a, p a) : SatisfiesM p x :=
⟨(fun a => ⟨a, h a⟩) <$> x, by simp⟩
/--
If `p` is always true, then every `x` satisfies it.
(This is the strongest postcondition version of `of_true`.)
-/
protected theorem trivial [Functor m] [LawfulFunctor m] {x : m α} :
SatisfiesM (fun _ => True) x := of_true fun _ => trivial
/-- The `SatisfiesM p x` predicate is monotonic in `p`. -/
theorem imp [Functor m] [LawfulFunctor m] {x : m α}
(h : SatisfiesM p x) (H : ∀ {a}, p a → q a) : SatisfiesM q x :=
let ⟨x, h⟩ := h; ⟨(fun ⟨_, h⟩ => ⟨_, H h⟩) <$> x, by rw [← h, ← comp_map]; rfl⟩
/-- `SatisfiesM` distributes over `<$>`, general version. -/
protected theorem map [Functor m] [LawfulFunctor m] {x : m α}
(hx : SatisfiesM p x) (hf : ∀ {a}, p a → q (f a)) : SatisfiesM q (f <$> x) := by
let ⟨x', hx⟩ := hx
refine ⟨(fun ⟨a, h⟩ => ⟨f a, hf h⟩) <$> x', ?_⟩
rw [← hx]; simp
/--
`SatisfiesM` distributes over `<$>`, strongest postcondition version.
(Use this for reasoning forward from assumptions.)
-/
theorem map_post [Functor m] [LawfulFunctor m] {x : m α}
(hx : SatisfiesM p x) : SatisfiesM (fun b => ∃ a, p a ∧ b = f a) (f <$> x) :=
hx.map fun h => ⟨_, h, rfl⟩
/--
`SatisfiesM` distributes over `<$>`, weakest precondition version.
(Use this for reasoning backward from the goal.)
-/
theorem map_pre [Functor m] [LawfulFunctor m] {x : m α}
(hx : SatisfiesM (fun a => p (f a)) x) : SatisfiesM p (f <$> x) :=
hx.map fun h => h
/-- `SatisfiesM` distributes over `mapConst`, general version. -/
protected theorem mapConst [Functor m] [LawfulFunctor m] {x : m α}
(hx : SatisfiesM q x) (ha : ∀ {b}, q b → p a) : SatisfiesM p (Functor.mapConst a x) :=
map_const (f := m) ▸ hx.map ha
/-- `SatisfiesM` distributes over `pure`, general version / weakest precondition version. -/
protected theorem pure [Applicative m] [LawfulApplicative m]
(h : p a) : SatisfiesM (m := m) p (pure a) := ⟨pure ⟨_, h⟩, by simp⟩
/-- `SatisfiesM` distributes over `<*>`, general version. -/
protected theorem seq [Applicative m] [LawfulApplicative m] {x : m α}
(hf : SatisfiesM p₁ f) (hx : SatisfiesM p₂ x)
(H : ∀ {f a}, p₁ f → p₂ a → q (f a)) : SatisfiesM q (f <*> x) := by
match f, x, hf, hx with | _, _, ⟨f, rfl⟩, ⟨x, rfl⟩ => ?_
refine ⟨(fun ⟨a, h₁⟩ ⟨b, h₂⟩ => ⟨a b, H h₁ h₂⟩) <$> f <*> x, ?_⟩
simp only [← pure_seq]; simp [seq_assoc]
simp only [← pure_seq]; simp [seq_assoc, Function.comp_def]
/-- `SatisfiesM` distributes over `<*>`, strongest postcondition version. -/
protected theorem seq_post [Applicative m] [LawfulApplicative m] {x : m α}
(hf : SatisfiesM p₁ f) (hx : SatisfiesM p₂ x) :
SatisfiesM (fun c => ∃ f a, p₁ f ∧ p₂ a ∧ c = f a) (f <*> x) :=
hf.seq hx fun hf ha => ⟨_, _, hf, ha, rfl⟩
/--
`SatisfiesM` distributes over `<*>`, weakest precondition version 1.
(Use this when `x` and the goal are known and `f` is a subgoal.)
-/
protected theorem seq_pre [Applicative m] [LawfulApplicative m] {x : m α}
(hf : SatisfiesM (fun f => ∀ {a}, p₂ a → q (f a)) f) (hx : SatisfiesM p₂ x) :
SatisfiesM q (f <*> x) :=
hf.seq hx fun hf ha => hf ha
/--
`SatisfiesM` distributes over `<*>`, weakest precondition version 2.
(Use this when `f` and the goal are known and `x` is a subgoal.)
-/
protected theorem seq_pre' [Applicative m] [LawfulApplicative m] {x : m α}
(hf : SatisfiesM p₁ f) (hx : SatisfiesM (fun a => ∀ {f}, p₁ f → q (f a)) x) :
SatisfiesM q (f <*> x) :=
hf.seq hx fun hf ha => ha hf
/-- `SatisfiesM` distributes over `<*`, general version. -/
protected theorem seqLeft [Applicative m] [LawfulApplicative m] {x : m α}
(hx : SatisfiesM p₁ x) (hy : SatisfiesM p₂ y)
(H : ∀ {a b}, p₁ a → p₂ b → q a) : SatisfiesM q (x <* y) :=
seqLeft_eq x y ▸ (hx.map fun h _ => H h).seq_pre hy
/-- `SatisfiesM` distributes over `*>`, general version. -/
protected theorem seqRight [Applicative m] [LawfulApplicative m] {x : m α}
(hx : SatisfiesM p₁ x) (hy : SatisfiesM p₂ y)
(H : ∀ {a b}, p₁ a → p₂ b → q b) : SatisfiesM q (x *> y) :=
seqRight_eq x y ▸ (hx.map fun h _ => H h).seq_pre hy
/-- `SatisfiesM` distributes over `>>=`, general version. -/
protected theorem bind [Monad m] [LawfulMonad m] {f : α → m β}
(hx : SatisfiesM p x) (hf : ∀ a, p a → SatisfiesM q (f a)) :
SatisfiesM q (x >>= f) := by
match x, hx with | _, ⟨x, rfl⟩ => ?_
have g a ha := Classical.indefiniteDescription _ (hf a ha)
refine ⟨x >>= fun ⟨a, h⟩ => g a h, ?_⟩
simp [← bind_pure_comp]; congr; funext ⟨a, h⟩; simp [← (g a h).2, ← bind_pure_comp]
/-- `SatisfiesM` distributes over `>>=`, weakest precondition version. -/
protected theorem bind_pre [Monad m] [LawfulMonad m] {f : α → m β}
(hx : SatisfiesM (fun a => SatisfiesM q (f a)) x) :
SatisfiesM q (x >>= f) := hx.bind fun _ h => h
end SatisfiesM
@[simp] theorem SatisfiesM_Id_eq : SatisfiesM (m := Id) p x ↔ p x :=
⟨fun ⟨y, eq⟩ => eq ▸ y.2, fun h => ⟨⟨_, h⟩, rfl⟩⟩
@[simp] theorem SatisfiesM_Option_eq : SatisfiesM (m := Option) p x ↔ ∀ a, x = some a → p a :=
⟨by revert x; intro | some _, ⟨some ⟨_, h⟩, rfl⟩, _, rfl => exact h,
fun h => match x with | some a => ⟨some ⟨a, h _ rfl⟩, rfl⟩ | none => ⟨none, rfl⟩⟩
@[simp] theorem SatisfiesM_Except_eq : SatisfiesM (m := Except ε) p x ↔ ∀ a, x = .ok a → p a :=
⟨by revert x; intro | .ok _, ⟨.ok ⟨_, h⟩, rfl⟩, _, rfl => exact h,
fun h => match x with | .ok a => ⟨.ok ⟨a, h _ rfl⟩, rfl⟩ | .error e => ⟨.error e, rfl⟩⟩
theorem SatisfiesM_EStateM_eq :
SatisfiesM (m := EStateM ε σ) p x ↔ ∀ s a s', x.run s = .ok a s' → p a := by
constructor
· rintro ⟨x, rfl⟩ s a s' h
match w : x.run s with
| .ok a s' => simp at h; exact h.1
| .error e s' => simp [w] at h
· intro w
refine ⟨?_, ?_⟩
· intro s
match q : x.run s with
| .ok a s' => exact .ok ⟨a, w s a s' q⟩ s'
| .error e s' => exact .error e s'
· ext s
rw [EStateM.run_map, EStateM.run]
split <;> simp_all
theorem SatisfiesM_ReaderT_eq [Monad m] :
SatisfiesM (m := ReaderT ρ m) p x ↔ ∀ s, SatisfiesM p (x.run s) :=
(exists_congr fun a => by exact ⟨fun eq _ => eq ▸ rfl, funext⟩).trans Classical.skolem.symm
theorem SatisfiesM_StateRefT_eq [Monad m] :
SatisfiesM (m := StateRefT' ω σ m) p x ↔ ∀ s, SatisfiesM p (x s) :=
SatisfiesM_ReaderT_eq
theorem SatisfiesM_StateT_eq [Monad m] [LawfulMonad m] :
SatisfiesM (m := StateT ρ m) (α := α) p x ↔ ∀ s, SatisfiesM (m := m) (p ·.1) (x.run s) := by
change SatisfiesM (m := StateT ρ m) (α := α) p x ↔ ∀ s, SatisfiesM (m := m) (p ·.1) (x s)
refine .trans ⟨fun ⟨f, eq⟩ => eq ▸ ?_, fun ⟨f, h⟩ => ?_⟩ Classical.skolem.symm
· refine ⟨fun s => (fun ⟨⟨a, h⟩, s'⟩ => ⟨⟨a, s'⟩, h⟩) <$> f s, fun s => ?_⟩
rw [← comp_map, map_eq_pure_bind]; rfl
· refine ⟨fun s => (fun ⟨⟨a, s'⟩, h⟩ => ⟨⟨a, h⟩, s'⟩) <$> f s, funext fun s => ?_⟩
show _ >>= _ = _; simp [← h]
theorem SatisfiesM_ExceptT_eq [Monad m] [LawfulMonad m] :
SatisfiesM (m := ExceptT ρ m) (α := α) p x ↔
SatisfiesM (m := m) (∀ a, · = .ok a → p a) x.run := by
change _ ↔ SatisfiesM (m := m) (∀ a, · = .ok a → p a) x
refine ⟨fun ⟨f, eq⟩ => eq ▸ ?_, fun ⟨f, eq⟩ => eq ▸ ?_⟩
· exists (fun | .ok ⟨a, h⟩ => ⟨.ok a, fun | _, rfl => h⟩ | .error e => ⟨.error e, nofun⟩) <$> f
show _ = _ >>= _; rw [← comp_map, map_eq_pure_bind]; congr; funext a; cases a <;> rfl
· exists ((fun | ⟨.ok a, h⟩ => .ok ⟨a, h _ rfl⟩ | ⟨.error e, _⟩ => .error e) <$> f : m _)
show _ >>= _ = _; simp [← bind_pure_comp]; congr; funext ⟨a, h⟩; cases a <;> rfl
/--
If a monad has `MonadSatisfying m`, then we can lift a `h : SatisfiesM (m := m) p x` predicate
to monadic value `satisfying x p : m { x // p x }`.
Reader, state, and exception monads have `MonadSatisfying` instances if the base monad does.
-/
class MonadSatisfying (m : Type u → Type v) [Functor m] [LawfulFunctor m] where
/-- Lift a `SatisfiesM` predicate to a monadic value. -/
satisfying {p : α → Prop} {x : m α} (h : SatisfiesM (m := m) p x) : m {a // p a}
/-- The value of the lifted monadic value is equal to the original monadic value. -/
val_eq {p : α → Prop} {x : m α} (h : SatisfiesM (m := m) p x) : Subtype.val <$> satisfying h = x
export MonadSatisfying (satisfying)
namespace MonadSatisfying
instance : MonadSatisfying Id where
satisfying {α p x} h := ⟨x, by obtain ⟨⟨_, h⟩, rfl⟩ := h; exact h⟩
val_eq {α p x} h := rfl
instance : MonadSatisfying Option where
satisfying {α p x?} h :=
have h' := SatisfiesM_Option_eq.mp h
match x? with
| none => none
| some x => some ⟨x, h' x rfl⟩
val_eq {α p x?} h := by cases x? <;> simp
instance : MonadSatisfying (Except ε) where
satisfying {α p x?} h :=
have h' := SatisfiesM_Except_eq.mp h
match x? with
| .ok x => .ok ⟨x, h' x rfl⟩
| .error e => .error e
val_eq {α p x?} h := by cases x? <;> simp
instance [Monad m] [LawfulMonad m][MonadSatisfying m] : MonadSatisfying (ReaderT ρ m) where
satisfying {α p x} h :=
have h' := SatisfiesM_ReaderT_eq.mp h
fun r => satisfying (h' r)
val_eq {α p x} h := by
have h' := SatisfiesM_ReaderT_eq.mp h
ext r
rw [ReaderT.run_map, ← MonadSatisfying.val_eq (h' r)]
rfl
instance [Monad m] [LawfulMonad m] [MonadSatisfying m] : MonadSatisfying (StateRefT' ω σ m) :=
inferInstanceAs <| MonadSatisfying (ReaderT (ST.Ref ω σ) m)
instance [Monad m] [LawfulMonad m] [MonadSatisfying m] : MonadSatisfying (StateT ρ m) where
satisfying {α p x} h :=
have h' := SatisfiesM_StateT_eq.mp h
fun r => (fun ⟨⟨a, r'⟩, h⟩ => ⟨⟨a, h⟩, r'⟩) <$> satisfying (h' r)
val_eq {α p x} h := by
have h' := SatisfiesM_StateT_eq.mp h
ext r
rw [← MonadSatisfying.val_eq (h' r), StateT.run_map]
simp [StateT.run]
instance [Monad m] [LawfulMonad m] [MonadSatisfying m] : MonadSatisfying (ExceptT ε m) where
satisfying {α p x} h :=
let x' := satisfying (SatisfiesM_ExceptT_eq.mp h)
ExceptT.mk ((fun ⟨y, w⟩ => y.pmap fun a h => ⟨a, w _ h⟩) <$> x')
val_eq {α p x} h := by
ext
refine Eq.trans ?_ (MonadSatisfying.val_eq (SatisfiesM_ExceptT_eq.mp h))
simp
instance : MonadSatisfying (EStateM ε σ) where
satisfying {α p x} h :=
have h' := SatisfiesM_EStateM_eq.mp h
fun s => match w : x.run s with
| .ok a s' => .ok ⟨a, h' s a s' w⟩ s'
| .error e s' => .error e s'
val_eq {α p x} h := by
ext s
rw [EStateM.run_map, EStateM.run]
split <;> simp_all
end MonadSatisfying
================================================
FILE: Batteries/CodeAction/Attr.lean
================================================
/-
Copyright (c) 2023 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
module
public import Lean.Server.CodeActions.Basic
public import Lean.Compiler.IR.CompilerM
@[expose] public section
/-!
# Initial setup for code action attributes
* `@[hole_code_action]` and `@[command_code_action]` now live in the Lean repository,
and are builtin.
* Attribute `@[tactic_code_action]` collects code actions which will be called
on each occurrence of a tactic.
-/
namespace Batteries.CodeAction
open Lean Elab Server Lsp RequestM Snapshots
/-- A tactic code action extension. -/
abbrev TacticCodeAction :=
CodeActionParams → Snapshot →
(ctx : ContextInfo) → (stack : Syntax.Stack) → (node : InfoTree) →
RequestM (Array LazyCodeAction)
/-- A tactic code action extension. -/
abbrev TacticSeqCodeAction :=
CodeActionParams → Snapshot →
(ctx : ContextInfo) → (i : Nat) → (stack : Syntax.Stack) → (goals : List MVarId) →
RequestM (Array LazyCodeAction)
/-- Read a tactic code action from a declaration of the right type. -/
def mkTacticCodeAction (n : Name) : ImportM TacticCodeAction := do
let { env, opts, .. } ← read
IO.ofExcept <| unsafe env.evalConstCheck TacticCodeAction opts ``TacticCodeAction n
/-- Read a tacticSeq code action from a declaration of the right type. -/
def mkTacticSeqCodeAction (n : Name) : ImportM TacticSeqCodeAction := do
let { env, opts, .. } ← read
IO.ofExcept <| unsafe env.evalConstCheck TacticSeqCodeAction opts ``TacticSeqCodeAction n
/-- An entry in the tactic code actions extension, containing the attribute arguments. -/
structure TacticCodeActionEntry where
/-- The declaration to tag -/
declName : Name
/-- The tactic kinds that this extension supports. If empty it is called on all tactic kinds. -/
tacticKinds : Array Name
deriving Inhabited
/-- The state of the tactic code actions extension. -/
structure TacticCodeActions where
/-- The list of tactic code actions to apply on any tactic. -/
onAnyTactic : Array TacticCodeAction := {}
/-- The list of tactic code actions to apply when a particular tactic kind is highlighted. -/
onTactic : NameMap (Array TacticCodeAction) := {}
deriving Inhabited
/-- Insert a tactic code action entry into the `TacticCodeActions` structure. -/
def TacticCodeActions.insert (self : TacticCodeActions)
(tacticKinds : Array Name) (action : TacticCodeAction) : TacticCodeActions :=
if tacticKinds.isEmpty then
{ self with onAnyTactic := self.onAnyTactic.push action }
else
{ self with onTactic := tacticKinds.foldl (init := self.onTactic) fun m a =>
m.insert a ((m.getD a #[]).push action) }
/-- An extension which collects all the tactic code actions. -/
initialize tacticSeqCodeActionExt :
PersistentEnvExtension Name (Name × TacticSeqCodeAction)
(Array Name × Array TacticSeqCodeAction) ←
registerPersistentEnvExtension {
mkInitial := pure (#[], #[])
addImportedFn := fun as => return (#[], ← as.foldlM (init := #[]) fun m as =>
as.foldlM (init := m) fun m a => return m.push (← mkTacticSeqCodeAction a))
addEntryFn := fun (s₁, s₂) (n₁, n₂) => (s₁.push n₁, s₂.push n₂)
exportEntriesFn := (·.1)
}
/-- An extension which collects all the tactic code actions. -/
initialize tacticCodeActionExt :
PersistentEnvExtension TacticCodeActionEntry (TacticCodeActionEntry × TacticCodeAction)
(Array TacticCodeActionEntry × TacticCodeActions) ←
registerPersistentEnvExtension {
mkInitial := pure (#[], {})
addImportedFn := fun as => return (#[], ← as.foldlM (init := {}) fun m as =>
as.foldlM (init := m) fun m ⟨name, kinds⟩ =>
return m.insert kinds (← mkTacticCodeAction name))
addEntryFn := fun (s₁, s₂) (e, n₂) => (s₁.push e, s₂.insert e.tacticKinds n₂)
exportEntriesFn := (·.1)
}
/--
This attribute marks a code action, which is used to suggest new tactics or replace existing ones.
* `@[tactic_code_action]`: This is a code action which applies to the spaces between tactics,
to suggest a new tactic to change the goal state.
* `@[tactic_code_action kind]`: This is a code action which applies to applications of the tactic
`kind` (a tactic syntax kind), which can replace the tactic or insert things before or after it.
* `@[tactic_code_action kind₁ kind₂]`: shorthand for
`@[tactic_code_action kind₁, tactic_code_action kind₂]`.
* `@[tactic_code_action *]`: This is a tactic code action that applies to all tactics.
Use sparingly.
-/
syntax (name := tactic_code_action) "tactic_code_action" ("*" <|> (ppSpace ident)*) : attr
initialize
registerBuiltinAttribute {
name := `tactic_code_action
descr := "Declare a new tactic code action, to appear in the code actions on tactics"
applicationTime := .afterCompilation
add := fun decl stx kind => do
unless kind == AttributeKind.global do
throwError "invalid attribute 'tactic_code_action', must be global"
match stx with
| `(attr| tactic_code_action *) =>
if (IR.getSorryDep (← getEnv) decl).isSome then return -- ignore in progress definitions
modifyEnv (tacticCodeActionExt.addEntry · (⟨decl, #[]⟩, ← mkTacticCodeAction decl))
| `(attr| tactic_code_action $[$args]*) =>
if args.isEmpty then
if (IR.getSorryDep (← getEnv) decl).isSome then return -- ignore in progress definitions
modifyEnv (tacticSeqCodeActionExt.addEntry · (decl, ← mkTacticSeqCodeAction decl))
else
let args ← args.mapM realizeGlobalConstNoOverloadWithInfo
if (IR.getSorryDep (← getEnv) decl).isSome then return -- ignore in progress definitions
modifyEnv (tacticCodeActionExt.addEntry · (⟨decl, args⟩, ← mkTacticCodeAction decl))
| _ => pure ()
}
================================================
FILE: Batteries/CodeAction/Basic.lean
================================================
/-
Copyright (c) 2023 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
module
public meta import Lean.Elab.BuiltinTerm
public meta import Lean.Elab.BuiltinNotation
public meta import Lean.Server.InfoUtils
public meta import Lean.Server.CodeActions.Provider
public meta import Batteries.CodeAction.Attr
public meta section
/-!
# Initial setup for code actions
This declares a code action provider that calls all `@[hole_code_action]` definitions
on each occurrence of a hole (`_`, `?_` or `sorry`).
(This is in a separate file from `Batteries.CodeAction.Hole.Attr` so that the server does not
attempt to use this code action provider when browsing the `Batteries.CodeAction.Hole.Attr` file
itself.)
-/
namespace Batteries.CodeAction
open Lean Elab Server RequestM CodeAction
/-- A code action which calls `@[tactic_code_action]` code actions. -/
@[code_action_provider] def tacticCodeActionProvider : CodeActionProvider := fun params snap => do
let doc ← readDoc
let startPos := doc.meta.text.lspPosToUtf8Pos params.range.start
let endPos := doc.meta.text.lspPosToUtf8Pos params.range.end
let pointerCol :=
if params.range.start.line == params.range.end.line then
max params.range.start.character params.range.end.character
else 0
let some result := findTactic?
(fun pos => (doc.meta.text.utf8PosToLspPos pos).character ≤ pointerCol)
⟨startPos, endPos⟩ snap.stx | return #[]
let tgtTac := match result with
| .tactic (tac :: _)
| .tacticSeq _ _ (_ :: tac :: _) => tac.1
| _ => unreachable!
let tgtRange := tgtTac.getRange?.get!
have info := findInfoTree? tgtTac.getKind tgtRange none snap.infoTree (canonicalOnly := true)
fun _ info => info matches .ofTacticInfo _
let some (ctx, node@(.node (.ofTacticInfo info) _)) := info | return #[]
let mut out := #[]
match result with
| .tactic stk@((tac, _) :: _) => do
let ctx := { ctx with mctx := info.mctxBefore }
let actions := (tacticCodeActionExt.getState snap.env).2
if let some arr := actions.onTactic.find? tac.getKind then
for act in arr do
try out := out ++ (← act params snap ctx stk node) catch _ => pure ()
for act in actions.onAnyTactic do
try out := out ++ (← act params snap ctx stk node) catch _ => pure ()
| .tacticSeq _ i stk@((seq, _) :: _) =>
let (ctx, goals) ← if 2*i < seq.getNumArgs then
let stx := seq[2*i]
let some stxRange := stx.getRange? | return #[]
let some (ctx, .node (.ofTacticInfo info') _) :=
findInfoTree? stx.getKind stxRange ctx node fun _ info => (info matches .ofTacticInfo _)
| return #[]
pure ({ ctx with mctx := info'.mctxBefore }, info'.goalsBefore)
else
pure ({ ctx with mctx := info.mctxAfter }, info.goalsAfter)
for act in (tacticSeqCodeActionExt.getState snap.env).2 do
try out := out ++ (← act params snap ctx i stk goals) catch _ => pure ()
| _ => unreachable!
pure out
================================================
FILE: Batteries/CodeAction/Deprecated.lean
================================================
/-
Copyright (c) 2023 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
module
public meta import Lean.Server.CodeActions.Provider
public meta section
/-!
# Code action for @[deprecated] replacements
This is an opt-in mechanism for making machine-applicable `@[deprecated]` definitions. When enabled
(by setting the `machineApplicableDeprecated` tag attribute), a code action will be triggered
whenever the deprecation lint also fires, allowing the user to replace the usage of the deprecated
constant.
-/
namespace Batteries
open Lean Elab Server Lsp RequestM CodeAction
/-- An environment extension for identifying `@[deprecated]` definitions which can be auto-fixed -/
initialize machineApplicableDeprecated : TagDeclarationExtension ← mkTagDeclarationExtension
namespace CodeAction
/-- A code action which applies replacements for `@[deprecated]` definitions. -/
@[code_action_provider]
def deprecatedCodeActionProvider : CodeActionProvider := fun params snap => do
let mut i := 0
let doc ← readDoc
let mut msgs := #[]
for m in snap.msgLog.toList do
if m.data.isDeprecationWarning then
if h : _ then
msgs := msgs.push (snap.cmdState.messages.toList[i]'h)
i := i + 1
if msgs.isEmpty then return #[]
let start := doc.meta.text.lspPosToUtf8Pos params.range.start
let stop := doc.meta.text.lspPosToUtf8Pos params.range.end
for msg in msgs do
let some endPos := msg.endPos | continue
let pos := doc.meta.text.ofPosition msg.pos
let endPos' := doc.meta.text.ofPosition endPos
unless start ≤ endPos' && pos ≤ stop do continue
let some (ctx, .node (.ofTermInfo info@{ expr := .const c .., ..}) _) :=
findInfoTree? identKind ⟨pos, endPos'⟩ none snap.infoTree fun _ i =>
(i matches .ofTermInfo { elaborator := .anonymous, expr := .const .., ..})
| continue
unless machineApplicableDeprecated.isTagged snap.cmdState.env c do continue
let some c' := Linter.getDeprecatedNewName snap.cmdState.env c | continue
let eager : CodeAction := {
title := s!"Replace {c} with {c'}"
kind? := "quickfix"
isPreferred? := true
}
return #[{
eager
lazy? := some do
let c' ← info.runMetaM ctx (unresolveNameGlobal c')
let pos := doc.meta.text.leanPosToLspPos msg.pos
let endPos' := doc.meta.text.leanPosToLspPos endPos
pure { eager with
edit? := some <| .ofTextEdit doc.versionedIdentifier {
range := ⟨pos, endPos'⟩
newText := toString c'
}
}
}]
return #[]
================================================
FILE: Batteries/CodeAction/Match.lean
================================================
/-
Copyright (c) 2026 Moritz Roos. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Moritz Roos
-/
module
public meta import Batteries.CodeAction.Misc
public meta import Batteries.Data.List.Basic
@[expose] public meta section
namespace Batteries.CodeAction
open Lean Meta Elab Server RequestM CodeAction
/-- Filter for the info-nodes to find the match-nodes. -/
def isMatchTerm : Info → Bool
| .ofTermInfo i => i.stx.isOfKind ``Lean.Parser.Term.match
| _ => false
/-- Returns the String.range that encompasses `match e (with)`. -/
def getMatchHeaderRange? (matchStx : Syntax) : Option Lean.Syntax.Range := do
match matchStx with
| `(term| match
$[(generalizing := $generalizingVal)]?
$[(motive := $motiveVal)]?
$[$discrs:matchDiscr],*
with $_) => --Here the $alts would go, if they were already typed. Else $_ will match "missing"
-- Isolate the syntax of only the "match" atom to get the starting position:
let mStx ← matchStx.getArgs.find? (fun s => s.isAtom && s.getAtomVal == "match")
let startPos ← mStx.getPos? -- begin of 'match' keyword
-- Depending on the existence of 'with', return the correct range:
if let some withStx := (matchStx.getArgs.find? (fun s => s.isAtom && s.getAtomVal == "with"))
then return ⟨startPos, ←withStx.getTailPos?⟩
else
let lastMatchDiscr ← discrs.back?
return ⟨startPos, ←lastMatchDiscr.raw.getTailPos?⟩
| _ => none
/-- Flattens an Infotree into an array of Info-nodes that fulfill p. -/
partial def findAllInfos (p : Info → Bool) (t : InfoTree) : Array Info :=
loop t #[]
where
/-- Inner loop for `findAllInfos`. -/
loop (t : InfoTree) (acc : Array Info) : Array Info :=
match t with
| .context _ childTree => loop childTree acc
| .node info children =>
let acc' := if p info then acc.push info else acc
children.foldl (fun currentAcc child => loop child currentAcc) acc'
| .hole _ => acc
/-- Computes for a constructor, if it makes sense to use `@constr` in a match, by determining
if it has any non-parameter implicit arguments. -/
def hasImplicitNonparArg (ctor : Name) (env : Environment) : Bool := Id.run do
let some (.ctorInfo ctorInfo) := env.find? ctor | panic! "bad inductive"
let explicitArgs := getExplicitArgs ctorInfo.type #[]
let allArgs := getAllArgs ctorInfo.type #[]
let some (.inductInfo indInfo) := env.find? ctorInfo.induct | panic! "not an inductive"
let numParams := indInfo.numParams
return (allArgs.size - (explicitArgs.size + numParams) > 0)
/-- From a constructor-name e.g. `Option.some` construct the corresponding match pattern, e.g.
`.some val`. We implement special cases for `Nat` and `List`, `Option` and `Bool` to e.g.
produce `n + 1` instead of `Nat.succ n`. -/
def patternFromConstructor (ctor : Name) (env : Environment) (suffix : String)
(explicitArgsOnly : Bool) (ctor_hasImplicitNonparArg : Bool): Option String := do
let some (.ctorInfo ctorInfo) := env.find? ctor | panic! "bad inductive"
let some (.inductInfo indInfo) := env.find? ctorInfo.induct | panic! "not an inductive"
let numParams := indInfo.numParams
let ctor_short := toString (ctor.updatePrefix .anonymous)
let explicitCtorArgs := getExplicitArgs ctorInfo.type #[]
let allCtorArgs := getAllArgs ctorInfo.type #[]
/- Special cases with nicer Notation. None of these constructors has any implicit arguments
that aren't parameters, i.e. that aren't already determined by the match discriminant.
So it doesn't make sense to use them with `@`. That's why we *always* nicely print them
regardless of the setting `explicitArgsOnly`. -/
match ctor with
| (.str (.str .anonymous "Nat") "zero") => "0"
/- At the moment this evaluates to "n + 1": -/
| (.str (.str .anonymous "Nat") "succ") => s!"{explicitCtorArgs[0]!}{suffix} + 1" --
| (.str (.str .anonymous "List") "nil") => "[]"
/- At the moment this evaluates to "head :: tail": -/
| (.str (.str .anonymous "List") "cons") =>
s!"{explicitCtorArgs[0]!}{suffix} :: {explicitCtorArgs[1]!}{suffix}"
| (.str (.str .anonymous "Option") "some") => s!"some {explicitCtorArgs[0]!}{suffix}"
| (.str (.str .anonymous "Option") "none") => "none"
| (.str (.str .anonymous "Bool") "true") => "true"
| (.str (.str .anonymous "Bool") "false") => "false"
| _ =>
/- This is the Default case. It fills the constructor arguments with the variable names `arg`
which were used in the inductive type specification. When using this action with multiple
(same-type) arguments these might clash, so we fix it by appending a suffix like `_2` -
you will probably want to rename these suffixed names yourself.
If the the user wants the match to contain the implicit arguments as well, we
additionally put `_` for every `parameter` (a parameter is an argument to the inductive
type that is fixed over constructors), since these should already be determined by the
match discriminant. One could elaborate the type of this discriminant and fill the parameters
from there, but we don't see any value in this. -/
if explicitArgsOnly || Bool.not ctor_hasImplicitNonparArg then
let mut str := s!".{ctor_short}"
for arg in explicitCtorArgs do
str := str ++ if arg.hasNum || arg.isInternal then " _" else s!" {arg}{suffix}"
return str
else
let mut str := s!".{ctor_short}"
/- This loop skips the first `numParams` many arguments, since these are the parameters
and are already determined by the match discriminant and thus unlikely to be
useful for the match. -/
for i in [numParams:allCtorArgs.size] do
let arg := allCtorArgs[i]!
str := str ++
if arg.hasNum || arg.isInternal then
" _"
else
if arg ∈ explicitCtorArgs then
s!" {arg}{suffix}"
else
s!" ({arg} := {arg}{suffix})"
return str
/--
Invoking tactic code action `Generate a list of alternatives for this match.` in the
following:
```lean
def myfun2 (n : Nat) : Nat :=
match n
```
produces:
```lean
def myfun2 (n : Nat) : Nat :=
match n with
| 0 => _
| n + 1 => _
```
Also has support for multiple discriminants, e.g.
```
def myfun3 (o : Option Bool) (m : Nat) : Nat :=
match o, m with
```
can be expanded into
```
def myfun3 (o : Option Bool) (m : Nat) : Nat :=
match o, m with
| none, 0 => _
| none, n_2 + 1 => _
| some val_1, 0 => _
| some val_1, n_2 + 1 => _
```
If it makes sense to use at least one of the constructors with `@` (i.e. iff it has an
implicit non-parameter argument) then we also show a codeaction that expands every such constructor
with implicit arguments filled in with the syntax `implicitArg := implicitArg`.
E.g. invoking `Generate a list of equations with implicit arguments for this match.` in
the following
```lean
inductive TermWithImplicit (F : Nat → Type u) (α : Type w)
| var (x : α) : TermWithImplicit F α
| func {l : Nat} (f : F l) (ts : Fin l → TermWithImplicit F α) : TermWithImplicit F α
def myfun4 (t : TermWithImplicit F α) : Nat := by
match t with
```
produces
```lean
def myfun4 (t : TermWithImplicit F α) : Nat := by
match t with
| .var x => _
| .func (l := l) f ts => _
```
where the implicit argument `{l : Nat}` is now usable.
Note that the arguments `F` and `α` are not filled since they are `parameters`
(a parameter is an argument to an inductive type that is fixed over constructors), i.e.
they are already determined by the match discriminant `t`. This means they don't provide any
new information for you.
-/
@[command_code_action]
def matchExpand : CommandCodeAction := fun CodeActionParams snap ctx node => do
/- Since `match` is a term (not a command) `@[command_code_action Parser.Term.match]` will
not fire. So we filter `command_code_action` ourselves in Step 1 for now. -/
/- 1. Find ALL ofTermInfo Info nodes that are of kind `Term.match` -/
let allMatchInfos := findAllInfos isMatchTerm node
/- 2. Filter these candidates within the `RequestM` monad based on the cursor being in the
header lines of these matches. -/
let doc ← readDoc
let relevantMatchInfos ← allMatchInfos.filterM fun matchInfo => do
let some headerRangeRaw := getMatchHeaderRange? matchInfo.stx | return false
let headerRangeLsp := doc.meta.text.utf8RangeToLspRange headerRangeRaw
let cursorRangeLsp := CodeActionParams.range
-- check if the cursor range is contained in the header range
return (cursorRangeLsp.start ≥ headerRangeLsp.start && cursorRangeLsp.end ≤ headerRangeLsp.end)
/- 3. Pick the first (and mostly only) candidate. There might sometimes be more,
since some things are just contained multiple times in 'node'. -/
let some matchInfo := relevantMatchInfos[0]? | return #[]
let some headerRangeRaw := getMatchHeaderRange? matchInfo.stx | return #[]
/- Isolate the array of match-discriminants -/
let discrs ← match matchInfo.stx with
| `(term| match
$[(generalizing := $generalizingVal)]?
$[(motive := $motiveVal)]?
$[$discrs:matchDiscr],*
with $_) => pure discrs
| _ => return #[]
/- Reduce discrs to the array of match-discriminants-terms (i.e. "[n1, n2]" in "match n2,n2"). -/
let some discrTerms := discrs.mapM (fun discr =>
match discr with
| `(matchDiscr| $t: term) => some t
| `(matchDiscr| $_:ident : $t: term) => some t
| _ => none
) | return #[]
-- Get a Bool, that tells us if "with" is already typed in:
let withPresent :=
(matchInfo.stx.getArgs.find? (fun s => s.isAtom && s.getAtomVal == "with")).isSome
/- Construct a list containing for each discriminant its list of constructor names paired with
a Bool that determines if it makes sense to use the constructor with `@`.
The list contains the first discriminant constructors last,
since we are prepending in the loop. -/
let mut constructors_rev : List (List (Name × Bool)) := []
for discrTerm in discrTerms do
let some (info, updatedCtx) := findTermInfoWithCtx? node discrTerm ctx | return #[]
let ty ← info.runMetaM updatedCtx (Lean.Meta.inferType info.expr)
let .const name _ := (← info.runMetaM updatedCtx (whnf ty)).getAppFn | return #[]
-- Find the inductive constructors of e:
let some (.inductInfo indInfo) := snap.env.find? name | return #[]
let ctors := indInfo.ctors
constructors_rev :=
(ctors.map (fun ctor => (ctor, hasImplicitNonparArg ctor snap.env)))
:: constructors_rev
let mkAction (title : String) (explicitArgsOnly : Bool) : LazyCodeAction :=
let eager : Lsp.CodeAction := {
title := title
kind? := "quickfix"
}
{ --rest is lightly adapted from eqnStub:
eager
lazy? := some do
let holePos := headerRangeRaw.stop --where we start inserting
let (indent, _) := findIndentAndIsStart doc.meta.text.source headerRangeRaw.start
let mut str := if withPresent then "" else " with"
let indent := "\n".pushn ' ' (indent) --use the same indent as the 'match' line.
let constructor_combinations := constructors_rev.sections.map List.reverse
for l in constructor_combinations do
str := str ++ indent ++ "| "
for ctor_idx in [:l.length] do
let (ctor, existsExplicitNonparArg) := l[ctor_idx]!
let suffix := if constructors_rev.length ≥ 2 then s!"_{ctor_idx + 1}" else ""
let some pat :=
patternFromConstructor ctor snap.env suffix explicitArgsOnly existsExplicitNonparArg |
panic! "bad inductive"
str := str ++ pat
if ctor_idx < l.length - 1 then
str := str ++ ", "
str := str ++ s!" => _"
pure { eager with
edit? := some <|.ofTextEdit doc.versionedIdentifier {
range := doc.meta.text.utf8RangeToLspRange ⟨holePos, holePos⟩-- adapted to insert-only
newText := str
}
}
}
/- Show the code action with implicit arguments if at least one constructor has an implicit
non-parameter argument. -/
let showExplicitCodeAction := constructors_rev.any (fun l =>
l.any (fun (_, ctor_hasImplicitNonparArg) => ctor_hasImplicitNonparArg))
if (showExplicitCodeAction) then
return #[mkAction "Generate a list of equations for this match." True,
mkAction "Generate a list of equations with implicit arguments for this match." False]
else
return #[mkAction "Generate a list of equations for this match." True]
================================================
FILE: Batteries/CodeAction/Misc.lean
================================================
/-
Copyright (c) 2023 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
module
public meta import Lean.Elab.Tactic.Induction
public meta import Batteries.Lean.Position
public meta import Batteries.CodeAction.Attr
public meta import Lean.Server.CodeActions.Provider
public meta section
/-!
# Miscellaneous code actions
This declares some basic tactic code actions, using the `@[tactic_code_action]` API.
-/
namespace Batteries.CodeAction
open Lean Meta Elab Server RequestM CodeAction
/-- Return the syntax stack leading to `target` from `root`, if one exists. -/
def findStack? (root target : Syntax) : Option Syntax.Stack := do
let range ← target.getRange?
root.findStack? (·.getRange?.any (·.includes range))
(fun s => s.getKind == target.getKind && s.getRange? == range)
/-- Constructs a hole with a kind matching the provided hole elaborator. -/
def holeKindToHoleString : (elaborator : Name) → (synthName : String) → String
| ``Elab.Term.elabSyntheticHole, name => "?" ++ name
| ``Elab.Term.elabSorry, _ => "sorry"
| _, _ => "_"
/--
Hole code action used to fill in a structure's field when specifying an instance.
In the following:
```lean
instance : Monad Id := _
```
invoking the hole code action "Generate a (minimal) skeleton for the structure under construction."
produces:
```lean
instance : Monad Id where
pure := _
bind := _
```
and invoking "Generate a (maximal) skeleton for the structure under construction." produces:
```lean
instance : Monad Id where
map := _
mapConst := _
pure := _
seq := _
seqLeft := _
seqRight := _
bind := _
```
-/
@[hole_code_action] partial def instanceStub : HoleCodeAction := fun _ snap ctx info => do
let some ty := info.expectedType? | return #[]
let .const name _ := (← info.runMetaM ctx (whnf ty)).getAppFn | return #[]
unless isStructure snap.env name do return #[]
let doc ← readDoc
let fields := collectFields snap.env name #[] []
let only := !fields.any fun (_, auto) => auto
let mkAutofix minimal :=
let eager := {
title := s!"\
Generate a {if only then "" else if minimal then "(minimal) " else "(maximal) "}\
skeleton for the structure under construction."
kind? := "quickfix"
isPreferred? := minimal
}
let lazy? := some do
let useWhere := do
let _ :: (stx, _) :: _ ← findStack? snap.stx info.stx | none
guard (stx.getKind == ``Parser.Command.declValSimple)
stx[0].getPos?
let holePos := useWhere.getD info.stx.getPos?.get!
let (indent, isStart) := findIndentAndIsStart doc.meta.text.source holePos
let indent := "\n".pushn ' ' indent
let mut str := if useWhere.isSome then "where" else "{"
let mut first := useWhere.isNone && isStart
for (field, auto) in fields do
if minimal && auto then continue
if first then
str := str ++ " "
first := false
else
str := str ++ indent ++ " "
let field := toString field
str := str ++ s!"{field} := {holeKindToHoleString info.elaborator field}"
if useWhere.isNone then
if isStart then
str := str ++ " }"
else
str := str ++ indent ++ "}"
pure { eager with
edit? := some <| .ofTextEdit doc.versionedIdentifier {
range := doc.meta.text.utf8RangeToLspRange ⟨holePos, info.stx.getTailPos?.get!⟩
newText := str
}
}
{ eager, lazy? }
pure <| if only then #[mkAutofix true] else #[mkAutofix true, mkAutofix false]
where
/-- Returns true if this field is an autoParam or optParam, or if it is given an optional value
in a child struct. -/
isAutofillable (env : Environment) (fieldInfo : StructureFieldInfo) (stack : List Name) : Bool :=
fieldInfo.autoParam?.isSome || env.contains (mkDefaultFnOfProjFn fieldInfo.projFn)
|| stack.any fun struct => env.contains (mkDefaultFnOfProjFn (struct ++ fieldInfo.fieldName))
/-- Returns the fields of a structure, unfolding parent structures. -/
collectFields (env : Environment) (structName : Name)
(fields : Array (Name × Bool)) (stack : List Name) : Array (Name × Bool) :=
(getStructureFields env structName).foldl (init := fields) fun fields field =>
if let some fieldInfo := getFieldInfo? env structName field then
if let some substructName := fieldInfo.subobject? then
collectFields env substructName fields (structName :: stack)
else
fields.push (field, isAutofillable env fieldInfo stack)
else fields
/-- Returns the explicit arguments given a type. The second argument of this
function is an accumulator. -/
def getExplicitArgs : Expr → Array Name → Array Name
| .forallE n _ body bi, args =>
getExplicitArgs body <| if bi.isExplicit then args.push n else args
| _, args => args
/-- Returns all of the arguments given a type. The second argument of this
function is an accumulator. -/
def getAllArgs : Expr → Array Name → Array Name
| .forallE n _ body _, args =>
getAllArgs body <| args.push n
| _, args => args
/--
Invoking hole code action "Generate a list of equations for a recursive definition" in the
following:
```lean
def foo : Expr → Unit := _
```
produces:
```lean
def foo : Expr → Unit := fun
| .bvar deBruijnIndex => _
| .fvar fvarId => _
| .mvar mvarId => _
| .sort u => _
| .const declName us => _
| .app fn arg => _
| .lam binderName binderType body binderInfo => _
| .forallE binderName binderType body binderInfo => _
| .letE declName type value body nonDep => _
| .lit _ => _
| .mdata data expr => _
| .proj typeName idx struct => _
```
-/
@[hole_code_action] def eqnStub : HoleCodeAction := fun _ snap ctx info => do
let some ty := info.expectedType? | return #[]
let .forallE _ dom .. ← info.runMetaM ctx (whnf ty) | return #[]
let .const name _ := (← info.runMetaM ctx (whnf dom)).getAppFn | return #[]
let some (.inductInfo val) := snap.env.find? name | return #[]
let eager := {
title := "Generate a list of equations for a recursive definition."
kind? := "quickfix"
}
let doc ← readDoc
pure #[{
eager
lazy? := some do
let holePos := info.stx.getPos?.get!
let (indent, isStart) := findIndentAndIsStart doc.meta.text.source holePos
let mut str := "fun"
let indent := "\n".pushn ' ' (if isStart then indent else indent + 2)
for ctor in val.ctors do
let some (.ctorInfo ci) := snap.env.find? ctor | panic! "bad inductive"
let ctor := toString (ctor.updatePrefix .anonymous)
str := str ++ indent ++ s!"| .{ctor}"
for arg in getExplicitArgs ci.type #[] do
str := str ++ if arg.hasNum || arg.isInternal then " _" else s!" {arg}"
str := str ++ s!" => {holeKindToHoleString info.elaborator ctor}"
pure { eager with
edit? := some <|.ofTextEdit doc.versionedIdentifier {
range := doc.meta.text.utf8RangeToLspRange ⟨holePos, info.stx.getTailPos?.get!⟩
newText := str
}
}
}]
/-- Invoking hole code action "Start a tactic proof" will fill in a hole with `by done`. -/
@[hole_code_action] def startTacticStub : HoleCodeAction := fun _ _ _ info => do
let holePos := info.stx.getPos?.get!
let doc ← readDoc
let indent := (findIndentAndIsStart doc.meta.text.source holePos).1
pure #[{
eager.title := "Start a tactic proof."
eager.kind? := "quickfix"
eager.edit? := some <|.ofTextEdit doc.versionedIdentifier {
range := doc.meta.text.utf8RangeToLspRange ⟨holePos, info.stx.getTailPos?.get!⟩
newText := "by\n".pushn ' ' (indent + 2) ++ "done"
}
}]
/-- The "Remove tactics after 'no goals'" code action deletes any tactics following a completed
proof.
```
example : True := by
trivial
trivial -- <- remove this, proof is already done
rfl
```
is transformed to
```
example : True := by
trivial
```
-/
@[tactic_code_action*]
def removeAfterDoneAction : TacticCodeAction := fun _ _ _ stk node => do
let .node (.ofTacticInfo info) _ := node | return #[]
unless info.goalsBefore.isEmpty do return #[]
let _ :: (seq, i) :: _ := stk | return #[]
let some stop := seq.getTailPos? | return #[]
let some prev := (seq.setArgs seq.getArgs[:i]).getTailPos? | return #[]
let doc ← readDoc
let eager := {
title := "Remove tactics after 'no goals'"
kind? := "quickfix"
isPreferred? := true
edit? := some <|.ofTextEdit doc.versionedIdentifier {
range := doc.meta.text.utf8RangeToLspRange ⟨prev, stop⟩
newText := ""
}
}
pure #[{ eager }]
/--
Similar to `getElimExprInfo`, but returns the names of binders instead of just the numbers;
intended for code actions which need to name the binders.
-/
def getElimExprNames (elimType : Expr) : MetaM (Array (Name × Array Name)) := do
-- let inductVal ← getConstInfoInduct inductName
-- let decl ← getConstInfo declName
forallTelescopeReducing elimType fun xs type => do
let motive := type.getAppFn
let targets := type.getAppArgs
let motiveType ← inferType motive
let mut altsInfo := #[]
for _h : i in [:xs.size] do
let x := xs[i]
if x != motive && !targets.contains x then
let xDecl ← x.fvarId!.getDecl
if xDecl.binderInfo.isExplicit then
let args ← forallTelescopeReducing xDecl.type fun args _ => do
let lctx ← getLCtx
pure <| args.filterMap fun y =>
let yDecl := (lctx.find? y.fvarId!).get!
if yDecl.binderInfo.isExplicit then some yDecl.userName else none
altsInfo := altsInfo.push (xDecl.userName, args)
pure altsInfo
/-- Finds the `TermInfo` for an elaborated term `stx`. -/
def findTermInfo? (node : InfoTree) (stx : Term) : Option TermInfo :=
match node.findInfo? fun
| .ofTermInfo i => i.stx.getKind == stx.raw.getKind && i.stx.getRange? == stx.raw.getRange?
| _ => false
with
| some (.ofTermInfo info) => pure info
| _ => none
/-- `findTermInfoWithCtx?` finds the `TermInfo` for an elaborated term `stx`
and also updates the inputted `ContextInfo` using all the
`PartialContextInfo` on the path to the returned `TermInfo`. -/
partial def findTermInfoWithCtx? (t : InfoTree) (stx : Term) (ctx : ContextInfo)
: Option (TermInfo × ContextInfo) :=
match t with
| .context partialCtx t' =>
-- Merge partial context with outer, fall back to outer if merge fails
let ctx' := partialCtx.mergeIntoOuter? ctx |>.getD ctx
findTermInfoWithCtx? t' stx ctx'
| .node info children =>
let optResult : Option (TermInfo × ContextInfo) :=
match info with
| .ofTermInfo i =>
if i.stx.getKind == stx.raw.getKind && i.stx.getRange? == stx.raw.getRange? then
some (i, ctx)
else none
| _ => none
if let some res := optResult then
return res
else
children.findSome? (findTermInfoWithCtx? · stx ctx)
| .hole _ => none
/--
Invoking tactic code action "Generate an explicit pattern match for 'induction'" in the
following:
```lean
example (x : Nat) : x = x := by
induction x
```
produces:
```lean
example (x : Nat) : x = x := by
induction x with
| zero => sorry
| succ n ih => sorry
```
It also works for `cases`.
-/
@[tactic_code_action Parser.Tactic.cases Parser.Tactic.induction]
def casesExpand : TacticCodeAction := fun _ snap ctx _ node => do
let .node (.ofTacticInfo info) _ := node | return #[]
let (targets, induction, using_, alts) ← match info.stx with
| `(tactic| cases $[$[$_ :]? $targets],* $[using $u]? $(alts)?) =>
pure (targets, false, u, alts)
| `(tactic| induction $[$[$_ :]? $targets],* $[using $u]? $[generalizing $_*]? $(alts)?) =>
pure (targets, true, u, alts)
| _ => return #[]
let some discrInfos := targets.mapM (findTermInfo? node) | return #[]
let some discr₀ := discrInfos[0]? | return #[]
let mut some ctors ← discr₀.runMetaM ctx do
let targets := discrInfos.map (·.expr)
match using_ with
| none =>
if tactic.customEliminators.get (← getOptions) then
if let some elimName ← getCustomEliminator? targets induction then
return some (← getElimExprNames (← getConstInfo elimName).type)
matchConstInduct (← whnf (← inferType discr₀.expr)).getAppFn
(fun _ => failure) fun val _ => do
let elimName := if induction then mkRecName val.name else mkCasesOnName val.name
return some (← getElimExprNames (← getConstInfo elimName).type)
| some u =>
let some info := findTermInfo? node u | return none
return some (← getElimExprNames (← inferType info.expr))
| return #[]
let mut fallback := none
if let some alts := alts then
if let `(Parser.Tactic.inductionAlts| with $(_)? $alts*) := alts then
for alt in alts do
match alt with
| `(Parser.Tactic.inductionAlt| | _ $_* => $fb) => fallback := fb.raw.getRange?
| `(Parser.Tactic.inductionAlt| | $id:ident $_* => $_) =>
ctors := ctors.filter (fun x => x.1 != id.getId)
| _ => pure ()
if ctors.isEmpty then return #[]
let tacName := info.stx.getKind.updatePrefix .anonymous
let eager := {
title := s!"Generate an explicit pattern match for '{tacName}'."
kind? := "quickfix"
}
let doc ← readDoc
pure #[{
eager
lazy? := some do
let tacPos := info.stx.getPos?.get!
let endPos := doc.meta.text.utf8PosToLspPos info.stx.getTailPos?.get!
let indent := "\n".pushn ' ' (findIndentAndIsStart doc.meta.text.source tacPos).1
let (startPos, str') := if alts.isSome then
let stx' := if fallback.isSome then
info.stx.modifyArg (if induction then 4 else 3)
(·.modifyArg 0 (·.modifyArg 2 (·.modifyArgs (·.filter fun s =>
!(s matches `(Parser.Tactic.inductionAlt| | _ $_* => $_))))))
else info.stx
(doc.meta.text.utf8PosToLspPos stx'.getTailPos?.get!, "")
else (endPos, " with")
let fallback := if let some ⟨startPos, endPos⟩ := fallback then
String.Pos.Raw.extract doc.meta.text.source startPos endPos
else
"sorry"
let newText := Id.run do
let mut str := str'
for (name, args) in ctors do
let mut ctor := toString name
if let some _ := (Parser.getTokenTable snap.env).find? ctor then
ctor := s!"{idBeginEscape}{ctor}{idEndEscape}"
str := str ++ indent ++ s!"| {ctor}"
-- replace n_ih with just ih if there is only one
let args := if induction &&
args.foldl (fun c n =>
if n.eraseMacroScopes.getString!.endsWith "_ih" then c+1 else c) 0 == 1
then
args.map (fun n => if !n.hasMacroScopes && n.getString!.endsWith "_ih" then `ih else n)
else args
for arg in args do
str := str ++ if arg.hasNum || arg.isInternal then " _" else s!" {arg}"
str := str ++ s!" => " ++ fallback
str
pure { eager with
edit? := some <|.ofTextEdit doc.versionedIdentifier {
range := ⟨startPos, endPos⟩
newText
}
}
}]
/-- The "Add subgoals" code action puts `· done` subgoals for any goals remaining at the end of a
proof.
```
example : True ∧ True := by
constructor
-- <- here
```
is transformed to
```
example : True ∧ True := by
constructor
· done
· done
```
-/
def addSubgoalsActionCore (params : Lsp.CodeActionParams)
(i : Nat) (stk : Syntax.Stack) (goals : List MVarId) : RequestM (Array LazyCodeAction) := do
-- If there are zero goals remaining, no need to do anything
-- If there is one goal remaining, the user can just keep typing and subgoals are not helpful
unless goals.length > 1 do return #[]
let seq := stk.head!.1
let nargs := (seq.getNumArgs + 1) / 2
unless i == nargs do -- only trigger at the end of a block
-- or if there is only a `done` or `sorry` terminator
unless i + 1 == nargs && [
``Parser.Tactic.done, ``Parser.Tactic.tacticSorry, ``Parser.Tactic.tacticAdmit
].contains seq[2*i].getKind do
return #[]
let some startPos := seq[0].getPos? true | return #[]
let doc ← readDoc
let eager := { title := "Add subgoals", kind? := "quickfix" }
pure #[{
eager
lazy? := some do
let indent := "\n".pushn ' ' (doc.meta.text.toPosition startPos).column
let mut (range, newText) := (default, "")
if let some tac := seq.getArgs[2*i]? then
let some range2 := tac.getRange? true | return eager
range := range2
else
let trimmed := seq.modifyArgs (·[:2*i])
let some tail := trimmed.getTailPos? true | return eager
(range, newText) := (⟨tail, tail⟩, indent)
let cursor := doc.meta.text.lspPosToUtf8Pos params.range.end
if range.stop ≤ cursor && cursor.1 ≤ range.stop.1 + trimmed.getTrailingSize then
range := { range with stop := cursor }
newText := newText ++ "· done"
for _ in goals.tail! do
newText := newText ++ indent ++ "· done"
pure { eager with
edit? := some <|.ofTextEdit doc.versionedIdentifier {
range := doc.meta.text.utf8RangeToLspRange range
newText
}
}
}]
@[inherit_doc addSubgoalsActionCore, tactic_code_action]
def addSubgoalsSeqAction : TacticSeqCodeAction := fun params _ _ => addSubgoalsActionCore params
-- This makes sure that the addSubgoals action also triggers
-- when the cursor is on the final `done` of a tactic block
@[inherit_doc addSubgoalsActionCore,
tactic_code_action Parser.Tactic.done Parser.Tactic.tacticSorry Parser.Tactic.tacticAdmit]
def addSubgoalsAction : TacticCodeAction := fun params _ _ stk node => do
let (_ :: (seq, i) :: stk@(_ :: t :: _), .node (.ofTacticInfo info) _) := (stk, node) | return #[]
unless t.1.getKind == ``Parser.Tactic.tacticSeq do return #[]
addSubgoalsActionCore params (i/2) ((seq, 0) :: stk) info.goalsBefore
================================================
FILE: Batteries/CodeAction.lean
================================================
module
public import Batteries.CodeAction.Attr
public import Batteries.CodeAction.Basic
public import Batteries.CodeAction.Misc
public import Batteries.CodeAction.Match
================================================
FILE: Batteries/Control/AlternativeMonad.lean
================================================
/-
Copyright (c) 2025 Devon Tuma. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Devon Tuma
-/
module
public import Batteries.Control.Lemmas
public import Batteries.Control.OptionT
import all Init.Control.Option
import all Init.Control.State
import all Init.Control.Reader
import all Init.Control.StateRef
@[expose] public section
/-!
# Laws for Monads with Failure
Definitions for monads that also have an `Alternative` instance while sharing the underlying
`Applicative` instance, and a class `LawfulAlternative` for types where the `failure` and `orElse`
operations behave in a natural way. More specifically they satisfy:
* `f <$> failure = failure`
* `failure <*> x = failure`
* `x <|> failure = x`
* `failure <|> y = y`
* `x <|> y <|> z = (x <|> y) <|> z`
* `f <$> (x <|> y) = (f <$> x <|> f <$> y)`
`Option`/`OptionT` are the most basic examples, but transformers like `StateT` also preserve
the lawfulness of this on the underlying monad.
The law `x *> failure = failure` is true for monads like `Option` and `List` that don't
have any "side effects" to execution, but not for something like `OptionT` on some monads,
so we don't include this condition.
We also define a class `LawfulAlternativeLift` similar to `LawfulMonadLift` that states that
a lifting between monads preserves `failure` and `orElse`.
## Tags
monad, alternative, failure
-/
/-- `AlternativeMonad m` means that `m` has both a `Monad` and `Alternative` instance,
which both share the same underlying `Applicative` instance.
The main example is `Option`, but many monad transformers also preserve or add this structure. -/
class AlternativeMonad (m : Type _ → Type _) extends Alternative m, Monad m
section LawfulAlternative
/-- `LawfulAlternative m` means that the `failure` operation on `m` behaves naturally
with respect to `map`, `seq`, and `orElse` operators. -/
class LawfulAlternative (m : Type _ → Type _) [Alternative m] : Prop
extends LawfulApplicative m where
/-- Mapping the result of a failure is still a failure -/
map_failure (f : α → β) : f <$> (failure : m α) = failure
/-- Sequencing a `failure` call results in failure -/
failure_seq (x : m α) : (failure : m (α → β)) <*> x = failure
/-- `failure` is a right identity for `orElse`. -/
orElse_failure (x : m α) : (x <|> failure) = x
/-- `failure` is a left identity for `orElse`. -/
failure_orElse (y : m α) : (failure <|> y) = y
/-- `orElse` is associative. -/
orElse_assoc (x y z : m α) : (x <|> y <|> z) = ((x <|> y) <|> z)
/-- `map` commutes with `orElse`. The stronger statement with `bind` generally isn't true -/
map_orElse (x y : m α) (f : α → β) : f <$> (x <|> y) = (f <$> x <|> f <$> y)
export LawfulAlternative (map_failure failure_seq orElse_failure failure_orElse orElse_assoc
map_orElse)
attribute [simp] map_failure failure_seq orElse_failure failure_orElse map_orElse
section Alternative
@[simp] theorem mapConst_failure [Alternative m] [LawfulAlternative m] (y : β) :
Functor.mapConst y (failure : m α) = failure := by
rw [LawfulFunctor.map_const, Function.comp_apply, map_failure]
@[simp] theorem mapConst_orElse [Alternative m] [LawfulAlternative m] (x x' : m α) (y : β) :
Functor.mapConst y (x <|> x') = (Functor.mapConst y x <|> Functor.mapConst y x') := by
simp only [map_const, Function.comp_apply, map_orElse]
@[simp] theorem failure_seqLeft [Alternative m] [LawfulAlternative m] (x : m α) :
(failure : m β) <* x = failure := by
simp only [seqLeft_eq, map_failure, failure_seq]
@[simp] theorem failure_seqRight [Alternative m] [LawfulAlternative m] (x : m α) :
(failure : m β) *> x = failure := by
simp only [seqRight_eq, map_failure, failure_seq]
end Alternative
section AlternativeMonad
@[simp] theorem failure_bind [AlternativeMonad m] [LawfulAlternative m] [LawfulMonad m]
(x : α → m β) : failure >>= x = failure := by
calc failure >>= x = (PEmpty.elim <$> failure) >>= x := by rw [map_failure]
_ = failure >>= (x ∘ PEmpty.elim) := by rw [bind_map_left, Function.comp_def]
_ = failure >>= (pure ∘ PEmpty.elim) := bind_congr fun a => a.elim
_ = (PEmpty.elim <$> failure) >>= pure := by rw [bind_map_left, Function.comp_def]
_ = failure := by rw [map_failure, bind_pure]
@[simp] theorem seq_failure [AlternativeMonad m] [LawfulAlternative m] [LawfulMonad m]
(x : m (α → β)) : x <*> failure = x *> failure := by
simp only [seq_eq_bind_map, map_failure, seqRight_eq, bind_map_left]
end AlternativeMonad
end LawfulAlternative
/-- Type-class for monad lifts that preserve the `Alternative` operations. -/
class LawfulAlternativeLift (m : semiOutParam (Type u → Type v)) (n : Type u → Type w)
[Alternative m] [Alternative n] [MonadLift m n] : Prop where
/-- Lifting preserves `failure`. -/
monadLift_failure : monadLift (failure : m α) = (failure : n α)
/-- Lifting preserves `orElse`. -/
monadLift_orElse (x y : m α) : monadLift (x <|> y) = (monadLift x <|> monadLift y : n α)
export LawfulAlternativeLift (monadLift_failure monadLift_orElse)
attribute [simp] monadLift_failure monadLift_orElse
namespace Option
instance : AlternativeMonad Option.{u} where
instance : LawfulAlternative Option.{u} where
map_failure _ := rfl
failure_seq _ := rfl
orElse_failure x := by cases x <;> rfl
failure_orElse := by simp [failure]
orElse_assoc | some _, _, _ => rfl | none, _, _ => rfl
map_orElse | some _ => by simp | none => by simp
end Option
namespace OptionT
instance (m) [Monad m] : AlternativeMonad (OptionT m) where
instance (m) [Monad m] [LawfulMonad m] : LawfulAlternative (OptionT m) where
map_failure _ := pure_bind _ _
failure_seq _ := pure_bind _ _
orElse_failure x := (bind_congr (fun | some _ => rfl | none => rfl)).trans (bind_pure x)
failure_orElse _ := pure_bind _ _
orElse_assoc _ _ _ := by
simp only [OptionT.ext_iff, run_orElse, Option.elimM, bind_assoc]
refine bind_congr fun | some _ => by simp | none => rfl
map_orElse x y f := by
simp only [OptionT.ext_iff, run_map, run_orElse, map_bind, bind_map_left, Option.elimM]
refine bind_congr fun | some _ => by simp | none => rfl
end OptionT
namespace StateT
instance (m) [AlternativeMonad m] : AlternativeMonad (StateT σ m) where
instance (m) [AlternativeMonad m] [LawfulAlternative m] [LawfulMonad m] :
LawfulAlternative (StateT σ m) where
map_failure _ := StateT.ext fun _ => by simp only [run_map, run_failure, map_failure]
failure_seq _ := StateT.ext fun _ => by simp only [run_seq, run_failure, failure_bind]
orElse_failure _ := StateT.ext fun _ => orElse_failure _
failure_orElse _ := StateT.ext fun _ => failure_orElse _
orElse_assoc _ _ _ := StateT.ext fun _ => orElse_assoc _ _ _
map_orElse _ _ _ := StateT.ext fun _ => by simp only [run_map, run_orElse, map_orElse]
instance (m) [AlternativeMonad m] [LawfulAlternative m] [LawfulMonad m] :
LawfulAlternativeLift m (StateT σ m) where
monadLift_failure {α} := StateT.ext fun s => by simp
monadLift_orElse {α} x y := StateT.ext fun s => by simp
end StateT
namespace ReaderT
instance [AlternativeMonad m] : AlternativeMonad (ReaderT ρ m) where
instance [AlternativeMonad m] [LawfulAlternative m] : LawfulAlternative (ReaderT ρ m) where
map_failure _ := ReaderT.ext fun _ => map_failure _
failure_seq _ := ReaderT.ext fun _ => failure_seq _
orElse_failure _ := ReaderT.ext fun _ => orElse_failure _
failure_orElse _ := ReaderT.ext fun _ => failure_orElse _
orElse_assoc _ _ _ := ReaderT.ext fun _ => orElse_assoc _ _ _
map_orElse _ _ _ := ReaderT.ext fun _ => by simp only [run_map, run_orElse, map_orElse]
instance [AlternativeMonad m] : LawfulAlternativeLift m (ReaderT ρ m) where
monadLift_failure {α} := ReaderT.ext fun s => by simp
monadLift_orElse {α} x y := ReaderT.ext fun s => by simp
end ReaderT
namespace StateRefT'
instance [AlternativeMonad m] : AlternativeMonad (StateRefT' ω σ m) where
instance [AlternativeMonad m] [LawfulAlternative m] :
LawfulAlternative (StateRefT' ω σ m) :=
inferInstanceAs (LawfulAlternative (ReaderT (ST.Ref ω σ) m))
instance [AlternativeMonad m] : LawfulAlternativeLift m (StateRefT' ω σ m) :=
inferInstanceAs (LawfulAlternativeLift m (ReaderT (ST.Ref ω σ) m))
end StateRefT'
================================================
FILE: Batteries/Control/ForInStep/Basic.lean
================================================
/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
module
@[expose] public section
/-! # Additional definitions on `ForInStep` -/
/--
This is similar to a monadic `bind` operator, except that the two type parameters have to be
the same, which prevents putting a monad instance on `ForInStepT m α := m (ForInStep α)`.
-/
@[inline] protected def ForInStep.bind [Monad m]
(a : ForInStep α) (f : α → m (ForInStep α)) : m (ForInStep α) :=
match a with
| .done a => return .done a
| .yield a => f a
@[inherit_doc ForInStep.bind] protected abbrev ForInStep.bindM [Monad m]
(a : m (ForInStep α)) (f : α → m (ForInStep α)) : m (ForInStep α) := a >>= (·.bind f)
/--
Get the value out of a `ForInStep`.
This is usually done at the end of a `forIn` loop to scope the early exit to the loop body.
-/
@[inline] def ForInStep.run : ForInStep α → α
| .done a
| .yield a => a
/-- Applies function `f` to each element of a list to accumulate a `ForInStep` value. -/
def ForInStep.bindList [Monad m]
(f : α → β → m (ForInStep β)) : List α → ForInStep β → m (ForInStep β)
| [], s => pure s
| a::l, s => s.bind fun b => f a b >>= (·.bindList f l)
================================================
FILE: Batteries/Control/ForInStep/Lemmas.lean
================================================
/-
Copyright (c) 2022 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
module
public import Batteries.Control.ForInStep.Basic
@[expose] public section
/-! # Additional theorems on `ForInStep` -/
@[simp] theorem ForInStep.bind_done [Monad m] (a : α) (f : α → m (ForInStep α)) :
(ForInStep.done a).bind (m := m) f = pure (.done a) := rfl
@[simp] theorem ForInStep.bind_yield [Monad m] (a : α) (f : α → m (ForInStep α)) :
(ForInStep.yield a).bind (m := m) f = f a := rfl
attribute [simp] ForInStep.bindM
@[simp] theorem ForInStep.run_done : (ForInStep.done a).run = a := rfl
@[simp] theorem ForInStep.run_yield : (ForInStep.yield a).run = a := rfl
@[simp] theorem ForInStep.bindList_nil [Monad m] (f : α → β → m (ForInStep β))
(s : ForInStep β) : s.bindList f [] = pure s := rfl
@[simp] theorem ForInStep.bindList_cons [Monad m]
(f : α → β → m (ForInStep β)) (s : ForInStep β) (a l) :
s.bindList f (a::l) = s.bind fun b => f a b >>= (·.bindList f l) := rfl
@[simp] theorem ForInStep.done_bindList [Monad m]
(f : α → β → m (ForInStep β)) (a l) :
(ForInStep.done a).bindList f l = pure (.done a) := by cases l <;> simp
@[simp] theorem ForInStep.bind_yield_bindList [Monad m]
(f : α → β → m (ForInStep β)) (s : ForInStep β) (l) :
(s.bind fun a => (yield a).bindList f l) = s.bindList f l := by cases s <;> simp
@[simp] theorem ForInStep.bind_bindList_assoc [Monad m] [LawfulMonad m]
(f : β → m (ForInStep β)) (g : α → β → m (ForInStep β)) (s : ForInStep β) (l) :
s.bind f >>= (·.bindList g l) = s.bind fun b => f b >>= (·.bindList g l) := by
cases s <;> simp
theorem ForInStep.bindList_cons' [Monad m] [LawfulMonad m]
(f : α → β → m (ForInStep β)) (s : ForInStep β) (a l) :
s.bindList f (a::l) = s.bind (f a) >>= (·.bindList f l) := by simp
@[simp] theorem ForInStep.bindList_append [Monad m] [LawfulMonad m]
(f : α → β → m (ForInStep β)) (s : ForInStep β) (l₁ l₂) :
s.bindList f (l₁ ++ l₂) = s.bindList f l₁ >>= (·.bindList f l₂) := by
induction l₁ generalizing s <;> simp [*]
================================================
FILE: Batteries/Control/ForInStep.lean
================================================
module
public import Batteries.Control.ForInStep.Basic
public import Batteries.Control.ForInStep.Lemmas
================================================
FILE: Batteries/Control/LawfulMonadState.lean
================================================
/-
Copyright (c) 2025 Devon Tuma. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Devon Tuma, Quang Dao
-/
module
import all Init.Control.StateRef
@[expose] public section
/-!
# Laws for Monads with State
This file defines a typeclass for `MonadStateOf` with compatible `get` and `set` operations.
Note that we use `MonadStateOf` over `MonadState` as the first induces the second,
but we phrase things using `MonadStateOf.set` and `MonadState.get` as those are the
versions that are available at the top level namespace.
-/
/-- The namespaced `MonadStateOf.get` is equal to the `MonadState` provided `get`. -/
@[simp] theorem monadStateOf_get_eq_get [MonadStateOf σ m] :
(MonadStateOf.get : m σ) = get := rfl
/-- The namespaced `MonadStateOf.modifyGet` is equal to the `MonadState` provided `modifyGet`. -/
@[simp] theorem monadStateOf_modifyGet_eq_modifyGet [MonadStateOf σ m]
(f : σ → α × σ) : (MonadStateOf.modifyGet f : m α) = modifyGet f := rfl
@[simp] theorem liftM_get {m n} [MonadStateOf σ m] [MonadLift m n] :
(liftM (get (m := m)) : n _) = get := rfl
@[simp] theorem liftM_set {m n} [MonadStateOf σ m] [MonadLift m n]
(s : σ) : (liftM (set (m := m) s) : n _) = set s := rfl
@[simp] theorem liftM_modify {m n} [MonadStateOf σ m] [MonadLift m n]
(f : σ → σ) : (liftM (modify (m := m) f) : n _) = modify f := rfl
@[simp] theorem liftM_modifyGet {m n} [MonadStateOf σ m] [MonadLift m n]
(f : σ → α × σ) : (liftM (modifyGet (m := m) f) : n _) = modifyGet f := rfl
@[simp] theorem liftM_getModify {m n} [MonadStateOf σ m] [MonadLift m n]
(f : σ → σ) : (liftM (getModify (m := m) f) : n _) = getModify f := rfl
/-- Class for well behaved state monads, extending the base `MonadState` type.
Requires that `modifyGet` is equal to the same definition with only `get` and `set`,
that `get` is idempotent if the result isn't used, and that `get` after `set` returns
exactly the value that was previously `set`. -/
class LawfulMonadStateOf (σ : semiOutParam (Type _)) (m : Type _ → Type _)
[Monad m] [MonadStateOf σ m] extends LawfulMonad m where
/-- `modifyGet f` is equal to getting the state, modifying it, and returning a result. -/
modifyGet_eq {α} (f : σ → α × σ) :
modifyGet (m := m) f = do let z ← f <$> get; set z.2; return z.1
/-- Discarding the result of `get` is the same as never getting the state. -/
get_bind_const {α} (mx : m α) : (do let _ ← get; mx) = mx
/-- Calling `get` twice is the same as just using the first retreived state value. -/
get_bind_get_bind {α} (mx : σ → σ → m α) :
(do let s ← get; let s' ← get; mx s s') = (do let s ← get; mx s s)
/-- Setting the monad state to its current value has no effect. -/
get_bind_set_bind {α} (mx : σ → PUnit → m α) :
(do let s ← get; let u ← set s; mx s u) = (do let s ← get; mx s PUnit.unit)
/-- Setting and then returning the monad state is the same as returning the set value. -/
set_bind_get (s : σ) : (do set (m := m) s; get) = (do set s; return s)
/-- Setting the monad twice is the same as just setting to the final state. -/
set_bind_set (s s' : σ) : (do set (m := m) s; set s') = set s'
namespace LawfulMonadStateOf
variable {σ : Type _} {m : Type _ → Type _} [Monad m]
[MonadStateOf σ m] [LawfulMonadStateOf σ m]
attribute [simp] get_bind_const get_bind_get_bind get_bind_set_bind set_bind_get set_bind_set
@[simp] theorem get_seqRight (mx : m α) : get *> mx = mx := by
rw [seqRight_eq_bind, get_bind_const]
@[simp] theorem seqLeft_get (mx : m α) : mx <* get = mx := by
simp only [seqLeft_eq_bind, get_bind_const, bind_pure]
@[simp] theorem get_map_const (x : α) :
(fun _ => x) <$> get (m := m) = pure x := by
rw [map_eq_pure_bind, get_bind_const]
theorem get_bind_get : (do let _ ← get (m := m); get) = get := get_bind_const get
@[simp] theorem get_bind_set :
(do let s ← get (m := m); set s) = return PUnit.unit := by
simpa only [bind_pure_comp, id_map', get_map_const] using
get_bind_set_bind (σ := σ) (m := m) (fun _ _ => return PUnit.unit)
@[simp] theorem get_bind_map_set (f : σ → PUnit → α) :
(do let s ← get (m := m); f s <$> set s) = (do return f (← get) PUnit.unit) := by
simp [map_eq_pure_bind, -bind_pure_comp]
@[simp] theorem set_bind_get_bind (s : σ) (f : σ → m α) :
(do set s; let s' ← get; f s') = (do set s; f s) := by
rw [← bind_assoc, set_bind_get, bind_assoc, pure_bind]
@[simp] theorem set_bind_map_get (f : σ → α) (s : σ) :
(do set (m := m) s; f <$> get) = (do set (m := m) s; pure (f s)) := by
simp [map_eq_pure_bind, -bind_pure_comp]
@[simp] theorem set_bind_set_bind (s s' : σ) (mx : m α) :
(do set s; set s'; mx) = (do set s'; mx) := by
rw [← bind_assoc, set_bind_set]
@[simp] theorem set_bind_map_set (s s' : σ) (f : PUnit → α) :
(do set (m := m) s; f <$> set s') = (do f <$> set s') := by
simp [map_eq_pure_bind, ← bind_assoc, -bind_pure_comp]
section modify
theorem modifyGetThe_eq (f : σ → α × σ) :
modifyGetThe σ (m := m) f = do let z ← f <$> get; set z.2; return z.1 := modifyGet_eq f
theorem modify_eq (f : σ → σ) :
modify (m := m) f = (do set (f (← get))) := by simp [modify, modifyGet_eq]
theorem modifyThe_eq (f : σ → σ) :
modifyThe σ (m := m) f = (do set (f (← get))) := modify_eq f
theorem getModify_eq (f : σ → σ) :
getModify (m := m) f = do let s ← get; set (f s); return s := by
rw [getModify, modifyGet_eq, bind_map_left]
/-- Version of `modifyGet_eq` that preserves an call to `modify`. -/
theorem modifyGet_eq' (f : σ → α × σ) :
modifyGet (m := m) f = do let s ← get; modify (Prod.snd ∘ f); return (f s).fst := by
simp [modify_eq, modifyGet_eq]
@[simp] theorem modify_id : modify (m := m) id = pure PUnit.unit := by
simp [modify_eq]
@[simp] theorem getModify_id : getModify (m := m) id = get := by
simp [getModify_eq]
@[simp] theorem set_bind_modify (s : σ) (f : σ → σ) :
(do set (m := m) s; modify f) = set (f s) := by simp [modify_eq]
@[simp] theorem set_bind_modify_bind (s : σ) (f : σ → σ) (mx : PUnit → m α) :
(do set s; let u ← modify f; mx u) = (do set (f s); mx PUnit.unit) := by
simp [modify_eq, ← bind_assoc]
@[simp] theorem set_bind_modifyGet (s : σ) (f : σ → α × σ) :
(do set (m := m) s; modifyGet f) = (do set (f s).2; return (f s).1) := by simp [modifyGet_eq]
@[simp] theorem set_bind_modifyGet_bind (s : σ) (f : σ → α × σ) (mx : α → m β) :
(do set s; let x ← modifyGet f; mx x) = (do set (f s).2; mx (f s).1) := by simp [modifyGet_eq]
@[simp] theorem set_bind_getModify (s : σ) (f : σ → σ) :
(do set (m := m) s; getModify f) = (do set (f s); return s) := by simp [getModify_eq]
@[simp] theorem set_bind_getModify_bind (s : σ) (f : σ → σ) (mx : σ → m α) :
(do set s; let x ← getModify f; mx x) = (do set (f s); mx s) := by
simp [getModify_eq, ← bind_assoc]
@[simp] theorem modify_bind_modify (f g : σ → σ) :
(do modify (m := m) f; modify g) = modify (g ∘ f) := by simp [modify_eq]
@[simp] theorem modify_bind_modifyGet (f : σ → σ) (g : σ → α × σ) :
(do modify (m := m) f; modifyGet g) = modifyGet (g ∘ f) := by
simp [modify_eq, modifyGet_eq]
@[simp] theorem getModify_bind_modify (f : σ → σ) (g : σ → σ → σ) :
(do let s ← getModify (m := m) f; modify (g s)) =
(do let s ← get; modify (g s ∘ f)) := by
simp [modify_eq, getModify_eq]
theorem modify_comm_of_comp_comm {f g : σ → σ} (h : f ∘ g = g ∘ f) :
(do modify (m := m) f; modify g) = (do modify (m := m) g; modify f) := by
simp [modify_bind_modify, h]
theorem modify_bind_get (f : σ → σ) :
(do modify (m := m) f; get) = (do let s ← get; modify f; return (f s)) := by
simp [modify_eq]
end modify
/-- `StateT` has lawful state operations if the underlying monad is lawful.
This is applied for `StateM` as well due to the reducibility of that definition. -/
instance {m σ} [Monad m] [LawfulMonad m] : LawfulMonadStateOf σ (StateT σ m) where
modifyGet_eq f := StateT.ext fun s => by simp
get_bind_const mx := StateT.ext fun s => by simp
get_bind_get_bind mx := StateT.ext fun s => by simp
get_bind_set_bind mx := StateT.ext fun s => by simp
set_bind_get s := StateT.ext fun s => by simp
set_bind_set s s' := StateT.ext fun s => by simp
/-- The continuation passing state monad variant `StateCpsT` always has lawful state instance. -/
instance {σ m} : LawfulMonadStateOf σ (StateCpsT σ m) where
modifyGet_eq _ := rfl
get_bind_const _ := rfl
get_bind_get_bind _ := rfl
get_bind_set_bind _ := rfl
set_bind_get _ := rfl
set_bind_set _ _ := rfl
/-- The `EStateM` monad always has a lawful state instance. -/
instance {σ ε} : LawfulMonadStateOf σ (EStateM ε σ) where
modifyGet_eq _ := rfl
get_bind_const _ := rfl
get_bind_get_bind _ := rfl
get_bind_set_bind _ := rfl
set_bind_get _ := rfl
set_bind_set _ _ := rfl
/-- If the underlying monad `m` has a lawful state instance, then the induced state instance on
`ReaderT ρ m` will also be lawful. -/
instance {m σ ρ} [Monad m] [LawfulMonad m] [MonadStateOf σ m] [LawfulMonadStateOf σ m] :
LawfulMonadStateOf σ (ReaderT ρ m) where
modifyGet_eq f := ReaderT.ext fun ctx => by
simp [← liftM_modifyGet, LawfulMonadStateOf.modifyGet_eq, ← liftM_get]
get_bind_const mx := ReaderT.ext fun ctx => by
simp [← liftM_get]
get_bind_get_bind mx := ReaderT.ext fun ctx => by
simp [← liftM_get]
get_bind_set_bind mx := ReaderT.ext fun ctx => by
simp [← liftM_get, ← liftM_set]
set_bind_get s := ReaderT.ext fun ctx => by
simp [← liftM_get, ← liftM_set]
set_bind_set s s' := ReaderT.ext fun ctx => by
simp [← liftM_set]
instance {m ω σ} [Monad m] [LawfulMonad m] [MonadStateOf σ m] [LawfulMonadStateOf σ m] :
LawfulMonadStateOf σ (StateRefT' ω σ m) :=
inferInstanceAs (LawfulMonadStateOf σ (ReaderT (ST.Ref ω σ) m))
================================================
FILE: Batteries/Control/Lemmas.lean
================================================
/-
Copyright (c) 2023 François G. Dorais. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: François G. Dorais, Eric Wieser
-/
module
import all Init.Control.Reader
import all Init.Control.State
@[expose] public section
namespace ReaderT
attribute [ext] ReaderT.ext
@[simp] theorem run_failure [Monad m] [Alternative m] (ctx : ρ) :
(failure : ReaderT ρ m α).run ctx = failure := (rfl)
@[simp] theorem run_orElse [Monad m] [Alternative m] (x y : ReaderT ρ m α) (ctx : ρ) :
(x <|> y).run ctx = (x.run ctx <|> y.run ctx) := (rfl)
@[simp] theorem run_throw [MonadExceptOf ε m] (e : ε) (ctx : ρ) :
(throw e : ReaderT ρ m α).run ctx = throw e := rfl
@[simp] theorem run_throwThe [MonadExceptOf ε m] (e : ε) (ctx : ρ) :
(throwThe ε e : ReaderT ρ m α).run ctx = throwThe ε e := rfl
@[simp] theorem run_tryCatch [MonadExceptOf ε m]
(body : ReaderT ρ m α) (handler : ε → ReaderT ρ m α) (ctx : ρ) :
(tryCatch body handler).run ctx = tryCatch (body.run ctx) (handler · |>.run ctx) := rfl
@[simp] theorem run_tryCatchThe [MonadExceptOf ε m]
(body : ReaderT ρ m α) (handler : ε → ReaderT ρ m α) (ctx : ρ) :
(tryCatchThe ε body handler).run ctx = tryCatchThe ε (body.run ctx) (handler · |>.run ctx) :=
rfl
end ReaderT
namespace StateT
attribute [ext] StateT.ext
@[simp] theorem run_failure {α σ} [Monad m] [Alternative m] (s : σ) :
(failure : StateT σ m α).run s = failure := (rfl)
@[simp] theorem run_orElse {α σ} [Monad m] [Alternative m] (x y : StateT σ m α) (s : σ) :
(x <|> y).run s = (x.run s <|> y.run s) := (rfl)
@[simp] theorem run_throw [Monad m] [MonadExceptOf ε m] (e : ε) (s : σ) :
(throw e : StateT σ m α).run s = (do let a ← throw e; pure (a, s)) := rfl
@[simp] theorem run_throwThe [Monad m] [MonadExceptOf ε m] (e : ε) (s : σ) :
(throwThe ε e : StateT σ m α).run s = (do let a ← throwThe ε e; pure (a, s)) := rfl
@[simp] theorem run_tryCatch [Monad m] [MonadExceptOf ε m]
(body : StateT σ m α) (handler : ε → StateT σ m α) (s : σ) :
(tryCatch body handler).run s = tryCatch (body.run s) (handler · |>.run s) := rfl
@[simp] theorem run_tryCatchThe [Monad m] [MonadExceptOf ε m]
(body : StateT σ m α) (handler : ε → StateT σ m α) (s : σ) :
(tryCatchThe ε body handler).run s = tryCatchThe ε (body.run s) (handler · |>.run s) := rfl
end StateT
================================================
FILE: Batteries/Control/Monad.lean
================================================
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
module
public import Batteries.Tactic.Alias
@[expose] public section
@[deprecated (since := "2025-02-09")] alias LawfulFunctor.map_inj_right_of_nonempty :=
map_inj_right_of_nonempty
@[deprecated (since := "2025-02-09")] alias LawfulMonad.map_inj_right :=
map_inj_right
================================================
FILE: Batteries/Control/Nondet/Basic.lean
================================================
/-
Copyright (c) 2023 Kim Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
module
public import Batteries.Tactic.Lint.Misc
public import Batteries.Data.MLList.Basic
import Lean.Util.MonadBacktrack
@[expose] public section
/-!
# A nondeterminism monad.
We represent nondeterministic values in a type `α` as a single field structure containing an
`MLList m (σ × α)`, i.e. as a monadic lazy list of possible values,
each equipped with the backtrackable state
required to run further computations in the ambient monad.
We provide an `Alternative` `Monad` instance, as well as functions `bind`, `mapM`, and `filterMapM`,
and functions `singletonM`, `ofListM`, `ofOptionM`, and `firstM`
for entering and leaving the nondeterministic world.
Operations on the nondeterministic value via `bind`, `mapM`, and `filterMapM`
run with the appropriate backtrackable state, and are responsible for updating the state themselves
(typically this doesn't need to be done explicitly,
but just happens as a side effect in the monad `m`).
-/
open Lean (MonadBacktrack)
open Lean.MonadBacktrack
/--
`Nondet m α` is variation on `MLList m α` suitable for use with backtrackable monads `m`.
We think of `Nondet m α` as a nondeterministic value in `α`,
with the possible alternatives stored in a monadic lazy list.
Along with each `a : α` we store the backtrackable state, and ensure that monadic operations
on alternatives run with the appropriate state.
Operations on the nondeterministic value via `bind`, `mapM`, and `filterMapM`
run with the appropriate backtrackable state, and are responsible for updating the state themselves
(typically this doesn't need to be done explicitly,
but just happens as a side effect in the monad `m`).
-/
@[nolint unusedArguments]
structure Nondet (m : Type → Type) [MonadBacktrack σ m] (α : Type) : Type where
/--
Convert a non-deterministic value into a lazy list, keeping the backtrackable state.
Be careful that monadic operations on the `MLList` will not respect this state!
-/
toMLList : MLList m (α × σ)
namespace Nondet
variable {m : Type → Type}
section Monad
variable [Monad m] [MonadBacktrack σ m]
/-- The empty nondeterministic value. -/
def nil : Nondet m α := .mk .nil
instance : Inhabited (Nondet m α) := ⟨.nil⟩
/--
Squash a monadic nondeterministic value to a nondeterministic value.
-/
def squash (L : Unit → m (Nondet m α)) : Nondet m α :=
.mk <| MLList.squash fun _ => return (← L ()).toMLList
/--
Bind a nondeterministic function over a nondeterministic value,
ensuring the function is run with the relevant backtrackable state at each value.
-/
partial def bind (L : Nondet m α) (f : α → Nondet m β) : Nondet m β := .squash fun _ => do
match ← L.toMLList.uncons with
| none => pure .nil
| some (⟨x, s⟩, xs) => do
let r := (Nondet.mk xs).bind f
restoreState s
match ← (f x).toMLList.uncons with
| none => return r
| some (y, ys) => return .mk <| .cons y (ys.append (fun _ => r.toMLList))
/-- Convert any value in the monad to the singleton nondeterministic value. -/
def singletonM (x : m α) : Nondet m α :=
.mk <| .singletonM do
let a ← x
return (a, ← saveState)
/-- Convert a value to the singleton nondeterministic value. -/
def singleton (x : α) : Nondet m α := singletonM (pure x)
/-- `Nondet m` is an alternative monad. -/
instance : AlternativeMonad (Nondet m) where
pure a := singletonM (pure a)
bind := bind
failure := .nil
orElse x y := .mk <| x.toMLList.append fun _ => (y ()).toMLList
instance : MonadLift m (Nondet m) where
monadLift := singletonM
/--
Lift a list of monadic values to a nondeterministic value.
We ensure that each monadic value is evaluated with the same backtrackable state.
-/
def ofListM (L : List (m α)) : Nondet m α :=
.squash fun _ => do
let s ← saveState
return .mk <| MLList.ofListM <| L.map fun x => do
restoreState s
let a ← x
pure (a, ← saveState)
/--
Lift a list of values to a nondeterministic value.
(The backtrackable state in each will be identical:
whatever the state was when we first read from the result.)
-/
def ofList (L : List α) : Nondet m α := ofListM (L.map pure)
/-- Apply a function which returns values in the monad to every alternative of a `Nondet m α`. -/
def mapM (f : α → m β) (L : Nondet m α) : Nondet m β :=
L.bind fun a => singletonM (f a)
/-- Apply a function to each alternative in a `Nondet m α` . -/
def map (f : α → β) (L : Nondet m α) : Nondet m β :=
L.mapM fun a => pure (f a)
/-- Convert a monadic optional value to a nondeterministic value. -/
def ofOptionM (x : m (Option α)) : Nondet m α := .squash fun _ => do
match ← x with
| none => return .nil
| some a => return singleton a
/-- Convert an optional value to a nondeterministic value. -/
def ofOption (x : Option α) : Nondet m α := ofOptionM (pure x)
/-- Filter and map a nondeterministic value using a monadic function which may return `none`. -/
def filterMapM (f : α → m (Option β)) (L : Nondet m α) : Nondet m β :=
L.bind fun a => ofOptionM (f a)
/-- Filter and map a nondeterministic value. -/
def filterMap (f : α → Option β) (L : Nondet m α) : Nondet m β :=
L.filterMapM fun a => pure (f a)
/-- Filter a nondeterministic value using a monadic predicate. -/
def filterM (p : α → m (ULift Bool)) (L : Nondet m α) : Nondet m α :=
L.filterMapM fun a => do
if (← p a).down then
pure (some a)
else
pure none
/-- Filter a nondeterministic value. -/
def filter (p : α → Bool) (L : Nondet m α) : Nondet m α :=
L.filterM fun a => pure <| .up (p a)
/--
All iterations of a non-deterministic function on an initial value.
(That is, depth first search.)
-/
partial def iterate (f : α → Nondet m α) (a : α) : Nondet m α :=
singleton a <|> (f a).bind (iterate f)
/--
Convert a non-deterministic value into a lazy list, by discarding the backtrackable state.
-/
def toMLList' (L : Nondet m α) : MLList m α := L.toMLList.map (·.1)
/--
Convert a non-deterministic value into a list in the monad, retaining the backtrackable state.
-/
def toList (L : Nondet m α) : m (List (α × σ)) := L.toMLList.force
/--
Convert a non-deterministic value into a list in the monad, by discarding the backtrackable state.
-/
def toList' (L : Nondet m α) : m (List α) := L.toMLList.map (·.1) |>.force
end Monad
section AlternativeMonad
variable [AlternativeMonad m] [MonadBacktrack σ m]
/--
Find the first alternative in a nondeterministic value, as a monadic value.
-/
def head (L : Nondet m α) : m α := do
let (x, s) ← L.toMLList.head
restoreState s
return x
/--
Find the value of a monadic function on the first alternative in a nondeterministic value
where the function succeeds.
-/
def firstM (L : Nondet m α) (f : α → m (Option β)) : m β :=
L.filterMapM f |>.head
end AlternativeMonad
end Nondet
/-- The `Id` monad is trivially backtrackable, with state `Unit`. -/
-- This is useful so we can use `Nondet Id α` instead of `List α`
-- as the basic non-determinism monad.
instance : MonadBacktrack Unit Id where
saveState := pure ()
restoreState _ := pure ()
================================================
FILE: Batteries/Control/OptionT.lean
================================================
/-
Copyright (c) 2017 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Ullrich
-/
module
public import Batteries.Control.LawfulMonadState
import all Init.Control.Option
@[expose] public section
/-!
# Lemmas About Option Monad Transformer
This file contains lemmas about the behavior of `OptionT` and `OptionT.run`.
-/
namespace OptionT
@[simp] theorem run_monadLift [Monad m] [LawfulMonad m] [MonadLiftT n m] (x : n α) :
(monadLift x : OptionT m α).run = some <$> (monadLift x : m α) := (map_eq_pure_bind _ _).symm
@[simp] theorem run_mapConst [Monad m] [LawfulMonad m] (x : OptionT m α) (y : β) :
(Functor.mapConst y x).run = Option.map (Function.const α y) <$> x.run := run_map _ _
instance [Monad m] [LawfulMonad m] [MonadStateOf σ m] [LawfulMonadStateOf σ m] :
LawfulMonadStateOf σ (OptionT m) where
modifyGet_eq f := by simp [← liftM_modifyGet, ← liftM_get, LawfulMonadStateOf.modifyGet_eq]
get_bind_const mx := OptionT.ext (by simp [← liftM_get])
get_bind_get_bind mx := OptionT.ext (by simp [← liftM_get])
get_bind_set_bind mx := OptionT.ext (by simp [← liftM_get, ← liftM_set])
set_bind_get s := OptionT.ext (by simp [← liftM_get, ← liftM_set])
set_bind_set s s' := OptionT.ext (by simp [← liftM_set])
end OptionT
================================================
FILE: Batteries/Data/Array/Basic.lean
================================================
/-
Copyright (c) 2021 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Arthur Paulino, Floris van Doorn, Jannis Limperg
-/
module
import Batteries.Tactic.Alias
import Batteries.Data.UInt
@[expose] public section
/-!
## Definitions on Arrays
This file contains various definitions on `Array`. It does not contain
proofs about these definitions, those are contained in other files in `Batteries.Data.Array`.
-/
namespace Array
/--
Check whether `xs` and `ys` are equal as sets, i.e. they contain the same
elements when disregarding order and duplicates. `O(n*m)`! If your element type
has an `Ord` instance, it is asymptotically more efficient to sort the two
arrays, remove duplicates and then compare them elementwise.
-/
def equalSet [BEq α] (xs ys : Array α) : Bool :=
xs.all (ys.contains ·) && ys.all (xs.contains ·)
/--
Returns the first minimal element among `d` and elements of the array.
If `start` and `stop` are given, only the subarray `xs[start...stop]` is
considered (in addition to `d`).
-/
@[inline]
protected def rangeMinWith [ord : Ord α]
(xs : Array α) (d : α) (start := 0) (stop := xs.size) : α :=
xs.foldl (init := d) (start := start) (stop := stop) fun min x =>
if compare x min |>.isLT then x else min
@[inherit_doc Array.rangeMinWith, deprecated Array.rangeMinWith (since := "2026-01-08")]
protected def minWith := @Array.rangeMinWith
/--
Find the first minimal element of an array. If the array is empty, `d` is
returned. If `start` and `stop` are given, only the subarray `xs[start...stop]` is
considered.
-/
@[inline]
protected def rangeMinD [ord : Ord α]
(xs : Array α) (d : α) (start := 0) (stop := xs.size) : α :=
if h: start < xs.size ∧ start < stop then
xs.rangeMinWith xs[start] (start + 1) stop
else
d
@[inherit_doc Array.rangeMinD, deprecated Array.rangeMinD (since := "2026-01-08")]
protected def minD := @Array.rangeMinD
/--
Find the first minimal element of an array. If the array is empty, `none` is
returned. If `start` and `stop` are given, only the subarray `xs[start...stop]` is
considered.
-/
@[inline]
protected def rangeMin? [ord : Ord α]
(xs : Array α) (start := 0) (stop := xs.size) : Option α :=
if h : start < xs.size ∧ start < stop then
some $ xs.rangeMinD xs[start] start stop
else
none
/--
Find the first minimal element of an array. If the array is empty, `default` is
returned. If `start` and `stop` are given, only the subarray `xs[start...stop]` is
considered.
-/
@[inline]
protected def rangeMinI [ord : Ord α] [Inhabited α]
(xs : Array α) (start := 0) (stop := xs.size) : α :=
xs.rangeMinD default start stop
@[inherit_doc Array.rangeMinI, deprecated Array.rangeMinI (since := "2026-01-08")]
protected def minI := @Array.rangeMinI
/--
Returns the first maximal element among `d` and elements of the array.
If `start` and `stop` are given, only the subarray `xs[start...stop]` is
considered (in addition to `d`).
-/
@[inline]
protected def rangeMaxWith [ord : Ord α]
(xs : Array α) (d : α) (start := 0) (stop := xs.size) : α :=
xs.rangeMinWith (ord := ord.opposite) d start stop
@[inherit_doc Array.rangeMaxWith, deprecated Array.rangeMaxWith (since := "2026-01-08")]
protected def maxWith := @Array.rangeMaxWith
/--
Find the first maximal element of an array. If the array is empty, `d` is
returned. If `start` and `stop` are given, only the subarray `xs[start...stop]` is
considered.
-/
@[inline]
protected def rangeMaxD [ord : Ord α]
(xs : Array α) (d : α) (start := 0) (stop := xs.size) : α :=
xs.rangeMinD (ord := ord.opposite) d start stop
@[inherit_doc Array.rangeMaxD, deprecated Array.rangeMaxD (since := "2026-01-08")]
protected def maxD := @Array.rangeMaxD
/--
Find the first maximal element of an array. If the array is empty, `none` is
returned. If `start` and `stop` are given, only the subarray `xs[start...stop]` is
considered.
-/
@[inline]
protected def rangeMax? [ord : Ord α]
(xs : Array α) (start := 0) (stop := xs.size) : Option α :=
xs.rangeMin? (ord := ord.opposite) start stop
/--
Find the first maximal element of an array. If the array is empty, `default` is
returned. If `start` and `stop` are given, only the subarray `xs[start...stop]` is
considered.
-/
@[inline]
protected def rangeMaxI [ord : Ord α] [Inhabited α]
(xs : Array α) (start := 0) (stop := xs.size) : α :=
xs.rangeMinI (ord := ord.opposite) start stop
@[inherit_doc Array.rangeMaxI, deprecated Array.rangeMaxI (since := "2026-01-08")]
protected def maxI := @Array.rangeMaxI
@[deprecated set (since := "2026-02-02")]
alias setN := set
/-
This is guaranteed by the Array docs but it is unprovable.
May be asserted to be true in an unsafe context via `Array.unsafe_sizeFitsUsize`
-/
private abbrev SizeFitsUSize (a : Array α) : Prop := a.size < USize.size
/-
This is guaranteed by the Array docs but it is unprovable.
Can be used in unsafe functions to write more efficient implementations
that avoid arbitrary precision integer arithmetic.
-/
private unsafe def unsafe_sizeFitsUSize (a : Array α) : SizeFitsUSize a :=
lcProof
@[inline]
private def scanlMFast [Monad m] (f : β → α → m β) (init : β) (as : Array α)
(start := 0) (stop := as.size) : m (Array β) :=
let stop := min stop as.size
let start := min start as.size
loop f init as
(start := USize.ofNat start) (stop := USize.ofNat stop)
(h_stop := by grind only [USize.size_eq, USize.ofNat_eq_iff_mod_eq_toNat, = Nat.min_def])
(acc := Array.mkEmpty <| stop - start + 1)
where
@[specialize]
loop (f : β → α → m β) (init: β) (as: Array α) (start stop : USize)
(h_stop : stop.toNat ≤ as.size) (acc : Array β) : m (Array β) := do
if h_lt: start < stop then
let next ← f init (as.uget start <| Nat.lt_of_lt_of_le h_lt h_stop)
loop f next as (start + 1) stop h_stop (acc.push init)
else
pure <| acc.push init
termination_by stop.toNat - min start.toNat stop.toNat
decreasing_by
have : start < (start + 1) := by grind only [USize.size_eq]
grind only [Nat.min_def, USize.lt_iff_toNat_lt]
/--
Folds a monadic function over an array from the left, accumulating the partial results starting with
`init`. The accumulated value is combined with the each element of the list in order, using `f`.
The optional parameters `start` and `stop` control the region of the array to be folded. Folding
proceeds from `start` (inclusive) to `stop` (exclusive), so no folding occurs unless `start < stop`.
By default, the entire array is folded.
Examples:
```lean example
example [Monad m] (f : α → β → m α) :
Array.scanlM f x₀ #[a, b, c] = (do
let x₁ ← f x₀ a
let x₂ ← f x₁ b
let x₃ ← f x₂ c
pure #[x₀, x₁, x₂, x₃])
:= by simp [scanlM, scanlM.loop]
```
```lean example
example [Monad m] (f : α → β → m α) :
Array.scanlM f x₀ #[a, b, c] (start := 1) (stop := 3) = (do
let x₁ ← f x₀ b
let x₂ ← f x₁ c
pure #[x₀, x₁, x₂])
:= by simp [scanlM, scanlM.loop]
```
-/
@[implemented_by scanlMFast]
def scanlM [Monad m] (f : β → α → m β) (init : β) (as : Array α) (start := 0)
(stop := as.size) : m (Array β) :=
loop f init as (min start as.size) (min stop as.size) (Nat.min_le_right _ _) #[]
where
/-- auxiliary tail-recursive function for scanlM -/
loop (f : β → α → m β) (init : β ) (as : Array α) (start stop : Nat)
(h_stop : stop ≤ as.size) (acc : Array β) : m (Array β) := do
if h_lt : start < stop then
loop f (← f init as[start]) as (start + 1) stop h_stop (acc.push init)
else
pure <| acc.push init
private theorem scanlM_loop_eq_scanlMFast_loop [Monad m]
{f : β → α → m β} {init : β} {as : Array α} {h_size : as.SizeFitsUSize}
{start stop : Nat} {h_start : start ≤ as.size}
{h_stop : stop ≤ as.size} {acc : Array β} :
scanlM.loop f init as start stop h_stop acc
= scanlMFast.loop f init as (USize.ofNat start) (USize.ofNat stop)
(by rw [USize.toNat_ofNat_of_le_of_lt h_size h_stop]; exact h_stop) acc := by
generalize h_n : stop - start = n
induction n using Nat.strongRecOn generalizing start acc init
rename_i n ih
rw [scanlM.loop, scanlMFast.loop]
have h_stop_usize := USize.toNat_ofNat_of_le_of_lt h_size h_stop
have h_start_usize := USize.toNat_ofNat_of_le_of_lt h_size h_start
split
case isTrue h_lt =>
simp_all only [USize.toNat_ofNat', ↓reduceDIte, uget,
show USize.ofNat start < USize.ofNat stop by simp_all [USize.lt_iff_toNat_lt]]
apply bind_congr
intro next
have h_start_succ : USize.ofNat start + 1 = USize.ofNat (start + 1) := by
simp_all only [← USize.toNat_inj, USize.toNat_add]
grind only [USize.size_eq, USize.toNat_ofNat_of_le_of_lt]
rw [h_start_succ]
apply ih (stop - (start + 1)) <;> omega
case isFalse h_nlt => grind [USize.lt_iff_toNat_lt]
-- this theorem establishes that given the (unprovable) assumption that as.size < USize.size,
-- the scanlMFast and scanlM are equivalent
-- TODO (cmlsharp): prova an analogous theorem for scanrM
private theorem scanlM_eq_scanlMFast [Monad m]
{f : β → α → m β} {init : β} {as : Array α}
{h_size : as.SizeFitsUSize} {start stop : Nat} :
scanlM f init as start stop = scanlMFast f init as start stop := by
unfold scanlM scanlMFast
apply scanlM_loop_eq_scanlMFast_loop
simp_all only [gt_iff_lt]
apply Nat.min_le_right
@[inline]
private def scanrMFast [Monad m] (f : α → β → m β) (init : β) (as : Array α)
(h_size : as.SizeFitsUSize) (start := as.size) (stop := 0) : m (Array β) :=
let start := min start as.size
let stop := min stop start
loop f init as
(start := USize.ofNat start) (stop := USize.ofNat stop)
(h_start := by grind only [USize.size_eq, USize.ofNat_eq_iff_mod_eq_toNat, = Nat.min_def])
(acc := Array.replicate (start - stop + 1) init)
(by grind only [!Array.size_replicate, = Nat.min_def, USize.toNat_ofNat_of_le_of_lt])
where
@[specialize]
loop (f : α → β → m β) (init : β) (as : Array α)
(start stop : USize)
(h_start : start.toNat ≤ as.size)
(acc : Array β)
(h_bound : start.toNat - stop.toNat < acc.size) :
m (Array β) := do
if h_gt : stop < start then
let startM1 := start - 1
have : startM1 < start := by grind only [!USize.sub_add_cancel, USize.lt_iff_le_and_ne,
USize.lt_add_one, USize.le_zero_iff]
have : startM1.toNat < as.size := Nat.lt_of_lt_of_le ‹_› ‹_›
have : (startM1 - stop) < (start - stop) := by grind only
[!USize.sub_add_cancel, USize.sub_right_inj, USize.add_comm, USize.lt_add_one,
USize.add_assoc, USize.add_right_inj]
let next ← f (as.uget startM1 ‹_›) init
loop f next as
(start := startM1)
(stop := stop)
(h_start := Nat.le_of_succ_le_succ (Nat.le_succ_of_le ‹_›))
(acc := acc.uset (startM1 - stop) next
(by grind only [USize.toNat_sub_of_le, USize.le_of_lt, USize.lt_iff_toNat_lt]))
(h_bound :=
(by grind only [USize.toNat_sub_of_le, = uset_eq_set, = size_set, USize.size_eq]))
else
pure acc
termination_by start.toNat - stop.toNat
decreasing_by
grind only [USize.lt_iff_toNat_lt, USize.toNat_sub,
USize.toNat_sub_of_le, USize.le_iff_toNat_le]
@[inline]
private unsafe def scanrMUnsafe [Monad m] (f : α → β → m β) (init : β) (as : Array α)
(start := as.size) (stop := 0) : m (Array β) :=
scanrMFast (h_size := as.unsafe_sizeFitsUSize) f init as (start := start) (stop := stop)
/--
Folds a monadic function over an array from the right, accumulating the partial results starting
with `init`. The accumulated value is combined with the each element of the list in order using `f`.
The optional parameters `start` and `stop` control the region of the array to be folded. Folding
proceeds from `start` (exclusive) to `stop` (inclusive), so no folding occurs unless `start > stop`.
By default, the entire array is folded.
Examples:
```lean example
example [Monad m] (f : α → β → m β) :
Array.scanrM f x₀ #[a, b, c] = (do
let x₁ ← f c x₀
let x₂ ← f b x₁
let x₃ ← f a x₂
pure #[x₃, x₂, x₁, x₀])
:= by simp [scanrM, scanrM.loop]
```
```lean example
example [Monad m] (f : α → β → m β) :
Array.scanrM f x₀ #[a, b, c] (start := 3) (stop := 1) = (do
let x₁ ← f c x₀
let x₂ ← f b x₁
pure #[x₂, x₁, x₀])
:= by simp [scanrM, scanrM.loop]
```
-/
@[implemented_by scanrMUnsafe]
def scanrM [Monad m]
(f : α → β → m β) (init : β) (as : Array α) (start := as.size) (stop := 0) : m (Array β) :=
let start := min start as.size
loop f init as start stop (Nat.min_le_right _ _) #[]
where
/-- auxiliary tail-recursive function for scanrM -/
loop (f : α → β → m β) (init : β) (as : Array α)
(start stop : Nat)
(h_start : start ≤ as.size)
(acc : Array β) :
m (Array β) := do
if h_gt : stop < start then
let i := start - 1
let next ← f as[i] init
loop f next as i stop (by omega) (acc.push init)
else
pure <| acc.push init |>.reverse
/--
Fold a function `f` over the array from the left, returning the array of partial results.
```
scanl (· + ·) 0 #[1, 2, 3] = #[0, 1, 3, 6]
```
-/
@[inline]
def scanl (f : β → α → β) (init : β) (as : Array α) (start := 0) (stop := as.size) : Array β :=
Id.run <| as.scanlM (pure <| f · ·) init start stop
/--
Fold a function `f` over the array from the right, returning the array of partial results.
```
scanr (· + ·) 0 #[1, 2, 3] = #[6, 5, 3, 0]
```
-/
@[inline]
def scanr (f : α → β → β) (init : β) (as : Array α) (start := as.size) (stop := 0) : Array β :=
Id.run <| as.scanrM (pure <| f · ·) init start stop
end Array
namespace Subarray
/--
Fold a monadic function `f` over the subarray from the left, returning the list of partial results.
-/
@[inline]
def scanlM [Monad m] (f : β → α → m β) (init : β) (as : Subarray α) : m (Array β) :=
as.array.scanlM f init (start := as.start) (stop := as.stop)
/--
Fold a monadic function `f` over the subarray from the right, returning the list of partial results.
-/
@[inline]
def scanrM [Monad m] (f : α → β → m β) (init : β) (as : Subarray α) : m (Array β) :=
as.array.scanrM f init (start := as.start) (stop := as.stop)
/--
Fold a function `f` over the subarray from the left, returning the list of partial results.
-/
@[inline]
def scanl (f : β → α → β) (init : β) (as : Subarray α) : Array β :=
as.array.scanl f init (start := as.start) (stop := as.stop)
/--
Fold a function `f` over the subarray from the right, returning the list of partial results.
-/
@[inline]
def scanr (f : α → β → β) (init : β) (as : Subarray α) : Array β :=
as.array.scanr f init (start := as.start) (stop := as.stop)
/--
Check whether a subarray is empty.
-/
@[inline]
def isEmpty (as : Subarray α) : Bool :=
as.start == as.stop
/--
Check whether a subarray contains a given element.
-/
@[inline]
def contains [BEq α] (as : Subarray α) (a : α) : Bool :=
as.any (· == a)
/--
Remove the first element of a subarray. Returns the element and the remaining
subarray, or `none` if the subarray is empty.
-/
def popHead? (as : Subarray α) : Option (α × Subarray α) :=
if h : as.start < as.stop
then
let head := as.array[as.start]'(Nat.lt_of_lt_of_le h as.stop_le_array_size)
let tail :=
⟨{ as.internalRepresentation with
start := as.start + 1
start_le_stop := Nat.le_of_lt_succ $ Nat.succ_lt_succ h }⟩
some (head, tail)
else
none
end Subarray
================================================
FILE: Batteries/Data/Array/Init/Lemmas.lean
================================================
/-
Copyright (c) 2024 Kim Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
module
@[expose] public section
/-!
While this file is currently empty, it is intended as a home for any lemmas which are required for
definitions in `Batteries.Data.Array.Basic`, but which are not provided by Lean.
-/
================================================
FILE: Batteries/Data/Array/Lemmas.lean
================================================
/-
Copyright (c) 2021 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Gabriel Ebner
-/
module
public import Batteries.Data.List.Lemmas
@[expose] public section
namespace Array
@[deprecated forIn_toList (since := "2025-07-01")]
theorem forIn_eq_forIn_toList [Monad m]
(as : Array α) (b : β) (f : α → β → m (ForInStep β)) :
forIn as b f = forIn as.toList b f := by
cases as
simp
/-! ### idxOf? -/
@[grind =]
theorem idxOf?_toList [BEq α] {a : α} {l : Array α} :
l.toList.idxOf? a = l.idxOf? a := by
rcases l with ⟨l⟩
simp
/-! ### erase -/
@[deprecated (since := "2025-02-06")] alias eraseP_toArray := List.eraseP_toArray
@[deprecated (since := "2025-02-06")] alias erase_toArray := List.erase_toArray
@[simp, grind =] theorem toList_erase [BEq α] (l : Array α) (a : α) :
(l.erase a).toList = l.toList.erase a := by
rcases l with ⟨l⟩
simp
@[simp] theorem size_eraseIdxIfInBounds (a : Array α) (i : Nat) :
(a.eraseIdxIfInBounds i).size = if i < a.size then a.size-1 else a.size := by
grind
theorem toList_drop (as: Array α) (n : Nat) :
(as.drop n).toList = as.toList.drop n := by
simp only [drop, toList_extract, size_eq_length_toList, List.drop_eq_extract]
/-! ### set -/
theorem size_set! (a : Array α) (i v) : (a.set! i v).size = a.size := by simp
/-! ### map -/
/-! ### mem -/
/-! ### insertAt -/
@[deprecated (since := "2025-02-06")] alias getElem_insertIdx_lt := getElem_insertIdx_of_lt
@[deprecated (since := "2025-02-06")] alias getElem_insertIdx_eq := getElem_insertIdx_self
@[deprecated (since := "2025-02-06")] alias getElem_insertIdx_gt := getElem_insertIdx_of_gt
/-! ### extract -/
@[simp] theorem extract_empty_of_start_eq_stop {a : Array α} :
a.extract i i = #[] := by grind
theorem extract_append_of_stop_le_size_left {a b : Array α} (h : j ≤ a.size) :
(a ++ b).extract i j = a.extract i j := by grind
theorem extract_append_of_size_left_le_start {a b : Array α} (h : a.size ≤ i) :
(a ++ b).extract i j = b.extract (i - a.size) (j - a.size) := by
rw [extract_append]; grind
theorem extract_eq_of_size_le_stop {a : Array α} (h : a.size ≤ j) :
a.extract i j = a.extract i := by grind
================================================
FILE: Batteries/Data/Array/Match.lean
================================================
/-
Copyright (c) 2023 F. G. Dorais. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: F. G. Dorais
-/
module
@[expose] public section
namespace Array
/-- Prefix table for the Knuth-Morris-Pratt matching algorithm
This is an array of the form `t = [(x₀,n₀), (x₁,n₁), (x₂, n₂), ...]` where for each `i`, `nᵢ` is
the length of the longest proper prefix of `xs = [x₀,x₁,...,xᵢ]` which is also a suffix of `xs`.
-/
structure PrefixTable (α : Type _) extends Array (α × Nat) where
/-- Validity condition to help with termination proofs -/
valid : (h : i < toArray.size) → toArray[i].2 ≤ i
instance : Inhabited (PrefixTable α) where
default := ⟨#[], nofun⟩
/-- Returns the size of the prefix table -/
abbrev PrefixTable.size (t : PrefixTable α) := t.toArray.size
/-- Transition function for the KMP matcher
Assuming we have an input `xs` with a suffix that matches the pattern prefix `t.pattern[:len]`
where `len : Fin (t.size+1)`. Then `xs.push x` has a suffix that matches the pattern prefix
`t.pattern[:t.step x len]`. If `len` is as large as possible then `t.step x len` will also be
as large as possible.
-/
def PrefixTable.step [BEq α] (t : PrefixTable α) (x : α) : Fin (t.size+1) → Fin (t.size+1)
| ⟨k, hk⟩ =>
let cont := fun () =>
match k with
| 0 => ⟨0, Nat.zero_lt_succ _⟩
| k + 1 =>
have h2 : k < t.size := Nat.lt_of_succ_lt_succ hk
let k' := t.toArray[k].2
have hk' : k' < k + 1 := Nat.lt_succ_of_le (t.valid h2)
step t x ⟨k', Nat.lt_trans hk' hk⟩
if hsz : k < t.size then
if x == t.toArray[k].1 then
⟨k+1, Nat.succ_lt_succ hsz⟩
else cont ()
else cont ()
termination_by k => k.val
/-- Extend a prefix table by one element
If `t` is the prefix table for `xs` then `t.extend x` is the prefix table for `xs.push x`.
-/
def PrefixTable.extend [BEq α] (t : PrefixTable α) (x : α) : PrefixTable α where
toArray := t.toArray.push (x, t.step x ⟨t.size, Nat.lt_succ_self _⟩)
valid _ := by
rw [Array.getElem_push]
split
· exact t.valid ..
· next h => exact Nat.le_trans (Nat.lt_succ_iff.1 <| Fin.isLt ..) (Nat.not_lt.1 h)
/-- Make prefix table from a pattern array -/
def mkPrefixTable [BEq α] (xs : Array α) : PrefixTable α := xs.foldl (·.extend) default
/-- Make prefix table from a pattern stream -/
partial def mkPrefixTableOfStream [BEq α] [Std.Stream σ α] (stream : σ) : PrefixTable α :=
loop default stream
where
/-- Inner loop for `mkPrefixTableOfStream` -/
loop (t : PrefixTable α) (stream : σ) :=
match Stream.next? stream with
| none => t
| some (x, stream) => loop (t.extend x) stream
/-- KMP matcher structure -/
structure Matcher (α) where
/-- Prefix table for the pattern -/
table : PrefixTable α
/-- Current longest matching prefix -/
state : Fin (table.size + 1) := 0
/-- Make a KMP matcher for a given pattern array -/
def Matcher.ofArray [BEq α] (pat : Array α) : Matcher α where
table := mkPrefixTable pat
/-- Make a KMP matcher for a given a pattern stream -/
def Matcher.ofStream [BEq α] [Std.Stream σ α] (pat : σ) : Matcher α where
table := mkPrefixTableOfStream pat
/-- Find next match from a given stream
Runs the stream until it reads a sequence that matches the sought pattern, then returns the stream
state at that point and an updated matcher state.
-/
partial def Matcher.next? [BEq α] [Std.Stream σ α] (m : Matcher α) (stream : σ) :
Option (σ × Matcher α) :=
match Stream.next? stream with
| none => none
| some (x, stream) =>
let state := m.table.step x m.state
if state = m.table.size then
some (stream, { m with state })
else
next? { m with state } stream
namespace Matcher
open Std Std.Iterators
/-- Iterator transformer for KMP matcher. -/
protected structure Iterator (σ n α) [BEq α] (m : Matcher α) [Iterator σ n α] where
/-- Inner iterator. -/
inner : IterM (α := σ) n α
/-- Matcher state. -/
state : Fin (m.table.size + 1) := 0
/-- Implementation datail for `Matcher.Iterator`. -/
def modifyStep [BEq α] (m : Matcher α) [Iterator σ n α]
(it : IterM (α := m.Iterator σ n α) n σ) :
it.internalState.inner.Step (α := σ) → IterStep (IterM (α := m.Iterator σ n α) n σ) σ
| .done _ => .done
| .skip it' _ => .skip ⟨{it.internalState with inner := it'}⟩
| .yield it' x _ =>
let state := m.table.step x m.state
if state = m.table.size then
.yield ⟨{inner := it', state := state}⟩ it'.internalState
else
.skip ⟨{inner := it', state := state}⟩
instance [Monad n] [BEq α] (m : Matcher α) [Iterator σ n α] :
Iterator (m.Iterator σ n α) n σ where
IsPlausibleStep it step := ∃ step', m.modifyStep it step' = step
step it := it.internalState.inner.step >>=
fun step => pure (.deflate ⟨m.modifyStep _ _, step.inflate, rfl⟩)
private def finitenessRelation [Monad n] [BEq α] (m : Matcher α) [Iterator σ n α] [Finite σ n] :
FinitenessRelation (m.Iterator σ n α) n where
Rel := InvImage IterM.IsPlausibleSuccessorOf fun it => it.internalState.inner
wf := InvImage.wf _ Finite.wf
subrelation {it it'} h := by
obtain ⟨_, hsucc, step, rfl⟩ := h
simp only [IterM.Step] at step
cases step with simp only [IterStep.successor, modifyStep, reduceCtorEq] at hsucc
| skip =>
cases hsucc
apply IterM.isPlausibleSuccessorOf_of_skip
assumption
| yield =>
split at hsucc
· next heq =>
cases hsucc
split at heq
· cases heq
apply IterM.isPlausibleSuccessorOf_of_yield
assumption
· contradiction
· next heq =>
cases hsucc
split at heq
· contradiction
· cases heq
apply IterM.isPlausibleSuccessorOf_of_yield
assumption
· contradiction
instance [Monad n] [BEq α] (m : Matcher α) [Iterator σ n α] [inst : Finite σ n] :
Finite (m.Iterator σ n α) n (β := σ) := .of_finitenessRelation m.finitenessRelation
end Matcher
end Array
================================================
FILE: Batteries/Data/Array/Merge.lean
================================================
/-
Copyright (c) 2022 Jannis Limperg. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jannis Limperg
-/
module
@[expose] public section
namespace Array
/--
`O(|xs| + |ys|)`. Merge arrays `xs` and `ys`. If the arrays are sorted according to `lt`, then the
result is sorted as well. If two (or more) elements are equal according to `lt`, they are preserved.
-/
def merge (lt : α → α → Bool) (xs ys : Array α) : Array α :=
go (Array.mkEmpty (xs.size + ys.size)) 0 0
where
/-- Auxiliary definition for `merge`. -/
go (acc : Array α) (i j : Nat) : Array α :=
if hi : i ≥ xs.size then
acc ++ ys[j:]
else if hj : j ≥ ys.size then
acc ++ xs[i:]
else
let x := xs[i]
let y := ys[j]
if lt x y then go (acc.push x) (i + 1) j else go (acc.push y) i (j + 1)
termination_by xs.size + ys.size - (i + j)
-- We name `ord` so it can be provided as a named argument.
set_option linter.unusedVariables.funArgs false in
/--
`O(|xs| + |ys|)`. Merge arrays `xs` and `ys`, which must be sorted according to `compare` and must
not contain duplicates. Equal elements are merged using `merge`. If `merge` respects the order
(i.e. for all `x`, `y`, `y'`, `z`, if `x < y < z` and `x < y' < z` then `x < merge y y' < z`)
then the resulting array is again sorted.
-/
def mergeDedupWith [ord : Ord α] (xs ys : Array α) (merge : α → α → α) : Array α :=
go (Array.mkEmpty (xs.size + ys.size)) 0 0
where
/-- Auxiliary definition for `mergeDedupWith`. -/
go (acc : Array α) (i j : Nat) : Array α :=
if hi : i ≥ xs.size then
acc ++ ys[j:]
else if hj : j ≥ ys.size then
acc ++ xs[i:]
else
let x := xs[i]
let y := ys[j]
match compare x y with
| .lt => go (acc.push x) (i + 1) j
| .gt => go (acc.push y) i (j + 1)
| .eq => go (acc.push (merge x y)) (i + 1) (j + 1)
termination_by xs.size + ys.size - (i + j)
/--
`O(|xs| + |ys|)`. Merge arrays `xs` and `ys`, which must be sorted according to `compare` and must
not contain duplicates. If an element appears in both `xs` and `ys`, only one copy is kept.
-/
@[inline] def mergeDedup [ord : Ord α] (xs ys : Array α) : Array α :=
mergeDedupWith (ord := ord) xs ys fun x _ => x
set_option linter.unusedVariables false in
/--
`O(|xs| * |ys|)`. Merge `xs` and `ys`, which do not need to be sorted. Elements which occur in
both `xs` and `ys` are only added once. If `xs` and `ys` do not contain duplicates, then neither
does the result.
-/
def mergeUnsortedDedup
gitextract_6ram4obd/
├── .docker/
│ └── gitpod/
│ └── Dockerfile
├── .github/
│ └── workflows/
│ ├── build.yml
│ ├── docs-deploy.yml
│ ├── docs-release.yml
│ ├── labels-from-comments.yml
│ ├── labels-from-status.yml
│ ├── merge_conflicts.yml
│ ├── nightly_bump_and_merge.yml
│ ├── nightly_detect_failure.yml
│ ├── nightly_merge_master.yml
│ └── test_mathlib.yml
├── .gitignore
├── .gitpod.yml
├── .vscode/
│ ├── copyright.code-snippets
│ └── settings.json
├── Batteries/
│ ├── Classes/
│ │ ├── Cast.lean
│ │ ├── Deprecated.lean
│ │ ├── Order.lean
│ │ ├── RatCast.lean
│ │ └── SatisfiesM.lean
│ ├── CodeAction/
│ │ ├── Attr.lean
│ │ ├── Basic.lean
│ │ ├── Deprecated.lean
│ │ ├── Match.lean
│ │ └── Misc.lean
│ ├── CodeAction.lean
│ ├── Control/
│ │ ├── AlternativeMonad.lean
│ │ ├── ForInStep/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── ForInStep.lean
│ │ ├── LawfulMonadState.lean
│ │ ├── Lemmas.lean
│ │ ├── Monad.lean
│ │ ├── Nondet/
│ │ │ └── Basic.lean
│ │ └── OptionT.lean
│ ├── Data/
│ │ ├── Array/
│ │ │ ├── Basic.lean
│ │ │ ├── Init/
│ │ │ │ └── Lemmas.lean
│ │ │ ├── Lemmas.lean
│ │ │ ├── Match.lean
│ │ │ ├── Merge.lean
│ │ │ ├── Monadic.lean
│ │ │ ├── Pairwise.lean
│ │ │ └── Scan.lean
│ │ ├── Array.lean
│ │ ├── AssocList.lean
│ │ ├── BinaryHeap/
│ │ │ └── Basic.lean
│ │ ├── BinaryHeap.lean
│ │ ├── BinomialHeap/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── BinomialHeap.lean
│ │ ├── BitVec/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── BitVec.lean
│ │ ├── Bool.lean
│ │ ├── ByteArray.lean
│ │ ├── ByteSlice.lean
│ │ ├── Char/
│ │ │ ├── AsciiCasing.lean
│ │ │ └── Basic.lean
│ │ ├── Char.lean
│ │ ├── DList/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── DList.lean
│ │ ├── Fin/
│ │ │ ├── Basic.lean
│ │ │ ├── Fold.lean
│ │ │ ├── Lemmas.lean
│ │ │ └── OfBits.lean
│ │ ├── Fin.lean
│ │ ├── FloatArray.lean
│ │ ├── HashMap/
│ │ │ └── Basic.lean
│ │ ├── HashMap.lean
│ │ ├── Int.lean
│ │ ├── List/
│ │ │ ├── ArrayMap.lean
│ │ │ ├── Basic.lean
│ │ │ ├── Count.lean
│ │ │ ├── Init/
│ │ │ │ └── Lemmas.lean
│ │ │ ├── Lemmas.lean
│ │ │ ├── Matcher.lean
│ │ │ ├── Monadic.lean
│ │ │ ├── Pairwise.lean
│ │ │ ├── Perm.lean
│ │ │ └── Scan.lean
│ │ ├── List.lean
│ │ ├── MLList/
│ │ │ ├── Basic.lean
│ │ │ ├── Heartbeats.lean
│ │ │ └── IO.lean
│ │ ├── MLList.lean
│ │ ├── NameSet.lean
│ │ ├── Nat/
│ │ │ ├── Basic.lean
│ │ │ ├── Bisect.lean
│ │ │ ├── Bitwise/
│ │ │ │ └── Lemmas.lean
│ │ │ ├── Bitwise.lean
│ │ │ ├── Gcd.lean
│ │ │ └── Lemmas.lean
│ │ ├── Nat.lean
│ │ ├── PairingHeap.lean
│ │ ├── RBMap/
│ │ │ ├── Alter.lean
│ │ │ ├── Basic.lean
│ │ │ ├── Depth.lean
│ │ │ ├── Lemmas.lean
│ │ │ └── WF.lean
│ │ ├── RBMap.lean
│ │ ├── Random/
│ │ │ └── MersenneTwister.lean
│ │ ├── Random.lean
│ │ ├── Range/
│ │ │ └── Lemmas.lean
│ │ ├── Range.lean
│ │ ├── Rat/
│ │ │ └── Float.lean
│ │ ├── Rat.lean
│ │ ├── RunningStats.lean
│ │ ├── Stream.lean
│ │ ├── String/
│ │ │ ├── AsciiCasing.lean
│ │ │ ├── Basic.lean
│ │ │ ├── Legacy.lean
│ │ │ ├── Lemmas.lean
│ │ │ └── Matcher.lean
│ │ ├── String.lean
│ │ ├── UInt.lean
│ │ ├── UnionFind/
│ │ │ ├── Basic.lean
│ │ │ └── Lemmas.lean
│ │ ├── UnionFind.lean
│ │ ├── Vector/
│ │ │ ├── Basic.lean
│ │ │ ├── Lemmas.lean
│ │ │ └── Monadic.lean
│ │ └── Vector.lean
│ ├── Lean/
│ │ ├── AttributeExtra.lean
│ │ ├── EStateM.lean
│ │ ├── Except.lean
│ │ ├── Expr.lean
│ │ ├── Float.lean
│ │ ├── HashMap.lean
│ │ ├── HashSet.lean
│ │ ├── IO/
│ │ │ └── Process.lean
│ │ ├── Json.lean
│ │ ├── LawfulMonad.lean
│ │ ├── LawfulMonadLift.lean
│ │ ├── Meta/
│ │ │ ├── Basic.lean
│ │ │ ├── DiscrTree.lean
│ │ │ ├── Expr.lean
│ │ │ ├── Inaccessible.lean
│ │ │ ├── InstantiateMVars.lean
│ │ │ ├── SavedState.lean
│ │ │ ├── Simp.lean
│ │ │ └── UnusedNames.lean
│ │ ├── MonadBacktrack.lean
│ │ ├── NameMapAttribute.lean
│ │ ├── PersistentHashMap.lean
│ │ ├── PersistentHashSet.lean
│ │ ├── Position.lean
│ │ ├── SatisfiesM.lean
│ │ ├── Syntax.lean
│ │ ├── System/
│ │ │ └── IO.lean
│ │ ├── TagAttribute.lean
│ │ └── Util/
│ │ └── EnvSearch.lean
│ ├── Linter/
│ │ ├── UnnecessarySeqFocus.lean
│ │ └── UnreachableTactic.lean
│ ├── Linter.lean
│ ├── Logic.lean
│ ├── Tactic/
│ │ ├── Alias.lean
│ │ ├── Basic.lean
│ │ ├── Case.lean
│ │ ├── Congr.lean
│ │ ├── Exact.lean
│ │ ├── GeneralizeProofs.lean
│ │ ├── HelpCmd.lean
│ │ ├── Init.lean
│ │ ├── Instances.lean
│ │ ├── Lemma.lean
│ │ ├── Lint/
│ │ │ ├── Basic.lean
│ │ │ ├── Frontend.lean
│ │ │ ├── Misc.lean
│ │ │ ├── Simp.lean
│ │ │ └── TypeClass.lean
│ │ ├── Lint.lean
│ │ ├── NoMatch.lean
│ │ ├── OpenPrivate.lean
│ │ ├── PermuteGoals.lean
│ │ ├── PrintDependents.lean
│ │ ├── PrintOpaques.lean
│ │ ├── PrintPrefix.lean
│ │ ├── SeqFocus.lean
│ │ ├── ShowUnused.lean
│ │ ├── SqueezeScope.lean
│ │ ├── Trans.lean
│ │ └── Unreachable.lean
│ └── Util/
│ ├── Cache.lean
│ ├── ExtendedBinder.lean
│ ├── LibraryNote.lean
│ ├── Panic.lean
│ ├── Pickle.lean
│ └── ProofWanted.lean
├── Batteries.lean
├── BatteriesTest/
│ ├── ArrayMap.lean
│ ├── Char.lean
│ ├── GeneralizeProofs.lean
│ ├── Internal/
│ │ ├── DummyLabelAttr.lean
│ │ ├── DummyLibraryNote.lean
│ │ └── DummyLibraryNote2.lean
│ ├── MLList.lean
│ ├── OpenPrivateDefs.lean
│ ├── String.lean
│ ├── absurd.lean
│ ├── alias.lean
│ ├── array.lean
│ ├── array_scan.lean
│ ├── by_contra.lean
│ ├── case.lean
│ ├── congr.lean
│ ├── conv_equals.lean
│ ├── except.lean
│ ├── exfalso.lean
│ ├── float.lean
│ ├── help_cmd.lean
│ ├── import_lean.lean
│ ├── instances.lean
│ ├── isIndependentOf.lean
│ ├── kmp_matcher.lean
│ ├── lemma_cmd.lean
│ ├── library_note.lean
│ ├── lintTC.lean
│ ├── lintTrace.lean
│ ├── lint_coinductive.lean
│ ├── lint_docBlame.lean
│ ├── lint_docBlameThm.lean
│ ├── lint_dupNamespace.lean
│ ├── lint_lean.lean
│ ├── lint_simpNF.lean
│ ├── lint_simpNF_respectTransparency.lean
│ ├── lint_unreachableTactic.lean
│ ├── linterVisibility.lean
│ ├── lintsimp.lean
│ ├── lintunused.lean
│ ├── list_enumeration.lean
│ ├── list_sublists.lean
│ ├── mersenne_twister.lean
│ ├── nondet.lean
│ ├── norm_cast.lean
│ ├── omega/
│ │ └── benchmark.lean
│ ├── on_goal.lean
│ ├── openPrivate.lean
│ ├── print_opaques.lean
│ ├── print_prefix.lean
│ ├── proof_wanted.lean
│ ├── register_label_attr.lean
│ ├── rfl.lean
│ ├── satisfying.lean
│ ├── seq_focus.lean
│ ├── show_term.lean
│ ├── show_unused.lean
│ ├── simp_trace.lean
│ ├── simpa.lean
│ ├── solve_by_elim.lean
│ ├── trans.lean
│ ├── tryThis.lean
│ ├── vector.lean
│ └── where.lean
├── LICENSE
├── README.md
├── Shake/
│ └── Main.lean
├── bors.toml
├── docs/
│ └── lakefile.toml
├── lake-manifest.json
├── lakefile.toml
├── lean-toolchain
└── scripts/
├── check_imports.lean
├── create-adaptation-pr.sh
├── lintWhitespace.sh
├── merge-lean-testing-pr.sh
├── nolints.json
├── noshake.json
├── runLinter.lean
└── updateBatteries.sh
Condensed preview — 270 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,392K chars).
[
{
"path": ".docker/gitpod/Dockerfile",
"chars": 1887,
"preview": "# This is the Dockerfile for leanprover-community/batteries\n# This file is mostly copied from [mathlib4](https://github."
},
{
"path": ".github/workflows/build.yml",
"chars": 1601,
"preview": "on:\n push:\n branches-ignore:\n # ignore tmp branches used by bors\n - 'staging.tmp*'\n - 'trying.tmp*'\n "
},
{
"path": ".github/workflows/docs-deploy.yml",
"chars": 947,
"preview": "name: Deploy Docs\n\non:\n workflow_dispatch:\n schedule:\n - cron: '0 10 * * *' # daily (UTC 10:00)\n\npermissions:\n con"
},
{
"path": ".github/workflows/docs-release.yml",
"chars": 1199,
"preview": "name: Release Docs\n\non:\n push:\n tags:\n - \"v[0-9]+.[0-9]+.[0-9]+\"\n - \"v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+\"\n\nperm"
},
{
"path": ".github/workflows/labels-from-comments.yml",
"chars": 2246,
"preview": "# This workflow allows any user to add one of the `awaiting-review`, `awaiting-author`, or `WIP` labels,\n# by commenting"
},
{
"path": ".github/workflows/labels-from-status.yml",
"chars": 2068,
"preview": "# This workflow assigns `awaiting-review` or `WIP` labels to new PRs, and it removes\n# `awaiting-review`, `awaiting-auth"
},
{
"path": ".github/workflows/merge_conflicts.yml",
"chars": 865,
"preview": "name: Merge conflicts\n\non:\n schedule:\n - cron: '*/60 * * * *' # run every 60 minutes\n\njobs:\n main:\n if: github.r"
},
{
"path": ".github/workflows/nightly_bump_and_merge.yml",
"chars": 13777,
"preview": "name: Bump toolchain and merge pr-testing branches\n\n# This workflow combines the former `nightly_bump_toolchain.yml` and"
},
{
"path": ".github/workflows/nightly_detect_failure.yml",
"chars": 16561,
"preview": "name: Post to zulip if the nightly-testing branch is failing.\n\non:\n workflow_run:\n workflows: [\"ci\"]\n types:\n "
},
{
"path": ".github/workflows/nightly_merge_master.yml",
"chars": 1442,
"preview": "# This job merges every commit to `main` into `nightly-testing`, resolving merge conflicts in favor of `nightly-testing`"
},
{
"path": ".github/workflows/test_mathlib.yml",
"chars": 4779,
"preview": "# Test Mathlib against a Batteries PR\n\nname: Test Mathlib\n\non:\n workflow_run:\n workflows: [ci]\n types: [completed"
},
{
"path": ".gitignore",
"chars": 246,
"preview": "# Prior to v4.3.0-rc2 lake stored files in these locations.\n# We'll leave them in the `.gitignore` for a while for users"
},
{
"path": ".gitpod.yml",
"chars": 147,
"preview": "image:\n file: .docker/gitpod/Dockerfile\n\nvscode:\n extensions:\n - leanprover.lean4\n\ntasks:\n - init: |\n elan se"
},
{
"path": ".vscode/copyright.code-snippets",
"chars": 275,
"preview": "{\n\t\"Copyright header for batteries\": {\n\t\t\"scope\": \"lean4\",\n\t\t\"prefix\": \"copyright\",\n\t\t\"body\": [\n\t\t\t\"/-\",\n\t\t\t\"Copyright ("
},
{
"path": ".vscode/settings.json",
"chars": 271,
"preview": "{\n \"editor.insertSpaces\": true,\n \"editor.tabSize\": 2,\n \"editor.rulers\" : [100],\n \"files.encoding\": \"utf8\",\n \"files."
},
{
"path": "Batteries/Classes/Cast.lean",
"chars": 823,
"preview": "/-\nCopyright (c) 2014 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Classes/Deprecated.lean",
"chars": 17548,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Classes/Order.lean",
"chars": 12212,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Classes/RatCast.lean",
"chars": 919,
"preview": "/-\nCopyright (c) 2014 Robert Lewis. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Classes/SatisfiesM.lean",
"chars": 12705,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/CodeAction/Attr.lean",
"chars": 5823,
"preview": "/-\nCopyright (c) 2023 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/CodeAction/Basic.lean",
"chars": 3023,
"preview": "/-\nCopyright (c) 2023 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/CodeAction/Deprecated.lean",
"chars": 2641,
"preview": "/-\nCopyright (c) 2023 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/CodeAction/Match.lean",
"chars": 12605,
"preview": "/-\nCopyright (c) 2026 Moritz Roos. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICEN"
},
{
"path": "Batteries/CodeAction/Misc.lean",
"chars": 18040,
"preview": "/-\nCopyright (c) 2023 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/CodeAction.lean",
"chars": 170,
"preview": "module\n\npublic import Batteries.CodeAction.Attr\npublic import Batteries.CodeAction.Basic\npublic import Batteries.CodeAct"
},
{
"path": "Batteries/Control/AlternativeMonad.lean",
"chars": 8281,
"preview": "/-\nCopyright (c) 2025 Devon Tuma. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICENS"
},
{
"path": "Batteries/Control/ForInStep/Basic.lean",
"chars": 1263,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Control/ForInStep/Lemmas.lean",
"chars": 2133,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Control/ForInStep.lean",
"chars": 105,
"preview": "module\n\npublic import Batteries.Control.ForInStep.Basic\npublic import Batteries.Control.ForInStep.Lemmas\n"
},
{
"path": "Batteries/Control/LawfulMonadState.lean",
"chars": 9813,
"preview": "/-\nCopyright (c) 2025 Devon Tuma. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICENS"
},
{
"path": "Batteries/Control/Lemmas.lean",
"chars": 2376,
"preview": "/-\nCopyright (c) 2023 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Control/Monad.lean",
"chars": 423,
"preview": "/-\nCopyright (c) 2025 Lean FRO, LLC. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Control/Nondet/Basic.lean",
"chars": 7151,
"preview": "/-\nCopyright (c) 2023 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Control/OptionT.lean",
"chars": 1337,
"preview": "/-\nCopyright (c) 2017 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/Array/Basic.lean",
"chars": 15548,
"preview": "/-\nCopyright (c) 2021 Floris van Doorn. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Data/Array/Init/Lemmas.lean",
"chars": 372,
"preview": "/-\nCopyright (c) 2024 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Data/Array/Lemmas.lean",
"chars": 2257,
"preview": "/-\nCopyright (c) 2021 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/Array/Match.lean",
"chars": 6023,
"preview": "/-\nCopyright (c) 2023 F. G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Data/Array/Merge.lean",
"chars": 4124,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/Array/Monadic.lean",
"chars": 8367,
"preview": "/-\nCopyright (c) 2021 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/Array/Pairwise.lean",
"chars": 2422,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/Array/Scan.lean",
"chars": 13335,
"preview": "/-\nCopyright (c) 2026 Chad Sharp. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICENS"
},
{
"path": "Batteries/Data/Array.lean",
"chars": 347,
"preview": "module\n\npublic import Batteries.Data.Array.Basic\npublic import Batteries.Data.Array.Init.Lemmas\npublic import Batteries."
},
{
"path": "Batteries/Data/AssocList.lean",
"chars": 12149,
"preview": "/-\nCopyright (c) 2019 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/BinaryHeap/Basic.lean",
"chars": 6697,
"preview": "/-\nCopyright (c) 2021 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/BinaryHeap.lean",
"chars": 53,
"preview": "module\npublic import Batteries.Data.BinaryHeap.Basic\n"
},
{
"path": "Batteries/Data/BinomialHeap/Basic.lean",
"chars": 24040,
"preview": "/-\nCopyright (c) 2019 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/BinomialHeap/Lemmas.lean",
"chars": 1147,
"preview": "/-\nCopyright (c) 2023 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/BinomialHeap.lean",
"chars": 105,
"preview": "module\n\npublic import Batteries.Data.BinomialHeap.Basic\npublic import Batteries.Data.BinomialHeap.Lemmas\n"
},
{
"path": "Batteries/Data/BitVec/Basic.lean",
"chars": 763,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/BitVec/Lemmas.lean",
"chars": 4485,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/BitVec.lean",
"chars": 93,
"preview": "module\n\npublic import Batteries.Data.BitVec.Basic\npublic import Batteries.Data.BitVec.Lemmas\n"
},
{
"path": "Batteries/Data/Bool.lean",
"chars": 929,
"preview": "/-\nCopyright (c) 2026 Chad Sharp. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICENS"
},
{
"path": "Batteries/Data/ByteArray.lean",
"chars": 4906,
"preview": "/-\nCopyright (c) 2023 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/ByteSlice.lean",
"chars": 6963,
"preview": "\n/-\nCopyright (c) 2021 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file L"
},
{
"path": "Batteries/Data/Char/AsciiCasing.lean",
"chars": 7333,
"preview": "/-\nCopyright (c) 2025 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/Char/Basic.lean",
"chars": 6204,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/Char.lean",
"chars": 94,
"preview": "module\n\npublic import Batteries.Data.Char.AsciiCasing\npublic import Batteries.Data.Char.Basic\n"
},
{
"path": "Batteries/Data/DList/Basic.lean",
"chars": 2769,
"preview": "/-\nCopyright (c) 2018 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/DList/Lemmas.lean",
"chars": 1804,
"preview": "/-\nCopyright (c) 2017 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/DList.lean",
"chars": 91,
"preview": "module\n\npublic import Batteries.Data.DList.Basic\npublic import Batteries.Data.DList.Lemmas\n"
},
{
"path": "Batteries/Data/Fin/Basic.lean",
"chars": 5043,
"preview": "/-\nCopyright (c) 2017 Robert Y. Lewis. All rights reserved.\nReleased under Apache 2.0 license as described in the file L"
},
{
"path": "Batteries/Data/Fin/Fold.lean",
"chars": 6346,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/Fin/Lemmas.lean",
"chars": 17237,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/Fin/OfBits.lean",
"chars": 513,
"preview": "/-\nCopyright (c) 2025 François G. Dorais. All rights reserved.\nReleased under Apache 2. license as described in the file"
},
{
"path": "Batteries/Data/Fin.lean",
"chars": 165,
"preview": "module\n\npublic import Batteries.Data.Fin.Basic\npublic import Batteries.Data.Fin.Fold\npublic import Batteries.Data.Fin.Le"
},
{
"path": "Batteries/Data/FloatArray.lean",
"chars": 1223,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2. license as described in the file"
},
{
"path": "Batteries/Data/HashMap/Basic.lean",
"chars": 9921,
"preview": "/-\nCopyright (c) 2018 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/HashMap.lean",
"chars": 51,
"preview": "module\n\npublic import Batteries.Data.HashMap.Basic\n"
},
{
"path": "Batteries/Data/Int.lean",
"chars": 1970,
"preview": "/-\nCopyright (c) 2025 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/List/ArrayMap.lean",
"chars": 1496,
"preview": "/-\nCopyright (c) 2024 Michael Rothgang. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Data/List/Basic.lean",
"chars": 42379,
"preview": "/-\nCopyright (c) 2016 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/List/Count.lean",
"chars": 3945,
"preview": "/-\nCopyright (c) 2014 Parikshit Khanna. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Data/List/Init/Lemmas.lean",
"chars": 371,
"preview": "/-\nCopyright (c) 2024 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Data/List/Lemmas.lean",
"chars": 53170,
"preview": "/-\nCopyright (c) 2014 Parikshit Khanna. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Data/List/Matcher.lean",
"chars": 3044,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/List/Monadic.lean",
"chars": 1383,
"preview": "/-\nCopyright (c) 2024 Lean FRO, LLC. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Data/List/Pairwise.lean",
"chars": 4406,
"preview": "/-\nCopyright (c) 2018 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/List/Perm.lean",
"chars": 16498,
"preview": "/-\nCopyright (c) 2015 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/List/Scan.lean",
"chars": 8849,
"preview": "/-\nCopyright (c) 2014 Parikshit Khanna. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Data/List.lean",
"chars": 423,
"preview": "module\n\npublic import Batteries.Data.List.ArrayMap\npublic import Batteries.Data.List.Basic\npublic import Batteries.Data."
},
{
"path": "Batteries/Data/MLList/Basic.lean",
"chars": 18986,
"preview": "/-\nCopyright (c) 2018 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Data/MLList/Heartbeats.lean",
"chars": 1271,
"preview": "/-\nCopyright (c) 2023 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Data/MLList/IO.lean",
"chars": 624,
"preview": "/-\nCopyright (c) 2023 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Data/MLList.lean",
"chars": 136,
"preview": "module\n\npublic import Batteries.Data.MLList.Basic\npublic import Batteries.Data.MLList.Heartbeats\npublic import Batteries"
},
{
"path": "Batteries/Data/NameSet.lean",
"chars": 645,
"preview": "/-\nCopyright (c) 2023 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Data/Nat/Basic.lean",
"chars": 3489,
"preview": "/-\nCopyright (c) 2016 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/Nat/Bisect.lean",
"chars": 4603,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/Nat/Bitwise/Lemmas.lean",
"chars": 3782,
"preview": "/-\nCopyright (c) 2025 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/Nat/Bitwise.lean",
"chars": 56,
"preview": "module\n\npublic import Batteries.Data.Nat.Bitwise.Lemmas\n"
},
{
"path": "Batteries/Data/Nat/Gcd.lean",
"chars": 400,
"preview": "/-\nCopyright (c) 2014 Jeremy Avigad. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Data/Nat/Lemmas.lean",
"chars": 10264,
"preview": "/-\nCopyright (c) 2016 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/Nat.lean",
"chars": 253,
"preview": "module\n\npublic import Batteries.Data.Nat.Basic\npublic import Batteries.Data.Nat.Bisect\npublic import Batteries.Data.Nat."
},
{
"path": "Batteries/Data/PairingHeap.lean",
"chars": 14595,
"preview": "/-\nCopyright (c) 2022 Yuyang Zhao. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICEN"
},
{
"path": "Batteries/Data/RBMap/Alter.lean",
"chars": 16184,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/RBMap/Basic.lean",
"chars": 48278,
"preview": "/-\nCopyright (c) 2017 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/RBMap/Depth.lean",
"chars": 3087,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/RBMap/Lemmas.lean",
"chars": 56641,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/RBMap/WF.lean",
"chars": 26351,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/RBMap.lean",
"chars": 211,
"preview": "module\n\npublic import Batteries.Data.RBMap.Alter\npublic import Batteries.Data.RBMap.Basic\npublic import Batteries.Data.R"
},
{
"path": "Batteries/Data/Random/MersenneTwister.lean",
"chars": 5812,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/Random.lean",
"chars": 60,
"preview": "module\n\npublic import Batteries.Data.Random.MersenneTwister\n"
},
{
"path": "Batteries/Data/Range/Lemmas.lean",
"chars": 536,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/Range.lean",
"chars": 50,
"preview": "module\n\npublic import Batteries.Data.Range.Lemmas\n"
},
{
"path": "Batteries/Data/Rat/Float.lean",
"chars": 864,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/Rat.lean",
"chars": 47,
"preview": "module\n\npublic import Batteries.Data.Rat.Float\n"
},
{
"path": "Batteries/Data/RunningStats.lean",
"chars": 2055,
"preview": "/-\nCopyright (c) 2025 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/Stream.lean",
"chars": 2873,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2. license as described in the file"
},
{
"path": "Batteries/Data/String/AsciiCasing.lean",
"chars": 3172,
"preview": "/-\nCopyright (c) 2025 Christopher Bailey. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/String/Basic.lean",
"chars": 1320,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/String/Legacy.lean",
"chars": 18894,
"preview": "/-\nCopyright (c) 2016 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Data/String/Lemmas.lean",
"chars": 53857,
"preview": "/-\nCopyright (c) 2023 Bulhwi Cha. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICENS"
},
{
"path": "Batteries/Data/String/Matcher.lean",
"chars": 4314,
"preview": "/-\nCopyright (c) 2023 F. G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Data/String.lean",
"chars": 228,
"preview": "module\n\npublic import Batteries.Data.String.AsciiCasing\npublic import Batteries.Data.String.Basic\npublic import Batterie"
},
{
"path": "Batteries/Data/UInt.lean",
"chars": 7966,
"preview": "/-\nCopyright (c) 2023 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Data/UnionFind/Basic.lean",
"chars": 23893,
"preview": "/-\nCopyright (c) 2021 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/UnionFind/Lemmas.lean",
"chars": 6303,
"preview": "/-\nCopyright (c) 2021 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Data/UnionFind.lean",
"chars": 99,
"preview": "module\n\npublic import Batteries.Data.UnionFind.Basic\npublic import Batteries.Data.UnionFind.Lemmas\n"
},
{
"path": "Batteries/Data/Vector/Basic.lean",
"chars": 509,
"preview": "/-\nCopyright (c) 2024 Shreyas Srinivas. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Data/Vector/Lemmas.lean",
"chars": 3904,
"preview": "/-\nCopyright (c) 2024 Shreyas Srinivas. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Data/Vector/Monadic.lean",
"chars": 1456,
"preview": "/-\nCopyright (c) 2025 Lean FRO, LLC. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Data/Vector.lean",
"chars": 137,
"preview": "module\n\npublic import Batteries.Data.Vector.Basic\npublic import Batteries.Data.Vector.Lemmas\npublic import Batteries.Dat"
},
{
"path": "Batteries/Lean/AttributeExtra.lean",
"chars": 4535,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/EStateM.lean",
"chars": 6798,
"preview": "/-\nCopyright (c) 2024 Lean FRO, LLC. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Lean/Except.lean",
"chars": 1982,
"preview": "/-\nCopyright (c) 2023 Lean FRO, LLC. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Lean/Expr.lean",
"chars": 3665,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/Float.lean",
"chars": 3720,
"preview": "/-\n Copyright (c) 2023 Mario Carneiro. All rights reserved.\n Released under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Lean/HashMap.lean",
"chars": 1185,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/HashSet.lean",
"chars": 833,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/IO/Process.lean",
"chars": 1284,
"preview": "/-\nCopyright (c) 2023 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Lean/Json.lean",
"chars": 930,
"preview": "/-\n Copyright (c) 2022 E.W.Ayers. All rights reserved.\n Released under Apache 2.0 license as described in the file LICEN"
},
{
"path": "Batteries/Lean/LawfulMonad.lean",
"chars": 1499,
"preview": "/-\nCopyright (c) 2024 Lean FRO, LLC. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Lean/LawfulMonadLift.lean",
"chars": 2761,
"preview": "/-\nCopyright (c) 2025 Quang Dao. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICENSE"
},
{
"path": "Batteries/Lean/Meta/Basic.lean",
"chars": 5728,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/Meta/DiscrTree.lean",
"chars": 1749,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/Meta/Expr.lean",
"chars": 428,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/Meta/Inaccessible.lean",
"chars": 2276,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/Meta/InstantiateMVars.lean",
"chars": 2245,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/Meta/SavedState.lean",
"chars": 1622,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/Meta/Simp.lean",
"chars": 2290,
"preview": "/-\nCopyright (c) 2022 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Lean/Meta/UnusedNames.lean",
"chars": 4066,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/MonadBacktrack.lean",
"chars": 568,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/NameMapAttribute.lean",
"chars": 2530,
"preview": "/-\nCopyright (c) 2022 E.W.Ayers. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICENSE"
},
{
"path": "Batteries/Lean/PersistentHashMap.lean",
"chars": 2350,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/PersistentHashSet.lean",
"chars": 2197,
"preview": "/-\nCopyright (c) 2022 Jannis Limperg. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/Position.lean",
"chars": 4821,
"preview": "/-\nCopyright (c) 2023 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Lean/SatisfiesM.lean",
"chars": 1517,
"preview": "/-\nCopyright (c) 2024 Lean FRO, LLC. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Lean/Syntax.lean",
"chars": 582,
"preview": "/-\nCopyright (c) 2022 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Lean/System/IO.lean",
"chars": 913,
"preview": "/-\nCopyright (c) 2023 Kim Morrison. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Lean/TagAttribute.lean",
"chars": 677,
"preview": "/-\nCopyright (c) 2022 Gabriel Ebner. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Lean/Util/EnvSearch.lean",
"chars": 884,
"preview": "/-\nCopyright (c) 2021 Shing Tak Lam. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Linter/UnnecessarySeqFocus.lean",
"chars": 6635,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Linter/UnreachableTactic.lean",
"chars": 4887,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Linter.lean",
"chars": 118,
"preview": "module\n\npublic meta import Batteries.Linter.UnreachableTactic\npublic meta import Batteries.Linter.UnnecessarySeqFocus\n"
},
{
"path": "Batteries/Logic.lean",
"chars": 4182,
"preview": "/-\nCopyright (c) 2014 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Tactic/Alias.lean",
"chars": 8185,
"preview": "/-\nCopyright (c) 2017 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/Basic.lean",
"chars": 298,
"preview": "module\n\npublic meta import Lean.Elab.Tactic.ElabTerm\npublic meta import Batteries.Linter\npublic meta import Batteries.Ta"
},
{
"path": "Batteries/Tactic/Case.lean",
"chars": 8578,
"preview": "/-\nCopyright (c) 2023 Kyle Miller. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICEN"
},
{
"path": "Batteries/Tactic/Congr.lean",
"chars": 4153,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/Exact.lean",
"chars": 798,
"preview": "/-\nCopyright (c) 2023 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/GeneralizeProofs.lean",
"chars": 23219,
"preview": "/-\nCopyright (c) 2022 Alex J. Best. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICE"
},
{
"path": "Batteries/Tactic/HelpCmd.lean",
"chars": 14598,
"preview": "/-\nCopyright (c) 2024 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/Init.lean",
"chars": 4474,
"preview": "/-\nCopyright (c) 2021 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/Instances.lean",
"chars": 2780,
"preview": "/-\nCopyright (c) 2023 Kyle Miller. All rights reserved.\nReleased under Apache 2.0 license as described in the file LICEN"
},
{
"path": "Batteries/Tactic/Lemma.lean",
"chars": 2054,
"preview": "/-\nCopyright (c) 2024 Johan Commelin. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/Lint/Basic.lean",
"chars": 7069,
"preview": "/-\nCopyright (c) 2020 Floris van Doorn. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Tactic/Lint/Frontend.lean",
"chars": 14761,
"preview": "/-\nCopyright (c) 2020 Floris van Doorn. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Tactic/Lint/Misc.lean",
"chars": 13726,
"preview": "/-\nCopyright (c) 2020 Floris van Doorn. All rights reserved.\nReleased under Apache 2.0 license as described in the file "
},
{
"path": "Batteries/Tactic/Lint/Simp.lean",
"chars": 14515,
"preview": "/-\nCopyright (c) 2020 Gabriel Ebner. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Tactic/Lint/TypeClass.lean",
"chars": 1882,
"preview": "/-\nCopyright (c) 2022 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Tactic/Lint.lean",
"chars": 223,
"preview": "module\n\npublic import Batteries.Tactic.Lint.Basic\npublic import Batteries.Tactic.Lint.Misc\npublic import Batteries.Tacti"
},
{
"path": "Batteries/Tactic/NoMatch.lean",
"chars": 1616,
"preview": "/-\nCopyright (c) 2021 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/OpenPrivate.lean",
"chars": 6941,
"preview": "/-\nCopyright (c) 2021 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Tactic/PermuteGoals.lean",
"chars": 2593,
"preview": "/-\nCopyright (c) 2022 Arthur Paulino. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/PrintDependents.lean",
"chars": 5012,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/PrintOpaques.lean",
"chars": 3234,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/PrintPrefix.lean",
"chars": 4548,
"preview": "/-\nCopyright (c) 2021 Shing Tak Lam. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Tactic/SeqFocus.lean",
"chars": 1415,
"preview": "/-\nCopyright (c) 2022 Jeremy Avigad. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Tactic/ShowUnused.lean",
"chars": 3138,
"preview": "/-\nCopyright (c) 2024 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/SqueezeScope.lean",
"chars": 7498,
"preview": "/-\nCopyright (c) 2022 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Tactic/Trans.lean",
"chars": 8322,
"preview": "/-\nCopyright (c) 2022 Siddhartha Gadgil. All rights reserved.\nReleased under Apache 2.0 license as described in the file"
},
{
"path": "Batteries/Tactic/Unreachable.lean",
"chars": 1137,
"preview": "/-\nCopyright (c) 2021 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Util/Cache.lean",
"chars": 6847,
"preview": "/-\nCopyright (c) 2021 Gabriel Ebner. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Util/ExtendedBinder.lean",
"chars": 2184,
"preview": "/-\nCopyright (c) 2021 Microsoft Corporation. All rights reserved.\nReleased under Apache 2.0 license as described in the "
},
{
"path": "Batteries/Util/LibraryNote.lean",
"chars": 4026,
"preview": "/-\nCopyright (c) 2022 Gabriel Ebner. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries/Util/Panic.lean",
"chars": 399,
"preview": "/-\nCopyright (c) 2024 François G. Dorais. All rights reserved.\nReleased under Apache 2.0 license as described in the fil"
},
{
"path": "Batteries/Util/Pickle.lean",
"chars": 1597,
"preview": "/-\nCopyright (c) 2023 Mario Carneiro. All rights reserved.\nReleased under Apache 2.0 license as described in the file LI"
},
{
"path": "Batteries/Util/ProofWanted.lean",
"chars": 1593,
"preview": "/-\nCopyright (c) 2023 Lean FRO, LLC. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "Batteries.lean",
"chars": 4599,
"preview": "module\n\npublic import Batteries.Classes.Cast\npublic import Batteries.Classes.Deprecated\npublic import Batteries.Classes."
},
{
"path": "BatteriesTest/ArrayMap.lean",
"chars": 242,
"preview": "import Batteries.Data.List.ArrayMap\n\nopen List\n\n/-- info: #[3, 4, 5, 6] -/\n#guard_msgs in\n#eval List.toArrayMap [0, 1, 2"
},
{
"path": "BatteriesTest/Char.lean",
"chars": 940,
"preview": "import Batteries.Data.Char\n\n/- Failing on nightly-2025-12-18\n#guard Char.caseFoldAsciiOnly 'A' == 'a'\n#guard Char.caseFo"
},
{
"path": "BatteriesTest/GeneralizeProofs.lean",
"chars": 6407,
"preview": "import Batteries.Tactic.GeneralizeProofs\n\nprivate axiom test_sorry : ∀ {α}, α\nset_option autoImplicit true\nnoncomputable"
},
{
"path": "BatteriesTest/Internal/DummyLabelAttr.lean",
"chars": 497,
"preview": "/-\nCopyright (c) 2023 Lean FRO, LLC. All rights reserved.\nReleased under Apache 2.0 license as described in the file LIC"
},
{
"path": "BatteriesTest/Internal/DummyLibraryNote.lean",
"chars": 443,
"preview": "import Batteries.Util.LibraryNote\n\nlibrary_note «test1» /--\n1: This is a testnote for testing the library note feature o"
},
{
"path": "BatteriesTest/Internal/DummyLibraryNote2.lean",
"chars": 435,
"preview": "import BatteriesTest.Internal.DummyLibraryNote\n\nlibrary_note «test3» /--\n3: this is a note in a different file importing"
},
{
"path": "BatteriesTest/MLList.lean",
"chars": 1063,
"preview": "import Lean.Meta.Basic\nimport Batteries.Data.MLList.IO\nimport Batteries.Data.List.Basic\n\nset_option linter.missingDocs f"
},
{
"path": "BatteriesTest/OpenPrivateDefs.lean",
"chars": 120,
"preview": "/-!\nThis file contains a private declaration. It's tested in `openPrivate.lean`.\n-/\nprivate def secretNumber : Nat := 2\n"
},
{
"path": "BatteriesTest/String.lean",
"chars": 1467,
"preview": "/- Failing on nightly-2025-12-18\nimport Batteries.Data.String.AsciiCasing\n\n#guard \"ABC\".caseFoldAsciiOnly == \"abc\"\n#guar"
},
{
"path": "BatteriesTest/absurd.lean",
"chars": 1043,
"preview": "import Batteries.Tactic.Basic\n\n/-! Tests for `absurd` tactic -/\n\n-- Basic example\n/--\nerror: unsolved goals\np : Prop\nh :"
}
]
// ... and 70 more files (download for full content)
About this extraction
This page contains the full source code of the leanprover/std4 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 270 files (1.2 MB), approximately 437.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.