mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-18 10:54:09 +00:00
Compare commits
3 Commits
IntModule_
...
save
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8149b1aa2 | ||
|
|
00b0c6a758 | ||
|
|
f2669aff78 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,6 +1,3 @@
|
||||
*.lean text eol=lf
|
||||
*.expected.out -text
|
||||
RELEASES.md merge=union
|
||||
stage0/** binary linguist-generated
|
||||
# The following file is often manually edited, so do show it in diffs
|
||||
stage0/src/stdlib_flags.h -binary -linguist-generated
|
||||
|
||||
30
.github/ISSUE_TEMPLATE.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
### Prerequisites
|
||||
|
||||
* [ ] Put an X between the brackets on this line if you have done all of the following:
|
||||
* Checked that your issue isn't already [filed](https://github.com/leanprover/lean4/issues).
|
||||
* Reduced the issue to a self-contained, reproducible test case.
|
||||
|
||||
### Description
|
||||
|
||||
[Description of the issue]
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [and so on...]
|
||||
|
||||
**Expected behavior:** [What you expect to happen]
|
||||
|
||||
**Actual behavior:** [What actually happens]
|
||||
|
||||
**Reproduces how often:** [What percentage of the time does it reproduce?]
|
||||
|
||||
### Versions
|
||||
|
||||
You can get this information from copy and pasting the output of `lean --version`,
|
||||
please include the OS and what version of the OS you're running.
|
||||
|
||||
### Additional Information
|
||||
|
||||
Any additional information, configuration or data that might be necessary to reproduce the issue.
|
||||
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Please put an X between the brackets as you perform the following steps:
|
||||
|
||||
* [ ] Check that your issue is not already filed:
|
||||
https://github.com/leanprover/lean4/issues
|
||||
* [ ] Reduce the issue to a minimal, self-contained, reproducible test case.
|
||||
Avoid dependencies to Mathlib or Batteries.
|
||||
* [ ] Test your test case against the latest nightly release, for example on
|
||||
https://live.lean-lang.org/#project=lean-nightly
|
||||
(You can also use the settings there to switch to “Lean nightly”)
|
||||
|
||||
### Description
|
||||
|
||||
[Clear and concise description of the issue]
|
||||
|
||||
### Context
|
||||
|
||||
[Broader context that the issue occurred in. If there was any prior discussion on [the Lean Zulip](https://leanprover.zulipchat.com), link it here as well.]
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected behavior:** [Clear and concise description of what you expect to happen]
|
||||
|
||||
**Actual behavior:** [Clear and concise description of what actually happens]
|
||||
|
||||
### Versions
|
||||
|
||||
[Output of `#version` or `#eval Lean.versionString`]
|
||||
[OS version, if not using live.lean-lang.org.]
|
||||
|
||||
### Additional Information
|
||||
|
||||
[Additional information, configuration or data that might be necessary to reproduce the issue]
|
||||
|
||||
### Impact
|
||||
|
||||
Add :+1: to [issues you consider important](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc). If others are impacted by this issue, please ask them to add :+1: to it.
|
||||
26
.github/ISSUE_TEMPLATE/rfc.md
vendored
26
.github/ISSUE_TEMPLATE/rfc.md
vendored
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: Request for comments
|
||||
about: Create a feature proposal
|
||||
title: 'RFC: '
|
||||
labels: RFC
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Proposal
|
||||
|
||||
Clear and detailed description of the proposal. Consider the following questions:
|
||||
|
||||
- **User Experience**: How does this feature improve the user experience?
|
||||
|
||||
- **Beneficiaries**: Which Lean users and projects benefit most from this feature/change?
|
||||
|
||||
- **Maintainability**: Will this change streamline code maintenance or simplify its structure?
|
||||
|
||||
### Community Feedback
|
||||
|
||||
Ideas should be discussed on [the Lean Zulip](https://leanprover.zulipchat.com) prior to submitting a proposal. Summarize all prior discussions and link them here.
|
||||
|
||||
### Impact
|
||||
|
||||
Add :+1: to [issues you consider important](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc). If others benefit from the changes in this proposal being added, please ask them to add :+1: to it.
|
||||
21
.github/PULL_REQUEST_TEMPLATE.md
vendored
21
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,21 +0,0 @@
|
||||
# Read this section before submitting
|
||||
|
||||
* Ensure your PR follows the [External Contribution Guidelines](https://github.com/leanprover/lean4/blob/master/CONTRIBUTING.md).
|
||||
* Please make sure the PR has excellent documentation and tests. If we label it `missing documentation` or `missing tests` then it needs fixing!
|
||||
* Include the link to your `RFC` or `bug` issue in the description.
|
||||
* If the issue does not already have approval from a developer, submit the PR as draft.
|
||||
* The PR title/description will become the commit message. Keep it up-to-date as the PR evolves.
|
||||
* For `feat/fix` PRs, the first paragraph starting with "This PR" must be present and will become a
|
||||
changelog entry unless the PR is labeled with `no-changelog`. If the PR does not have this label,
|
||||
it must instead be categorized with one of the `changelog-*` labels (which will be done by a
|
||||
reviewer for external PRs).
|
||||
* A toolchain of the form `leanprover/lean4-pr-releases:pr-release-NNNN` for Linux and M-series Macs will be generated upon build. To generate binaries for Windows and Intel-based Macs as well, write a comment containing `release-ci` on its own line.
|
||||
* If you rebase your PR onto `nightly-with-mathlib` then CI will test Mathlib against your PR.
|
||||
* You can manage the `awaiting-review`, `awaiting-author`, and `WIP` labels yourself, by writing a comment containing one of these labels on its own line.
|
||||
* Remove this section, up to and including the `---` before submitting.
|
||||
|
||||
---
|
||||
|
||||
This PR <short changelog summary for feat/fix, see above>.
|
||||
|
||||
Closes <`RFC` or `bug` issue number fixed by this PR, if any>
|
||||
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
@@ -1,8 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "chore: CI"
|
||||
22
.github/workflows/actionlint.yml
vendored
22
.github/workflows/actionlint.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: Actionlint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
paths:
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/**'
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: actionlint
|
||||
uses: raven-actions/actionlint@v2
|
||||
with:
|
||||
pyflakes: false # we do not use python scripts
|
||||
38
.github/workflows/awaiting-mathlib.yml
vendored
38
.github/workflows/awaiting-mathlib.yml
vendored
@@ -1,38 +0,0 @@
|
||||
name: Check awaiting-mathlib label
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
check-awaiting-mathlib:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check awaiting-mathlib label
|
||||
id: check-awaiting-mathlib-label
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { labels, number: prNumber } = context.payload.pull_request;
|
||||
const hasAwaiting = labels.some(label => label.name == "awaiting-mathlib");
|
||||
const hasBreaks = labels.some(label => label.name == "breaks-mathlib");
|
||||
const hasBuilds = labels.some(label => label.name == "builds-mathlib");
|
||||
|
||||
if (hasAwaiting && hasBreaks) {
|
||||
core.setFailed('PR has both "awaiting-mathlib" and "breaks-mathlib" labels.');
|
||||
} else if (hasAwaiting && !hasBreaks && !hasBuilds) {
|
||||
core.info('PR is marked "awaiting-mathlib" but neither "breaks-mathlib" nor "builds-mathlib" labels are present.');
|
||||
core.setOutput('awaiting', 'true');
|
||||
}
|
||||
|
||||
- name: Wait for mathlib compatibility
|
||||
if: github.event_name == 'pull_request' && steps.check-awaiting-mathlib-label.outputs.awaiting == 'true'
|
||||
run: |
|
||||
echo "::notice title=Awaiting mathlib::PR is marked 'awaiting-mathlib' but neither 'breaks-mathlib' nor 'builds-mathlib' labels are present."
|
||||
echo "This check will remain in progress until the PR is updated with appropriate mathlib compatibility labels."
|
||||
# Keep the job running indefinitely to show "in progress" status
|
||||
while true; do
|
||||
sleep 3600 # Sleep for 1 hour at a time
|
||||
done
|
||||
26
.github/workflows/backport.yml
vendored
26
.github/workflows/backport.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Backport
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
name: Backport
|
||||
runs-on: ubuntu-latest
|
||||
# Only react to merged PRs for security reasons.
|
||||
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
|
||||
if: >
|
||||
github.event.pull_request.merged
|
||||
&& (
|
||||
github.event.action == 'closed'
|
||||
|| (
|
||||
github.event.action == 'labeled'
|
||||
&& contains(github.event.label.name, 'backport')
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: tibdex/backport@v2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
253
.github/workflows/build-template.yml
vendored
253
.github/workflows/build-template.yml
vendored
@@ -1,253 +0,0 @@
|
||||
# instantiated by ci.yml
|
||||
name: build-template
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
check-level:
|
||||
type: string
|
||||
required: true
|
||||
config:
|
||||
type: string
|
||||
required: true
|
||||
nightly:
|
||||
type: string
|
||||
required: true
|
||||
LEAN_VERSION_MAJOR:
|
||||
type: string
|
||||
required: true
|
||||
LEAN_VERSION_MINOR:
|
||||
type: string
|
||||
required: true
|
||||
LEAN_VERSION_PATCH:
|
||||
type: string
|
||||
required: true
|
||||
LEAN_SPECIAL_VERSION_DESC:
|
||||
type: string
|
||||
required: true
|
||||
RELEASE_TAG:
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name != 'schedule' || github.repository == 'leanprover/lean4'
|
||||
strategy:
|
||||
matrix:
|
||||
include: ${{fromJson(inputs.config)}}
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell || 'nix develop -c bash -euxo pipefail {0}' }}
|
||||
name: ${{ matrix.name }}
|
||||
env:
|
||||
# must be inside workspace
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPRESS: true
|
||||
# current cache limit
|
||||
CCACHE_MAXSIZE: 400M
|
||||
# squelch error message about missing nixpkgs channel
|
||||
NIX_BUILD_SHELL: bash
|
||||
LSAN_OPTIONS: max_leaks=10
|
||||
# somehow MinGW clang64 (or cmake?) defaults to `g++` even though it doesn't exist
|
||||
CXX: c++
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.15
|
||||
steps:
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
if: runner.os == 'Linux' && !matrix.cmultilib
|
||||
- name: Install MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: clang64
|
||||
# `:` means do not prefix with msystem
|
||||
pacboy: "make: python: cmake clang ccache gmp libuv git: zip: unzip: diffutils: binutils: tree: zstd tar:"
|
||||
if: runner.os == 'Windows'
|
||||
- name: Install Brew Packages
|
||||
run: |
|
||||
brew install ccache tree zstd coreutils gmp libuv
|
||||
if: runner.os == 'macOS'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# the default is to use a virtual merge commit between the PR and master: just use the PR
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Open Nix shell once
|
||||
run: true
|
||||
if: runner.os == 'Linux'
|
||||
# Do check out some CI-relevant files from virtual merge commit to accommodate CI changes on
|
||||
# master (as the workflow files themselves are always taken from the merge)
|
||||
# (needs to be after "Install *" to use the right shell)
|
||||
- name: CI Merge Checkout
|
||||
run: |
|
||||
git fetch --depth=1 origin ${{ github.sha }}
|
||||
git checkout FETCH_HEAD flake.nix flake.lock script/prepare-* tests/lean/run/importStructure.lean
|
||||
if: github.event_name == 'pull_request'
|
||||
# (needs to be after "Checkout" so files don't get overridden)
|
||||
- name: Setup emsdk
|
||||
uses: mymindstorm/setup-emsdk@v14
|
||||
with:
|
||||
version: 3.1.44
|
||||
actions-cache-folder: emsdk
|
||||
if: matrix.wasm
|
||||
- name: Install 32bit c libs
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-multilib g++-multilib ccache libuv1-dev:i386 pkgconf:i386
|
||||
if: matrix.cmultilib
|
||||
- name: Cache
|
||||
id: restore-cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
# NOTE: must be in sync with `save` below
|
||||
path: |
|
||||
.ccache
|
||||
${{ matrix.name == 'Linux Lake' && false && 'build/stage1/**/*.trace
|
||||
build/stage1/**/*.olean*
|
||||
build/stage1/**/*.ilean
|
||||
build/stage1/**/*.c
|
||||
build/stage1/**/*.c.o*' || '' }}
|
||||
key: ${{ matrix.name }}-build-v3-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-build-v3
|
||||
# open nix-shell once for initial setup
|
||||
- name: Setup
|
||||
run: |
|
||||
ccache --zero-stats
|
||||
if: runner.os == 'Linux'
|
||||
- name: Set up NPROC
|
||||
run: |
|
||||
echo "NPROC=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)" >> $GITHUB_ENV
|
||||
- name: Build
|
||||
run: |
|
||||
ulimit -c unlimited # coredumps
|
||||
[ -d build ] || mkdir build
|
||||
cd build
|
||||
# arguments passed to `cmake`
|
||||
OPTIONS=(-DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true)
|
||||
if [[ -n '${{ matrix.release }}' ]]; then
|
||||
# this also enables githash embedding into stage 1 library, which prohibits reusing
|
||||
# `.olean`s across commits, so we don't do it in the fast non-release CI
|
||||
OPTIONS+=(-DCHECK_OLEAN_VERSION=ON)
|
||||
fi
|
||||
if [[ -n '${{ matrix.cross_target }}' ]]; then
|
||||
# used by `prepare-llvm`
|
||||
export EXTRA_FLAGS=--target=${{ matrix.cross_target }}
|
||||
OPTIONS+=(-DLEAN_PLATFORM_TARGET=${{ matrix.cross_target }})
|
||||
fi
|
||||
if [[ -n '${{ matrix.prepare-llvm }}' ]]; then
|
||||
wget -q ${{ matrix.llvm-url }}
|
||||
PREPARE="$(${{ matrix.prepare-llvm }})"
|
||||
eval "OPTIONS+=($PREPARE)"
|
||||
fi
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ inputs.nightly }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ inputs.nightly }})
|
||||
fi
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ inputs.RELEASE_TAG }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_VERSION_MAJOR=${{ inputs.LEAN_VERSION_MAJOR }})
|
||||
OPTIONS+=(-DLEAN_VERSION_MINOR=${{ inputs.LEAN_VERSION_MINOR }})
|
||||
OPTIONS+=(-DLEAN_VERSION_PATCH=${{ inputs.LEAN_VERSION_PATCH }})
|
||||
OPTIONS+=(-DLEAN_VERSION_IS_RELEASE=1)
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ inputs.LEAN_SPECIAL_VERSION_DESC }})
|
||||
fi
|
||||
# contortion to support empty OPTIONS with old macOS bash
|
||||
cmake .. --preset ${{ matrix.CMAKE_PRESET || 'release' }} -B . ${{ matrix.CMAKE_OPTIONS }} ${OPTIONS[@]+"${OPTIONS[@]}"} -DLEAN_INSTALL_PREFIX=$PWD/..
|
||||
time make -j$NPROC
|
||||
- name: Install
|
||||
run: |
|
||||
make -C build install
|
||||
- name: Check Binaries
|
||||
run: ${{ matrix.binary-check }} lean-*/bin/* || true
|
||||
- name: Count binary symbols
|
||||
run: |
|
||||
for f in lean-*/bin/*; do
|
||||
echo "$f: $(nm $f | grep " T " | wc -l) exported symbols"
|
||||
done
|
||||
if: matrix.name == 'Windows'
|
||||
- name: List Install Tree
|
||||
run: |
|
||||
# omit contents of Init/, ...
|
||||
tree --du -h lean-*-* | grep -E ' (Init|Lean|Lake|LICENSE|[a-z])'
|
||||
- name: Pack
|
||||
run: |
|
||||
dir=$(echo lean-*-*)
|
||||
mkdir pack
|
||||
# high-compression tar.zst + zip for release, fast tar.zst otherwise
|
||||
if [[ '${{ startsWith(github.ref, 'refs/tags/') && matrix.release }}' == true || -n '${{ inputs.nightly }}' || -n '${{ inputs.RELEASE_TAG }}' ]]; then
|
||||
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -19 -o pack/$dir.tar.zst
|
||||
zip -rq pack/$dir.zip $dir
|
||||
else
|
||||
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -o pack/$dir.tar.zst
|
||||
fi
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: matrix.release
|
||||
with:
|
||||
name: build-${{ matrix.name }}
|
||||
path: pack/*
|
||||
- name: Lean stats
|
||||
run: |
|
||||
build/stage1/bin/lean --stats src/Lean.lean
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Test
|
||||
id: test
|
||||
run: |
|
||||
ulimit -c unlimited # coredumps
|
||||
time ctest --preset ${{ matrix.CMAKE_PRESET || 'release' }} --test-dir build/stage1 -j$NPROC --output-junit test-results.xml ${{ matrix.CTEST_OPTIONS }}
|
||||
if: (matrix.wasm || !matrix.cross) && (inputs.check-level >= 1 || matrix.test)
|
||||
- name: Test Summary
|
||||
uses: test-summary/action@v2
|
||||
with:
|
||||
paths: build/stage1/test-results.xml
|
||||
# prefix `if` above with `always` so it's run even if tests failed
|
||||
if: always() && steps.test.conclusion != 'skipped'
|
||||
- name: Check Test Binary
|
||||
run: ${{ matrix.binary-check }} tests/compiler/534.lean.out
|
||||
if: (!matrix.cross) && steps.test.conclusion != 'skipped'
|
||||
- name: Build Stage 2
|
||||
run: |
|
||||
make -C build -j$NPROC stage2
|
||||
if: matrix.test-speedcenter
|
||||
- name: Check Stage 3
|
||||
run: |
|
||||
make -C build -j$NPROC check-stage3
|
||||
if: matrix.check-stage3
|
||||
- name: Test Speedcenter Benchmarks
|
||||
run: |
|
||||
# Necessary for some timing metrics but does not work on Namespace runners
|
||||
# and we just want to test that the benchmarks run at all here
|
||||
#echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
|
||||
export BUILD=$PWD/build PATH=$PWD/build/stage1/bin:$PATH
|
||||
cd tests/bench
|
||||
nix shell .#temci -c temci exec --config speedcenter.yaml --included_blocks fast --runs 1
|
||||
if: matrix.test-speedcenter
|
||||
- name: Check rebootstrap
|
||||
run: |
|
||||
# clean rebuild in case of Makefile changes
|
||||
make -C build update-stage0 && rm -rf build/stage* && make -C build -j$NPROC
|
||||
if: matrix.check-rebootstrap
|
||||
- name: CCache stats
|
||||
if: always()
|
||||
run: ccache -s
|
||||
- name: Show stacktrace for coredumps
|
||||
if: failure() && runner.os == 'Linux'
|
||||
run: |
|
||||
for c in $(find . -name core); do
|
||||
progbin="$(file $c | sed "s/.*execfn: '\([^']*\)'.*/\1/")"
|
||||
echo bt | $GDB/bin/gdb -q $progbin $c || true
|
||||
done
|
||||
- name: Save Cache
|
||||
if: always() && steps.restore-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
# NOTE: must be in sync with `restore` above
|
||||
path: |
|
||||
.ccache
|
||||
${{ matrix.name == 'Linux Lake' && false && 'build/stage1/**/*.trace
|
||||
build/stage1/**/*.olean*
|
||||
build/stage1/**/*.ilean
|
||||
build/stage1/**/*.c
|
||||
build/stage1/**/*.c.o*' || '' }}
|
||||
key: ${{ steps.restore-cache.outputs.cache-primary-key }}
|
||||
29
.github/workflows/check-prelude.yml
vendored
29
.github/workflows/check-prelude.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: Check for modules that should use `prelude`
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
check-prelude:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# the default is to use a virtual merge commit between the PR and master: just use the PR
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
sparse-checkout: |
|
||||
src/Lean
|
||||
src/Std
|
||||
src/lake/Lake
|
||||
- name: Check Prelude
|
||||
run: |
|
||||
failed_files=""
|
||||
while IFS= read -r -d '' file; do
|
||||
if ! grep -q "^prelude$" "$file"; then
|
||||
failed_files="$failed_files$file\n"
|
||||
fi
|
||||
done < <(find src/Lean src/Std src/lake/Lake -name '*.lean' -print0)
|
||||
if [ -n "$failed_files" ]; then
|
||||
echo -e "The following files should use 'prelude':\n$failed_files"
|
||||
exit 1
|
||||
fi
|
||||
55
.github/workflows/check-stage0.yml
vendored
55
.github/workflows/check-stage0.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: Check for stage0 changes
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check-stage0-on-queue:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
filter: blob:none
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Find base commit
|
||||
if: github.event_name == 'pull_request'
|
||||
run: echo "BASE=$(git merge-base origin/${{ github.base_ref }} HEAD)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Identify stage0 changes
|
||||
run: |
|
||||
git diff "${BASE:-HEAD^}..HEAD" --name-only -- stage0/stdlib > "$RUNNER_TEMP/stage0" || true
|
||||
if test -s "$RUNNER_TEMP/stage0"
|
||||
then
|
||||
echo "CHANGES=yes" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "CHANGES=no" >> "$GITHUB_ENV"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- if: github.event_name == 'pull_request'
|
||||
name: Set label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo, number: issue_number } = context.issue;
|
||||
if (process.env.CHANGES == 'yes') {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['changes-stage0'] }).catch(() => {});
|
||||
} else {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'changes-stage0' }).catch(() => {});
|
||||
}
|
||||
|
||||
- if: env.CHANGES == 'yes'
|
||||
name: Report changes
|
||||
run: |
|
||||
echo "Found changes to stage0/, please do not merge using the merge queue." | tee "$GITHUB_STEP_SUMMARY"
|
||||
# shellcheck disable=SC2129
|
||||
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||
cat "$RUNNER_TEMP/stage0" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- if: github.event_name == 'merge_group' && env.CHANGES == 'yes'
|
||||
name: Fail when on the merge queue
|
||||
run: exit 1
|
||||
578
.github/workflows/ci.yml
vendored
578
.github/workflows/ci.yml
vendored
@@ -6,421 +6,285 @@ on:
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
merge_group:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: '0 7 * * *' # 8AM CET/11PM PT
|
||||
# for manual re-release of a nightly
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'Action'
|
||||
required: true
|
||||
default: 'release nightly'
|
||||
type: choice
|
||||
options:
|
||||
- release nightly
|
||||
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
# This job determines various settings for the following CI runs; see the `outputs` for details
|
||||
configure:
|
||||
set-nightly:
|
||||
# don't schedule nightlies on forks
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
# 0: PRs without special label
|
||||
# 1: PRs with `merge-ci` label, merge queue checks, master commits
|
||||
# 2: PRs with `release-ci` label, releases (incl. nightlies)
|
||||
check-level: ${{ steps.set-level.outputs.check-level }}
|
||||
# The build matrix, dynamically generated here
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
# secondary build jobs that should not block the CI success/merge queue
|
||||
matrix-secondary: ${{ steps.set-matrix.outputs.matrix-secondary }}
|
||||
# Should we make a nightly release? If so, this output contains the lean version string, else it is empty
|
||||
nightly: ${{ steps.set-nightly.outputs.nightly }}
|
||||
# Should this be the CI for a tagged release?
|
||||
# Yes only if a tag is pushed to the `leanprover` repository, and the tag is "v" followed by a valid semver.
|
||||
# It sets `set-release.outputs.RELEASE_TAG` to the tag
|
||||
# and sets `set-release.outputs.{LEAN_VERSION_MAJOR,LEAN_VERSION_MINOR,LEAN_VERSION_PATCH,LEAN_SPECIAL_VERSION_DESC}`
|
||||
# to the semver components parsed via regex.
|
||||
LEAN_VERSION_MAJOR: ${{ steps.set-release.outputs.LEAN_VERSION_MAJOR }}
|
||||
LEAN_VERSION_MINOR: ${{ steps.set-release.outputs.LEAN_VERSION_MINOR }}
|
||||
LEAN_VERSION_PATCH: ${{ steps.set-release.outputs.LEAN_VERSION_PATCH }}
|
||||
LEAN_SPECIAL_VERSION_DESC: ${{ steps.set-release.outputs.LEAN_SPECIAL_VERSION_DESC }}
|
||||
RELEASE_TAG: ${{ steps.set-release.outputs.RELEASE_TAG }}
|
||||
|
||||
nightly: ${{ steps.set.outputs.nightly }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
# don't schedule nightlies on forks
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly'
|
||||
uses: actions/checkout@v2
|
||||
- name: Set Nightly
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly'
|
||||
id: set-nightly
|
||||
id: set
|
||||
run: |
|
||||
if [[ -n '${{ secrets.PUSH_NIGHTLY_TOKEN }}' ]]; then
|
||||
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
|
||||
git fetch nightly --tags
|
||||
LEAN_VERSION_STRING="nightly-$(date -u +%F)"
|
||||
# do nothing if commit already has a different tag
|
||||
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
|
||||
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||
if [[ $(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo $LEAN_VERSION_STRING) == $LEAN_VERSION_STRING ]]; then
|
||||
echo "::set-output name=nightly::$LEAN_VERSION_STRING"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Check for official release
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'leanprover/lean4'
|
||||
id: set-release
|
||||
run: |
|
||||
TAG_NAME="${GITHUB_REF##*/}"
|
||||
|
||||
# From https://github.com/fsaintjacques/semver-tool/blob/master/src/semver
|
||||
|
||||
NAT='0|[1-9][0-9]*'
|
||||
ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*'
|
||||
IDENT="$NAT|$ALPHANUM"
|
||||
FIELD='[0-9A-Za-z-]+'
|
||||
|
||||
SEMVER_REGEX="\
|
||||
^[vV]?\
|
||||
($NAT)\\.($NAT)\\.($NAT)\
|
||||
(\\-(${IDENT})(\\.(${IDENT}))*)?\
|
||||
(\\+${FIELD}(\\.${FIELD})*)?$"
|
||||
|
||||
if [[ ${TAG_NAME} =~ ${SEMVER_REGEX} ]]; then
|
||||
echo "Tag ${TAG_NAME} matches SemVer regex, with groups ${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]}"
|
||||
{
|
||||
echo "LEAN_VERSION_MAJOR=${BASH_REMATCH[1]}"
|
||||
echo "LEAN_VERSION_MINOR=${BASH_REMATCH[2]}"
|
||||
echo "LEAN_VERSION_PATCH=${BASH_REMATCH[3]}"
|
||||
echo "LEAN_SPECIAL_VERSION_DESC=${BASH_REMATCH[4]##-}"
|
||||
echo "RELEASE_TAG=$TAG_NAME"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Tag ${TAG_NAME} did not match SemVer regex."
|
||||
fi
|
||||
|
||||
- name: Check for custom releases (e.g., not in the main lean repository)
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository != 'leanprover/lean4'
|
||||
id: set-release-custom
|
||||
run: |
|
||||
TAG_NAME="${GITHUB_REF##*/}"
|
||||
echo "RELEASE_TAG=$TAG_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set check level
|
||||
id: set-level
|
||||
# We do not use github.event.pull_request.labels.*.name here because
|
||||
# re-running a run does not update that list, and we do want to be able to
|
||||
# rerun the workflow run after setting the `release-ci`/`merge-ci` labels.
|
||||
run: |
|
||||
check_level=0
|
||||
|
||||
if [[ -n "${{ steps.set-nightly.outputs.nightly }}" || -n "${{ steps.set-release.outputs.RELEASE_TAG }}" || -n "${{ steps.set-release-custom.outputs.RELEASE_TAG }}" ]]; then
|
||||
check_level=2
|
||||
elif [[ "${{ github.event_name }}" != "pull_request" ]]; then
|
||||
check_level=1
|
||||
else
|
||||
labels="$(gh api repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }} --jq '.labels')"
|
||||
if echo "$labels" | grep -q "release-ci"; then
|
||||
check_level=2
|
||||
elif echo "$labels" | grep -q "merge-ci"; then
|
||||
check_level=1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "check-level=$check_level" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Configure build matrix
|
||||
id: set-matrix
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const level = ${{ steps.set-level.outputs.check-level }};
|
||||
console.log(`level: ${level}`);
|
||||
// use large runners where available (original repo)
|
||||
let large = ${{ github.repository == 'leanprover/lean4' }};
|
||||
const isPr = "${{ github.event_name }}" == "pull_request";
|
||||
let matrix = [
|
||||
/* TODO: to be updated to new LLVM
|
||||
{
|
||||
"name": "Linux LLVM",
|
||||
"os": "ubuntu-latest",
|
||||
"release": false,
|
||||
"check-level": 2,
|
||||
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
|
||||
"binary-check": "ldd -v",
|
||||
// foreign code may be linked against more recent glibc
|
||||
// reverse-ffi needs to be updated to link to LLVM libraries
|
||||
"CTEST_OPTIONS": "-E 'foreign|leanlaketest_reverse-ffi'",
|
||||
"CMAKE_OPTIONS": "-DLLVM=ON -DLLVM_CONFIG=${GITHUB_WORKSPACE}/build/llvm-host/bin/llvm-config"
|
||||
}, */
|
||||
{
|
||||
// portable release build: use channel with older glibc (2.26)
|
||||
"name": "Linux release",
|
||||
"os": large && level < 2 ? "nscloud-ubuntu-22.04-amd64-4x16" : "ubuntu-latest",
|
||||
"release": true,
|
||||
// Special handling for release jobs. We want:
|
||||
// 1. To run it in PRs so developrs get PR toolchains (so secondary is sufficient)
|
||||
// 2. To skip it in merge queues as it takes longer than the
|
||||
// Linux lake build and adds little value in the merge queue
|
||||
// 3. To run it in release (obviously)
|
||||
"check-level": isPr ? 0 : 2,
|
||||
"secondary": isPr,
|
||||
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
|
||||
"binary-check": "ldd -v",
|
||||
// foreign code may be linked against more recent glibc
|
||||
"CTEST_OPTIONS": "-E 'foreign'"
|
||||
},
|
||||
{
|
||||
"name": "Linux Lake",
|
||||
"os": large ? "nscloud-ubuntu-22.04-amd64-8x16" : "ubuntu-latest",
|
||||
"check-level": 0,
|
||||
"test": true,
|
||||
"check-rebootstrap": level >= 1,
|
||||
"check-stage3": level >= 2,
|
||||
// NOTE: `test-speedcenter` currently seems to be broken on `ubuntu-latest`
|
||||
"test-speedcenter": large && level >= 2,
|
||||
"CMAKE_OPTIONS": "-DUSE_LAKE=ON",
|
||||
},
|
||||
{
|
||||
"name": "Linux Reldebug",
|
||||
"os": "ubuntu-latest",
|
||||
"check-level": 2,
|
||||
"CMAKE_PRESET": "reldebug",
|
||||
// exclude seriously slow/stackoverflowing tests
|
||||
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest|bv_bitblast_stress|3807'"
|
||||
},
|
||||
// TODO: suddenly started failing in CI
|
||||
/*{
|
||||
"name": "Linux fsanitize",
|
||||
"os": "ubuntu-latest",
|
||||
"check-level": 2,
|
||||
// turn off custom allocator & symbolic functions to make LSAN do its magic
|
||||
"CMAKE_PRESET": "sanitize",
|
||||
// exclude seriously slow/problematic tests (laketests crash)
|
||||
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
|
||||
},*/
|
||||
{
|
||||
"name": "macOS",
|
||||
"os": "macos-13",
|
||||
"release": true,
|
||||
"check-level": 2,
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-apple-darwin.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
|
||||
},
|
||||
{
|
||||
"name": "macOS aarch64",
|
||||
// standard GH runner only comes with 7GB so use large runner if possible
|
||||
"os": large ? "nscloud-macos-sonoma-arm64-6x14" : "macos-14",
|
||||
"CMAKE_OPTIONS": "-DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
|
||||
"release": true,
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-aarch64-apple-darwin.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar", // https://github.com/actions/runner-images/issues/2619
|
||||
// See above for release job levels
|
||||
"check-level": isPr ? 0 : 2,
|
||||
"secondary": isPr,
|
||||
},
|
||||
{
|
||||
"name": "Windows",
|
||||
"os": "windows-2022",
|
||||
"release": true,
|
||||
"check-level": 2,
|
||||
"shell": "msys2 {0}",
|
||||
"CMAKE_OPTIONS": "-G \"Unix Makefiles\"",
|
||||
// for reasons unknown, interactivetests are flaky on Windows
|
||||
"CTEST_OPTIONS": "--repeat until-pass:2",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-w64-windows-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-mingw.sh lean-llvm*",
|
||||
"binary-check": "ldd"
|
||||
},
|
||||
{
|
||||
"name": "Linux aarch64",
|
||||
"os": "nscloud-ubuntu-22.04-arm64-4x16",
|
||||
"CMAKE_OPTIONS": "-DLEAN_INSTALL_SUFFIX=-linux_aarch64",
|
||||
"release": true,
|
||||
"check-level": 2,
|
||||
"shell": "nix develop .#oldGlibcAArch -c bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-aarch64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*"
|
||||
},
|
||||
// Started running out of memory building expensive modules, a 2GB heap is just not that much even before fragmentation
|
||||
//{
|
||||
// "name": "Linux 32bit",
|
||||
// "os": "ubuntu-latest",
|
||||
// // Use 32bit on stage0 and stage1 to keep oleans compatible
|
||||
// "CMAKE_OPTIONS": "-DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_MMAP=OFF -DUSE_GMP=OFF -DLEAN_EXTRA_CXX_FLAGS='-m32' -DLEANC_OPTS='-m32' -DMMAP=OFF -DLEAN_INSTALL_SUFFIX=-linux_x86 -DCMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/ -DSTAGE0_CMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/ -DPKG_CONFIG_EXECUTABLE=/usr/bin/i386-linux-gnu-pkg-config",
|
||||
// "cmultilib": true,
|
||||
// "release": true,
|
||||
// "check-level": 2,
|
||||
// "cross": true,
|
||||
// "shell": "bash -euxo pipefail {0}"
|
||||
//}
|
||||
// {
|
||||
// "name": "Web Assembly",
|
||||
// "os": "ubuntu-latest",
|
||||
// // Build a native 32bit binary in stage0 and use it to compile the oleans and the wasm build
|
||||
// "CMAKE_OPTIONS": "-DCMAKE_C_COMPILER_WORKS=1 -DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_CMAKE_CXX_COMPILER=clang++ -DSTAGE0_CMAKE_C_COMPILER=clang -DSTAGE0_CMAKE_EXECUTABLE_SUFFIX=\"\" -DUSE_GMP=OFF -DMMAP=OFF -DSTAGE0_MMAP=OFF -DCMAKE_AR=../emsdk/emsdk-main/upstream/emscripten/emar -DCMAKE_TOOLCHAIN_FILE=../emsdk/emsdk-main/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DLEAN_INSTALL_SUFFIX=-linux_wasm32 -DSTAGE0_CMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/",
|
||||
// "wasm": true,
|
||||
// "cmultilib": true,
|
||||
// "release": true,
|
||||
// "check-level": 2,
|
||||
// "cross": true,
|
||||
// "shell": "bash -euxo pipefail {0}",
|
||||
// // Just a few selected tests because wasm is slow
|
||||
// "CTEST_OPTIONS": "-R \"leantest_1007\\.lean|leantest_Format\\.lean|leanruntest\\_1037.lean|leanruntest_ac_rfl\\.lean|leanruntest_tempfile.lean\\.|leanruntest_libuv\\.lean\""
|
||||
// }
|
||||
];
|
||||
console.log(`matrix:\n${JSON.stringify(matrix, null, 2)}`);
|
||||
matrix = matrix.filter((job) => level >= job["check-level"]);
|
||||
core.setOutput('matrix', matrix.filter((job) => !job["secondary"]));
|
||||
core.setOutput('matrix-secondary', matrix.filter((job) => job["secondary"]));
|
||||
|
||||
build:
|
||||
if: github.event_name != 'schedule' || github.repository == 'leanprover/lean4'
|
||||
needs: [configure]
|
||||
uses: ./.github/workflows/build-template.yml
|
||||
with:
|
||||
config: ${{needs.configure.outputs.matrix}}
|
||||
check-level: ${{ needs.configure.outputs.check-level }}
|
||||
nightly: ${{ needs.configure.outputs.nightly }}
|
||||
LEAN_VERSION_MAJOR: ${{ needs.configure.outputs.LEAN_VERSION_MAJOR }}
|
||||
LEAN_VERSION_MINOR: ${{ needs.configure.outputs.LEAN_VERSION_MINOR }}
|
||||
LEAN_VERSION_PATCH: ${{ needs.configure.outputs.LEAN_VERSION_PATCH }}
|
||||
LEAN_SPECIAL_VERSION_DESC: ${{ needs.configure.outputs.LEAN_SPECIAL_VERSION_DESC }}
|
||||
RELEASE_TAG: ${{ needs.configure.outputs.RELEASE_TAG }}
|
||||
secrets: inherit
|
||||
|
||||
# build jobs that should not be considered by `all-done` below
|
||||
build-secondary:
|
||||
needs: [configure]
|
||||
if: needs.configure.outputs.matrix-secondary != '[]'
|
||||
uses: ./.github/workflows/build-template.yml
|
||||
with:
|
||||
config: ${{needs.configure.outputs.matrix-secondary}}
|
||||
check-level: ${{ needs.configure.outputs.check-level }}
|
||||
nightly: ${{ needs.configure.outputs.nightly }}
|
||||
LEAN_VERSION_MAJOR: ${{ needs.configure.outputs.LEAN_VERSION_MAJOR }}
|
||||
LEAN_VERSION_MINOR: ${{ needs.configure.outputs.LEAN_VERSION_MINOR }}
|
||||
LEAN_VERSION_PATCH: ${{ needs.configure.outputs.LEAN_VERSION_PATCH }}
|
||||
LEAN_SPECIAL_VERSION_DESC: ${{ needs.configure.outputs.LEAN_SPECIAL_VERSION_DESC }}
|
||||
RELEASE_TAG: ${{ needs.configure.outputs.RELEASE_TAG }}
|
||||
secrets: inherit
|
||||
|
||||
# This job collects results from all the matrix jobs
|
||||
# This can be made the "required" job, instead of listing each
|
||||
# matrix job separately
|
||||
all-done:
|
||||
name: Build matrix complete
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
# mark as merely cancelled not failed if builds are cancelled
|
||||
if: ${{ !cancelled() }}
|
||||
needs: set-nightly
|
||||
# `always` *must* be used to continue even after a dependency has been skipped
|
||||
if: always() && (github.event_name != 'schedule' || github.repository == 'leanprover/lean4')
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell || 'nix-shell --run "bash -euxo pipefail {0}"' }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# portable release build: use channel with older glibc (2.27)
|
||||
- name: Linux release
|
||||
os: ubuntu-latest
|
||||
release: true
|
||||
shell: nix-shell --arg pkgsDist "import (fetchTarball \"channel:nixos-19.03\") {{}}" --run "bash -euxo pipefail {0}"
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-linux-gnu.tar.zst
|
||||
prepare-llvm: ../script/prepare-llvm-linux.sh lean-llvm*
|
||||
binary-check: ldd -v
|
||||
# foreign code may be linked against more recent glibc
|
||||
CTEST_OPTIONS: -E 'foreign|leanlaketest_git'
|
||||
- name: Linux
|
||||
os: ubuntu-latest
|
||||
check-stage3: true
|
||||
test-speedcenter: true
|
||||
- name: Linux Debug
|
||||
os: ubuntu-latest
|
||||
CMAKE_OPTIONS: -DCMAKE_BUILD_TYPE=Debug
|
||||
- name: Linux fsanitize
|
||||
os: ubuntu-latest
|
||||
# turn off custom allocator & symbolic functions to make LSAN do its magic
|
||||
CMAKE_OPTIONS: -DLEAN_EXTRA_CXX_FLAGS=-fsanitize=address,undefined -DLEANC_EXTRA_FLAGS='-fsanitize=address,undefined -fsanitize-link-c++-runtime' -DSMALL_ALLOCATOR=OFF -DBSYMBOLIC=OFF
|
||||
# exclude problematic tests
|
||||
CTEST_OPTIONS: -E laketest
|
||||
- name: macOS
|
||||
os: macos-latest
|
||||
release: true
|
||||
shell: bash -euxo pipefail {0}
|
||||
CMAKE_OPTIONS: -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-apple-darwin.tar.zst
|
||||
prepare-llvm: ../script/prepare-llvm-macos.sh lean-llvm*
|
||||
binary-check: otool -L
|
||||
- name: macOS aarch64
|
||||
os: macos-latest
|
||||
release: true
|
||||
cross: true
|
||||
shell: bash -euxo pipefail {0}
|
||||
CMAKE_OPTIONS: -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-darwin_aarch64
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-aarch64-apple-darwin.tar.zst https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-apple-darwin.tar.zst
|
||||
prepare-llvm: EXTRA_FLAGS=--target=aarch64-apple-darwin ../script/prepare-llvm-macos.sh lean-llvm-aarch64-* lean-llvm-x86_64-*
|
||||
binary-check: otool -L
|
||||
- name: Windows
|
||||
os: windows-2022
|
||||
release: true
|
||||
shell: msys2 {0}
|
||||
CMAKE_OPTIONS: -G "Unix Makefiles"
|
||||
# for reasons unknown, interactivetests are flaky on Windows
|
||||
CTEST_OPTIONS: --repeat until-pass:2
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-w64-windows-gnu.tar.zst
|
||||
prepare-llvm: ../script/prepare-llvm-mingw.sh lean-llvm*
|
||||
binary-check: ldd
|
||||
- name: Linux aarch64
|
||||
os: ubuntu-latest
|
||||
CMAKE_OPTIONS: -DCMAKE_PREFIX_PATH=$GMP -DLEAN_INSTALL_SUFFIX=-linux_aarch64
|
||||
release: true
|
||||
cross: true
|
||||
shell: nix-shell --arg pkgsDist "import (fetchTarball \"channel:nixos-19.03\") {{ localSystem.config = \"aarch64-unknown-linux-gnu\"; }}" --run "bash -euxo pipefail {0}"
|
||||
llvm-url: https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-x86_64-linux-gnu.tar.zst https://github.com/leanprover/lean-llvm/releases/download/14.0.0/lean-llvm-aarch64-linux-gnu.tar.zst
|
||||
prepare-llvm: EXTRA_FLAGS=--target=aarch64-unknown-linux-gnu ../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
env:
|
||||
# must be inside workspace
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPRESS: true
|
||||
# current cache limit
|
||||
CCACHE_MAXSIZE: 200M
|
||||
# squelch error message about missing nixpkgs channel
|
||||
NIX_BUILD_SHELL: bash
|
||||
LSAN_OPTIONS: max_leaks=10
|
||||
# somehow MinGW clang64 (or cmake?) defaults to `g++` even though it doesn't exist
|
||||
CXX: c++
|
||||
steps:
|
||||
- if: ${{ contains(needs.*.result, 'failure') && github.repository == 'leanprover/lean4' && github.ref_name == 'master' }}
|
||||
uses: zulip/github-actions-zulip/send-message@v1
|
||||
with:
|
||||
api-key: ${{ secrets.ZULIP_BOT_KEY }}
|
||||
email: "github-actions-bot@lean-fro.zulipchat.com"
|
||||
organization-url: "https://lean-fro.zulipchat.com"
|
||||
to: "infrastructure"
|
||||
topic: "Github actions"
|
||||
type: "stream"
|
||||
content: |
|
||||
A build of `${{ github.ref_name }}`, triggered by event `${{ github.event_name }}`, [failed](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
||||
- if: contains(needs.*.result, 'failure')
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Some jobs failed')
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v15
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Install MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: clang64
|
||||
# `:p` means prefix with appropriate msystem prefix
|
||||
pacboy: "make python cmake:p clang:p ccache:p gmp:p git zip unzip diffutils binutils tree zstd:p tar"
|
||||
if: matrix.os == 'windows-2022'
|
||||
- name: Install Brew Packages
|
||||
run: |
|
||||
brew install ccache tree zstd coreutils
|
||||
if: matrix.os == 'macos-latest'
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ matrix.name }}-build-v3-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-build-v3
|
||||
- name: Setup
|
||||
run: |
|
||||
# open nix-shell once for initial setup
|
||||
true
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
OPTIONS=()
|
||||
if [[ -n '${{ matrix.prepare-llvm }}' ]]; then
|
||||
wget -q ${{ matrix.llvm-url }}
|
||||
PREPARE="$(${{ matrix.prepare-llvm }})"
|
||||
eval "OPTIONS+=($PREPARE)"
|
||||
fi
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ needs.set-nightly.outputs.nightly }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ needs.set-nightly.outputs.nightly }})
|
||||
fi
|
||||
# contortion to support empty OPTIONS with old macOS bash
|
||||
cmake .. ${{ matrix.CMAKE_OPTIONS }} ${OPTIONS[@]+"${OPTIONS[@]}"} -DLEAN_INSTALL_PREFIX=$PWD/..
|
||||
make -j4
|
||||
make install
|
||||
- name: Check Binaries
|
||||
run: ${{ matrix.binary-check }} lean-*/bin/* || true
|
||||
- name: List Install Tree
|
||||
run: |
|
||||
# omit contents of Init/, ...
|
||||
tree --du -h lean-* | grep -E ' (Init|Std|Lean|Lake|LICENSE|[a-z])'
|
||||
- name: Pack
|
||||
run: |
|
||||
dir=$(echo lean-*)
|
||||
mkdir pack
|
||||
# high-compression tar.zst + zip for release, fast tar.zst otherwise
|
||||
if [[ '${{ startsWith(github.ref, 'refs/tags/v') && matrix.release }}' == true || -n '${{ needs.set-nightly.outputs.nightly }}' ]]; then
|
||||
tar cf - $dir | zstd -T0 --no-progress -19 -o pack/$dir.tar.zst
|
||||
zip -rq pack/$dir.zip $dir
|
||||
else
|
||||
tar cf - $dir | zstd -T0 --no-progress -o pack/$dir.tar.zst
|
||||
fi
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: matrix.release
|
||||
with:
|
||||
name: build-${{ matrix.name }}
|
||||
path: pack/*
|
||||
- name: Trigger TPIL build
|
||||
run: |
|
||||
curl -u '${{ github.repository_owner }}:${{ secrets.PUSH_NIGHTLY_TOKEN }}' 'https://api.github.com/repos/leanprover/theorem_proving_in_lean4/dispatches' -X POST -d '{ "event_type": "build_event" }'
|
||||
if: matrix.name == 'Linux release' && github.event_name == 'push' && github.repository == 'leanprover/lean4'
|
||||
- name: Lean stats
|
||||
run: |
|
||||
build/stage1/bin/lean --stats src/Lean.lean
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/stage1
|
||||
# exclude nonreproducible test
|
||||
ctest -j4 --output-on-failure -E leanlaketest_git ${{ matrix.CTEST_OPTIONS }} < /dev/null
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Check Test Binary
|
||||
run: ${{ matrix.binary-check }} tests/compiler/534.lean.out
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Build Stage 2
|
||||
run: |
|
||||
cd build
|
||||
make -j4 stage2
|
||||
if: matrix.build-stage2 || matrix.check-stage3
|
||||
- name: Check Stage 3
|
||||
run: |
|
||||
cd build
|
||||
make -j4 check-stage3
|
||||
if: matrix.check-stage3
|
||||
- name: Test Speedcenter Benchmarks
|
||||
run: |
|
||||
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
|
||||
export BUILD=$PWD/build PATH=$PWD/build/stage1/bin:$PATH
|
||||
cd tests/bench
|
||||
nix shell .#temci -c temci exec --config speedcenter.yaml --included_blocks fast --runs 1
|
||||
if: matrix.test-speedcenter
|
||||
- name: Check rebootstrap
|
||||
run: |
|
||||
cd build
|
||||
make update-stage0 && make -j4
|
||||
if: matrix.name == 'Linux'
|
||||
- name: CCache stats
|
||||
run: ccache -s
|
||||
|
||||
|
||||
# This job creates releases from tags
|
||||
# (whether they are "unofficial" releases for experiments, or official releases when the tag is "v" followed by a semver string.)
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
# When GitHub says "If a job fails, all jobs that need it are skipped unless
|
||||
# the jobs use a conditional expression that causes the job to continue.", don't believe
|
||||
# their lies. It's actually the entire closure (i.e. including `set-nightly`) that
|
||||
# must succeed for subsequent to be run without `always()`.
|
||||
if: always() && needs.build.result == 'success' && startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') || contains(github.ref, '-rc') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Update release.lean-lang.org
|
||||
run: |
|
||||
gh workflow -R leanprover/release-index run update-index.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_INDEX_TOKEN }}
|
||||
|
||||
# This job creates nightly releases during the cron job.
|
||||
# It is responsible for creating the tag, and automatically generating a changelog.
|
||||
release-nightly:
|
||||
needs: [configure, build]
|
||||
if: needs.configure.outputs.nightly
|
||||
needs: [set-nightly, build]
|
||||
if: needs.set-nightly.outputs.nightly
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# needed for tagging
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PUSH_NIGHTLY_TOKEN }}
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Prepare Nightly Release
|
||||
run: |
|
||||
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
|
||||
git fetch nightly --tags
|
||||
git tag "${{ needs.configure.outputs.nightly }}"
|
||||
git push nightly "${{ needs.configure.outputs.nightly }}"
|
||||
git push -f origin refs/tags/${{ needs.configure.outputs.nightly }}:refs/heads/nightly
|
||||
last_tag="$(git log HEAD^ --simplify-by-decoration --pretty="format:%d" | grep -o "nightly-[-0-9]*" | head -n 1)"
|
||||
git tag ${{ needs.set-nightly.outputs.nightly }}
|
||||
git push nightly ${{ needs.set-nightly.outputs.nightly }}
|
||||
last_tag=$(git describe HEAD^ --abbrev=0 --tags)
|
||||
echo -e "*Changes since ${last_tag}:*\n\n" > diff.md
|
||||
git show "$last_tag":RELEASES.md > old.md
|
||||
git show $last_tag:RELEASES.md > old.md
|
||||
#./script/diff_changelogs.py old.md doc/changes.md >> diff.md
|
||||
diff --changed-group-format='%>' --unchanged-group-format='' old.md RELEASES.md >> diff.md || true
|
||||
echo -e "\n*Full commit log*\n" >> diff.md
|
||||
git log --oneline "$last_tag"..HEAD | sed 's/^/* /' >> diff.md
|
||||
git log --oneline $last_tag..HEAD | sed 's/^/* /' >> diff.md
|
||||
- name: Release Nightly
|
||||
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body_path: diff.md
|
||||
prerelease: true
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
tag_name: ${{ needs.configure.outputs.nightly }}
|
||||
tag_name: ${{ needs.set-nightly.outputs.nightly }}
|
||||
repository: ${{ github.repository_owner }}/lean4-nightly
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PUSH_NIGHTLY_TOKEN }}
|
||||
- name: Update release.lean-lang.org
|
||||
run: |
|
||||
gh workflow -R leanprover/release-index run update-index.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_INDEX_TOKEN }}
|
||||
- name: Update toolchain on mathlib4's nightly-testing branch
|
||||
run: |
|
||||
gh workflow -R leanprover-community/mathlib4 run nightly_bump_toolchain.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.MATHLIB4_BOT }}
|
||||
|
||||
20
.github/workflows/copyright-header.yml
vendored
20
.github/workflows/copyright-header.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Check for copyright header
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
check-lean-files:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Verify .lean files start with a copyright header.
|
||||
run: |
|
||||
FILES=$(find ./src -type d \( -path "./src/lake/examples" -o -path "./src/lake/tests" \) -prune -o -type f -name "*.lean" -exec perl -ne 'BEGIN { $/ = undef; } print "$ARGV\n" if !m{\A/-\nCopyright}; exit;' {} \;)
|
||||
if [ -n "$FILES" ]; then
|
||||
echo "Found .lean files which do not have a copyright header:"
|
||||
echo "$FILES"
|
||||
exit 1
|
||||
else
|
||||
echo "All copyright headers present."
|
||||
fi
|
||||
34
.github/workflows/jira.yml
vendored
34
.github/workflows/jira.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Jira sync
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
jira-sync:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Move Jira issue to Done
|
||||
env:
|
||||
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
|
||||
JIRA_USERNAME: ${{ secrets.JIRA_USERNAME }}
|
||||
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
||||
run: |
|
||||
issue_number=${{ github.event.issue.number }}
|
||||
|
||||
jira_issue_key=$(curl -s -u "${JIRA_USERNAME}:${JIRA_API_TOKEN}" \
|
||||
-X GET -H "Content-Type: application/json" \
|
||||
"${JIRA_BASE_URL}/rest/api/2/search?jql=summary~\"${issue_number}\"" | \
|
||||
jq -r '.issues[0].key')
|
||||
|
||||
if [ -z "$jira_issue_key" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
curl -s -u "${JIRA_USERNAME}:${JIRA_API_TOKEN}" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
--data "{\"transition\": {\"id\": \"41\"}}" \
|
||||
"${JIRA_BASE_URL}/rest/api/2/issue/${jira_issue_key}/transitions"
|
||||
|
||||
echo "Moved Jira issue ${jira_issue_key} to Done"
|
||||
67
.github/workflows/labels-from-comments.yml
vendored
67
.github/workflows/labels-from-comments.yml
vendored
@@ -1,67 +0,0 @@
|
||||
# This workflow allows any user to add one of the `awaiting-review`, `awaiting-author`, `WIP`,
|
||||
# `release-ci`, or a `changelog-XXX` label by commenting on the PR or issue.
|
||||
# If any labels from the set {`awaiting-review`, `awaiting-author`, `WIP`} are added, other labels
|
||||
# from that set are removed automatically at the same time.
|
||||
# Similarly, if any `changelog-XXX` label is added, other `changelog-YYY` labels are removed.
|
||||
|
||||
name: Label PR based on Comment
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
update-label:
|
||||
if: github.event.issue.pull_request != null && (contains(github.event.comment.body, 'awaiting-review') || contains(github.event.comment.body, 'awaiting-author') || contains(github.event.comment.body, 'WIP') || contains(github.event.comment.body, 'release-ci') || contains(github.event.comment.body, 'changelog-'))
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Add label based on comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo, number: issue_number } = context.issue;
|
||||
const commentLines = context.payload.comment.body.split('\r\n');
|
||||
|
||||
const awaitingReview = commentLines.includes('awaiting-review');
|
||||
const awaitingAuthor = commentLines.includes('awaiting-author');
|
||||
const wip = commentLines.includes('WIP');
|
||||
const releaseCI = commentLines.includes('release-ci');
|
||||
const changelogMatch = commentLines.find(line => line.startsWith('changelog-'));
|
||||
|
||||
if (awaitingReview || awaitingAuthor || wip) {
|
||||
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(() => {});
|
||||
}
|
||||
|
||||
if (awaitingReview) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['awaiting-review'] });
|
||||
}
|
||||
if (awaitingAuthor) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['awaiting-author'] });
|
||||
}
|
||||
if (wip) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['WIP'] });
|
||||
}
|
||||
|
||||
if (releaseCI) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['release-ci'] });
|
||||
}
|
||||
|
||||
if (changelogMatch) {
|
||||
const changelogLabel = changelogMatch.trim();
|
||||
const { data: existingLabels } = await github.rest.issues.listLabelsOnIssue({ owner, repo, issue_number });
|
||||
const changelogLabels = existingLabels.filter(label => label.name.startsWith('changelog-'));
|
||||
|
||||
// Remove all other changelog labels
|
||||
for (const label of changelogLabels) {
|
||||
if (label.name !== changelogLabel) {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: label.name }).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new changelog label
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [changelogLabel] });
|
||||
}
|
||||
108
.github/workflows/nix-ci.yml
vendored
Normal file
108
.github/workflows/nix-ci.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
name: Nix CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: nix -v --experimental-features "nix-command flakes" run .#ciShell -- bash -euxo pipefail {0}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: Nix Linux
|
||||
os: ubuntu-latest
|
||||
#- name: Nix macOS
|
||||
# os: macos-latest
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
env:
|
||||
NIX_BUILD_ARGS: -v --print-build-logs --fallback
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v15
|
||||
with:
|
||||
# https://github.com/NixOS/nix/issues/6572
|
||||
install_url: https://releases.nixos.org/nix/nix-2.7.0/install
|
||||
extra_nix_config: |
|
||||
extra-sandbox-paths = /nix/var/cache/ccache
|
||||
substituters = file://${{ github.workspace }}/nix-store-cache-copy?priority=10&trusted=true https://cache.nixos.org
|
||||
- name: Set Up Nix Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: nix-store-cache
|
||||
key: ${{ matrix.name }}-nix-store-cache-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-nix-store-cache
|
||||
- name: Further Set Up Nix Cache
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
# Nix seems to mutate the cache, so make a copy
|
||||
cp -r nix-store-cache nix-store-cache-copy || true
|
||||
- name: Prepare CCache Cache
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
sudo mkdir -m0770 -p /nix/var/cache/ccache
|
||||
sudo chown -R $USER /nix/var/cache/ccache
|
||||
- name: Setup CCache Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /nix/var/cache/ccache
|
||||
key: ${{ matrix.name }}-nix-ccache-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-nix-ccache
|
||||
- name: Further Set Up CCache Cache
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
sudo chown -R root:nixbld /nix/var/cache
|
||||
sudo chmod -R 770 /nix/var/cache
|
||||
- name: Install Cachix
|
||||
uses: cachix/cachix-action@v10
|
||||
with:
|
||||
name: lean4
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
skipPush: true # we push specific outputs only
|
||||
- name: Build
|
||||
run: |
|
||||
# .o files are not a runtime dependency on macOS because of lack of thin archives
|
||||
nix build $NIX_BUILD_ARGS .#stage0 .#stage1.lean-all .#Lean.oTree .#iTree .#modDepsFiles -o push-build
|
||||
- name: Test
|
||||
run: |
|
||||
nix build $NIX_BUILD_ARGS .#test -o push-test
|
||||
- name: Build manual
|
||||
run: |
|
||||
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc#{lean-mdbook,leanInk,alectryon,test} -o push-doc
|
||||
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc
|
||||
if: matrix.name == 'Nix Linux'
|
||||
- name: Push to Cachix
|
||||
run: |
|
||||
[ -z "${{ secrets.CACHIX_AUTH_TOKEN }}" ] || cachix push -j4 lean4 ./push-* || true
|
||||
- name: Rebuild Nix Store Cache
|
||||
run: |
|
||||
rm -rf nix-store-cache || true
|
||||
nix copy ./push-* --to file://$PWD/nix-store-cache?compression=none
|
||||
- name: Publish manual
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./result
|
||||
destination_dir: ./doc
|
||||
if: matrix.name == 'Nix Linux' && github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||
- name: Fixup CCache Cache
|
||||
run: |
|
||||
sudo chown -R $USER /nix/var/cache/ccache
|
||||
- name: CCache stats
|
||||
run: CCACHE_DIR=/nix/var/cache/ccache nix run .#nixpkgs.ccache -- -s
|
||||
25
.github/workflows/pr-body.yml
vendored
25
.github/workflows/pr-body.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Check PR body for changelog convention
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited, labeled, converted_to_draft, ready_for_review]
|
||||
|
||||
jobs:
|
||||
check-pr-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR body
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { title, body, labels, draft } = context.payload.pull_request;
|
||||
if (!draft && /^(feat|fix):/.test(title) && !labels.some(label => label.name == "changelog-no")) {
|
||||
if (!labels.some(label => label.name.startsWith("changelog-"))) {
|
||||
core.setFailed('feat/fix PR must have a `changelog-*` label');
|
||||
}
|
||||
if (!/^This PR [^<]/.test(body)) {
|
||||
core.setFailed('feat/fix PR must have changelog summary starting with "This PR ..." as first line.');
|
||||
}
|
||||
}
|
||||
413
.github/workflows/pr-release.yml
vendored
413
.github/workflows/pr-release.yml
vendored
@@ -1,413 +0,0 @@
|
||||
# Push a release to the lean4-pr-releases repository, whenever someone pushes to a PR branch.
|
||||
|
||||
# This needs to run with the `secrets.PR_RELEASES_TOKEN` token available,
|
||||
# but PR branches will generally come from forks,
|
||||
# so it is not possible to run this using the `pull_request` or `pull_request_target` workflows.
|
||||
# Instead we use `workflow_run`, which essentially allows us to escalate privileges
|
||||
# (but only runs the CI as described in the `master` branch, not in the PR branch).
|
||||
|
||||
# The main specification/documentation for this workflow is at
|
||||
# https://leanprover-community.github.io/contribute/tags_and_branches.html
|
||||
# Keep that in sync!
|
||||
|
||||
name: PR release
|
||||
|
||||
on:
|
||||
workflow_run: # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#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/lean4'
|
||||
steps:
|
||||
- name: Retrieve information about the original workflow
|
||||
uses: potiuk/get-workflow-origin@v1_1 # https://github.com/marketplace/actions/get-workflow-origin
|
||||
# This action is deprecated and archived, but it seems hard to find a better solution for getting the PR number
|
||||
# see https://github.com/orgs/community/discussions/25220 for some discussion
|
||||
id: workflow-info
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sourceRunId: ${{ github.event.workflow_run.id }}
|
||||
|
||||
- name: Download artifact from the previous workflow.
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: download-artifact
|
||||
uses: dawidd6/action-download-artifact@v10 # https://github.com/marketplace/actions/download-workflow-artifact
|
||||
with:
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: artifacts
|
||||
name: build-.*
|
||||
name_is_regexp: true
|
||||
|
||||
- name: Push tag
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
git init --bare lean4.git
|
||||
git -C lean4.git remote add origin https://github.com/${{ github.repository_owner }}/lean4.git
|
||||
git -C lean4.git fetch -n origin master
|
||||
git -C lean4.git fetch -n origin "${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
|
||||
# Create both the original tag and the SHA-suffixed tag
|
||||
SHORT_SHA="${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
SHORT_SHA="${SHORT_SHA:0:7}"
|
||||
|
||||
# Export the short SHA for use in subsequent steps
|
||||
echo "SHORT_SHA=${SHORT_SHA}" >> "$GITHUB_ENV"
|
||||
|
||||
git -C lean4.git tag -f pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} "${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
git -C lean4.git tag -f pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-"${SHORT_SHA}" "${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
|
||||
git -C lean4.git remote add pr-releases https://foo:'${{ secrets.PR_RELEASES_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-pr-releases.git
|
||||
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-"${SHORT_SHA}"
|
||||
- name: Delete existing release if present
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
# Try to delete any existing release for the current PR (just the version without the SHA suffix).
|
||||
gh release delete --repo ${{ github.repository_owner }}/lean4-pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} -y || true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
- name: Release (short format)
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||
with:
|
||||
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# There are coredumps files here as well, but all in deeper subdirectories.
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
draft: false
|
||||
tag_name: pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
repository: ${{ github.repository_owner }}/lean4-pr-releases
|
||||
env:
|
||||
# The token used here must have `workflow` privileges.
|
||||
GITHUB_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
|
||||
- name: Release (SHA-suffixed format)
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631
|
||||
with:
|
||||
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }} (${{ steps.workflow-info.outputs.sourceHeadSha }})
|
||||
# There are coredumps files here as well, but all in deeper subdirectories.
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
draft: false
|
||||
tag_name: pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}
|
||||
repository: ${{ github.repository_owner }}/lean4-pr-releases
|
||||
env:
|
||||
# The token used here must have `workflow` privileges.
|
||||
GITHUB_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
|
||||
- name: Report release status (short format)
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: "${{ steps.workflow-info.outputs.sourceHeadSha }}",
|
||||
state: "success",
|
||||
context: "PR toolchain",
|
||||
description: "${{ github.repository_owner }}/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}",
|
||||
});
|
||||
|
||||
- name: Report release status (SHA-suffixed format)
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: "${{ steps.workflow-info.outputs.sourceHeadSha }}",
|
||||
state: "success",
|
||||
context: "PR toolchain (SHA-suffixed)",
|
||||
description: "${{ github.repository_owner }}/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}",
|
||||
});
|
||||
|
||||
- name: Add label
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addLabels({
|
||||
issue_number: ${{ steps.workflow-info.outputs.pullRequestNumber }},
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['toolchain-available']
|
||||
})
|
||||
|
||||
# Next, determine the most recent nightly release in this PR's history.
|
||||
- name: Find most recent nightly in feature branch
|
||||
id: most-recent-nightly-tag
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
git -C lean4.git remote add nightly https://github.com/leanprover/lean4-nightly.git
|
||||
git -C lean4.git fetch nightly '+refs/tags/nightly-*:refs/tags/nightly-*'
|
||||
git -C lean4.git tag --merged "${{ steps.workflow-info.outputs.sourceHeadSha }}" --list "nightly-*" \
|
||||
| sort -rV | head -n 1 | sed "s/^nightly-*/MOST_RECENT_NIGHTLY=/" | tee -a "$GITHUB_ENV"
|
||||
|
||||
- name: 'Setup jq'
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: dcarbone/install-jq-action@v3.1.1
|
||||
|
||||
# Check that the most recently nightly coincides with 'git merge-base HEAD master'
|
||||
- name: Check merge-base and nightly-testing-YYYY-MM-DD
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: ready
|
||||
run: |
|
||||
echo "Most recent nightly release in your branch: $MOST_RECENT_NIGHTLY"
|
||||
NIGHTLY_SHA=$(git -C lean4.git rev-parse "nightly-$MOST_RECENT_NIGHTLY^{commit}")
|
||||
echo "SHA of most recent nightly release: $NIGHTLY_SHA"
|
||||
MERGE_BASE_SHA=$(git -C lean4.git merge-base origin/master "${{ steps.workflow-info.outputs.sourceHeadSha }}")
|
||||
echo "SHA of merge-base: $MERGE_BASE_SHA"
|
||||
if [ "$NIGHTLY_SHA" = "$MERGE_BASE_SHA" ]; then
|
||||
echo "The merge base of this PR coincides with the nightly release"
|
||||
|
||||
BATTERIES_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover-community/batteries.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
|
||||
MATHLIB_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover-community/mathlib4.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
|
||||
|
||||
if [[ -n "$BATTERIES_REMOTE_TAGS" ]]; then
|
||||
echo "... and Batteries has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE=""
|
||||
|
||||
if [[ -n "$MATHLIB_REMOTE_TAGS" ]]; then
|
||||
echo "... and Mathlib has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
else
|
||||
echo "... but Mathlib does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE="- ❗ Mathlib CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Mathlib CI should run now."
|
||||
fi
|
||||
else
|
||||
echo "... but Batteries does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE="- ❗ Batteries CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Batteries CI should run now."
|
||||
fi
|
||||
|
||||
else
|
||||
echo "The most recently nightly tag on this branch has SHA: $NIGHTLY_SHA"
|
||||
echo "but 'git merge-base origin/master HEAD' reported: $MERGE_BASE_SHA"
|
||||
git -C lean4.git log -10 origin/master
|
||||
|
||||
git -C lean4.git fetch origin nightly-with-mathlib
|
||||
NIGHTLY_WITH_MATHLIB_SHA="$(git -C lean4.git rev-parse "origin/nightly-with-mathlib")"
|
||||
MESSAGE="- ❗ Batteries/Mathlib CI will not be attempted unless your PR branches off the \`nightly-with-mathlib\` branch. Try \`git rebase $MERGE_BASE_SHA --onto $NIGHTLY_WITH_MATHLIB_SHA\`."
|
||||
fi
|
||||
|
||||
if [[ -n "$MESSAGE" ]]; then
|
||||
# Check if force-mathlib-ci label is present
|
||||
LABELS="$(curl --retry 3 --location --silent \
|
||||
-H "Authorization: token ${{ secrets.MATHLIB4_COMMENT_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/labels" \
|
||||
| jq -r '.[].name')"
|
||||
|
||||
if echo "$LABELS" | grep -q "^force-mathlib-ci$"; then
|
||||
echo "force-mathlib-ci label detected, forcing CI despite issues"
|
||||
MESSAGE="Forcing Mathlib CI because the \`force-mathlib-ci\` label is present, despite problem: $MESSAGE"
|
||||
FORCE_CI=true
|
||||
else
|
||||
MESSAGE="$MESSAGE You can force Mathlib CI using the \`force-mathlib-ci\` label."
|
||||
fi
|
||||
|
||||
echo "Checking existing messages"
|
||||
|
||||
# The code for updating comments is duplicated in mathlib's
|
||||
# scripts/lean-pr-testing-comments.sh
|
||||
# so keep in sync
|
||||
|
||||
# Use GitHub API to check if a comment already exists
|
||||
existing_comment="$(curl --retry 3 --location --silent \
|
||||
-H "Authorization: token ${{ secrets.MATHLIB4_COMMENT_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments" \
|
||||
| jq 'first(.[] | select(.body | test("^- . Mathlib") or startswith("Mathlib CI status")) | select(.user.login == "leanprover-community-bot"))')"
|
||||
existing_comment_id="$(echo "$existing_comment" | jq -r .id)"
|
||||
existing_comment_body="$(echo "$existing_comment" | jq -r .body)"
|
||||
|
||||
if [[ "$existing_comment_body" != *"$MESSAGE"* ]]; then
|
||||
MESSAGE="$MESSAGE ($(date "+%Y-%m-%d %H:%M:%S"))"
|
||||
|
||||
echo "Posting message to the comments: $MESSAGE"
|
||||
|
||||
# Append new result to the existing comment or post a new comment
|
||||
# It's essential we use the MATHLIB4_COMMENT_BOT token here, so that Mathlib CI can subsequently edit the comment.
|
||||
if [ -z "$existing_comment_id" ]; then
|
||||
INTRO="Mathlib CI status ([docs](https://leanprover-community.github.io/contribute/tags_and_branches.html)):"
|
||||
# Post new comment with a bullet point
|
||||
echo "Posting as new comment at leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
curl -L -s \
|
||||
-X POST \
|
||||
-H "Authorization: token ${{ secrets.MATHLIB4_COMMENT_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-d "$(jq --null-input --arg intro "$INTRO" --arg val "$MESSAGE" '{"body":($intro + "\n" + $val)}')" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
else
|
||||
# Append new result to the existing comment
|
||||
echo "Appending to existing comment at leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
curl -L -s \
|
||||
-X PATCH \
|
||||
-H "Authorization: token ${{ secrets.MATHLIB4_COMMENT_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-d "$(jq --null-input --arg existing "$existing_comment_body" --arg message "$MESSAGE" '{"body":($existing + "\n" + $message)}')" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/comments/$existing_comment_id"
|
||||
fi
|
||||
else
|
||||
echo "The message already exists in the comment body."
|
||||
fi
|
||||
|
||||
if [[ "$FORCE_CI" == "true" ]]; then
|
||||
echo "mathlib_ready=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "mathlib_ready=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
echo "mathlib_ready=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Report mathlib base
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const description =
|
||||
process.env.MOST_RECENT_NIGHTLY ?
|
||||
"nightly-" + process.env.MOST_RECENT_NIGHTLY :
|
||||
"not branched off nightly";
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: "${{ steps.workflow-info.outputs.sourceHeadSha }}",
|
||||
state: "success",
|
||||
context: "PR branched off:",
|
||||
description: description,
|
||||
});
|
||||
|
||||
# We next automatically create a Batteries branch using this toolchain.
|
||||
# Batteries doesn't itself have a mechanism to report results of CI from this branch back to Lean
|
||||
# Instead this is taken care of by Mathlib CI, which will fail if Batteries fails.
|
||||
- name: Cleanup workspace
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
sudo rm -rf ./*
|
||||
|
||||
# Checkout the Batteries repository with all branches
|
||||
- name: Checkout Batteries repository
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: leanprover-community/batteries
|
||||
token: ${{ secrets.MATHLIB4_BOT }}
|
||||
ref: nightly-testing
|
||||
fetch-depth: 0 # This ensures we check out all tags and branches.
|
||||
|
||||
- name: Check if tag exists
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
id: check_batteries_tag
|
||||
run: |
|
||||
git config user.name "leanprover-community-mathlib4-bot"
|
||||
git config user.email "leanprover-community-mathlib4-bot@users.noreply.github.com"
|
||||
|
||||
if git ls-remote --heads --tags --exit-code origin "nightly-testing-${MOST_RECENT_NIGHTLY}" >/dev/null; then
|
||||
BASE="nightly-testing-${MOST_RECENT_NIGHTLY}"
|
||||
else
|
||||
echo "Couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' tag at Batteries. Falling back to 'nightly-testing'."
|
||||
BASE=nightly-testing
|
||||
fi
|
||||
|
||||
echo "Using base branch: $BASE"
|
||||
|
||||
EXISTS="$(git ls-remote --heads origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} | wc -l)"
|
||||
echo "Branch exists: $EXISTS"
|
||||
if [ "$EXISTS" = "0" ]; then
|
||||
echo "Branch does not exist, creating it."
|
||||
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
else
|
||||
echo "Branch already exists, updating lean-toolchain."
|
||||
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# The Batteries `nightly-testing` or `nightly-testing-YYYY-MM-DD` branch may have moved since this branch was created, so merge their changes.
|
||||
# (This should no longer be possible once `nightly-testing-YYYY-MM-DD` is a tag, but it is still safe to merge.)
|
||||
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
git commit -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
fi
|
||||
|
||||
- name: Push changes
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
git push origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
|
||||
|
||||
# We next automatically create a Mathlib branch using this toolchain.
|
||||
# Mathlib CI will be responsible for reporting back success or failure
|
||||
# to the PR comments asynchronously.
|
||||
- name: Cleanup workspace
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
sudo rm -rf ./*
|
||||
|
||||
# Checkout the mathlib4 repository with all branches
|
||||
- name: Checkout mathlib4 repository
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: leanprover-community/mathlib4
|
||||
token: ${{ secrets.MATHLIB4_BOT }}
|
||||
ref: nightly-testing
|
||||
fetch-depth: 0 # This ensures we check out all tags and branches.
|
||||
|
||||
- name: install elan
|
||||
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 tag exists
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
id: check_mathlib_tag
|
||||
run: |
|
||||
git config user.name "leanprover-community-mathlib4-bot"
|
||||
git config user.email "leanprover-community-mathlib4-bot@users.noreply.github.com"
|
||||
|
||||
if git ls-remote --heads --tags --exit-code origin "nightly-testing-${MOST_RECENT_NIGHTLY}" >/dev/null; then
|
||||
BASE="nightly-testing-${MOST_RECENT_NIGHTLY}"
|
||||
else
|
||||
echo "Couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' branch at Mathlib. Falling back to 'nightly-testing'."
|
||||
BASE=nightly-testing
|
||||
fi
|
||||
|
||||
echo "Using base tag: $BASE"
|
||||
|
||||
EXISTS="$(git ls-remote --heads origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} | wc -l)"
|
||||
echo "Branch exists: $EXISTS"
|
||||
if [ "$EXISTS" = "0" ]; then
|
||||
echo "Branch does not exist, creating it."
|
||||
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
sed -i 's,require "leanprover-community" / "batteries" @ git ".\+",require "leanprover-community" / "batteries" @ git "lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}",' lakefile.lean
|
||||
lake update batteries
|
||||
git add lakefile.lean lake-manifest.json
|
||||
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
else
|
||||
echo "Branch already exists, updating lean-toolchain and bumping Batteries."
|
||||
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# The Mathlib `nightly-testing` branch or `nightly-testing-YYYY-MM-DD` tag may have moved since this branch was created, so merge their changes.
|
||||
# (This should no longer be possible once `nightly-testing-YYYY-MM-DD` is a tag, but it is still safe to merge.)
|
||||
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
lake update batteries
|
||||
git add lake-manifest.json
|
||||
git commit -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
fi
|
||||
|
||||
- name: Push changes
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
git push origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
20
.github/workflows/pr-title.yml
vendored
20
.github/workflows/pr-title.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Check PR title for commit convention
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
jobs:
|
||||
check-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR title
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const msg = context.payload.pull_request? context.payload.pull_request.title : context.payload.merge_group.head_commit.message;
|
||||
console.log(`Message: ${msg}`)
|
||||
if (!/^(feat|fix|doc|style|refactor|test|chore|perf): .*[^.]($|\n\n)/.test(msg)) {
|
||||
core.setFailed('PR title does not follow the Commit Convention (https://leanprover.github.io/lean4/doc/dev/commit_convention.html).');
|
||||
}
|
||||
31
.github/workflows/pr.yml
vendored
Normal file
31
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: sanity-check opened PRs
|
||||
|
||||
on:
|
||||
# needs read/write GH token, do *not* execute arbitrary code from PR
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
check-pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Commit Message
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { data: commits } = await github.pulls.listCommits({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
console.log(commits[0].commit.message);
|
||||
// check first commit only (and only once) since later commits might be intended to be squashed away
|
||||
if (!/^(feat|fix|doc|style|refactor|test|chore|perf): .*[^.]($|\n\n)/.test(commits[0].commit.message)) {
|
||||
await github.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: 'Thanks for your contribution! Please make sure to follow our [Commit Convention](https://leanprover.github.io/lean4/doc/dev/commit_convention.html).',
|
||||
});
|
||||
}
|
||||
35
.github/workflows/restart-on-label.yml
vendored
35
.github/workflows/restart-on-label.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Restart by label
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- unlabeled
|
||||
- labeled
|
||||
jobs:
|
||||
restart-on-label:
|
||||
runs-on: ubuntu-latest
|
||||
if: contains(github.event.label.name, 'merge-ci') || contains(github.event.label.name, 'release-ci')
|
||||
steps:
|
||||
- run: |
|
||||
# Finding latest CI workflow run on current pull request
|
||||
# (unfortunately cannot search by PR number, only base branch,
|
||||
# and that is't even unique given PRs from forks, but the risk
|
||||
# of confusion is low and the danger is mild)
|
||||
echo "Trying to find a run with branch $head_ref and commit $head_sha"
|
||||
run_id="$(gh run list -e pull_request -b "$head_ref" -c "$head_sha" \
|
||||
--workflow 'CI' --limit 1 --json databaseId --jq '.[0].databaseId')"
|
||||
echo "Run id: ${run_id}"
|
||||
gh run view "$run_id"
|
||||
echo "Cancelling (just in case)"
|
||||
gh run cancel "$run_id" || echo "(failed)"
|
||||
echo "Waiting for 30s"
|
||||
sleep 30
|
||||
gh run view "$run_id"
|
||||
echo "Rerunning"
|
||||
gh run rerun "$run_id"
|
||||
gh run view "$run_id"
|
||||
shell: bash
|
||||
env:
|
||||
head_ref: ${{ github.head_ref }}
|
||||
head_sha: ${{ github.event.pull_request.head.sha }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
20
.github/workflows/stale.yml
vendored
20
.github/workflows/stale.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: 'Label stale PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-stale: -1
|
||||
days-before-pr-stale: 30
|
||||
days-before-close: -1
|
||||
stale-pr-label: 'stale'
|
||||
only-labels: 'awaiting-author'
|
||||
68
.github/workflows/update-stage0.yml
vendored
68
.github/workflows/update-stage0.yml
vendored
@@ -1,68 +0,0 @@
|
||||
name: Update stage0
|
||||
|
||||
# This action will update stage0 on master as soon as
|
||||
# src/stdlib_flags.h and stage0/src/stdlib_flags.h
|
||||
# are out of sync there, or when manually triggered.
|
||||
# The update bypasses the merge queue to be quick.
|
||||
# Also see <doc/dev/bootstrap.md>.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: stage0
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
update-stage0:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# This action should push to an otherwise protected branch, so it
|
||||
# uses a deploy key with write permissions, as suggested at
|
||||
# https://stackoverflow.com/a/76135647/946226
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ssh-key: ${{secrets.STAGE0_SSH_KEY}}
|
||||
- run: echo "should_update_stage0=yes" >> "$GITHUB_ENV"
|
||||
- name: Check if automatic update is needed
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
if diff -u src/stdlib_flags.h stage0/src/stdlib_flags.h
|
||||
then
|
||||
echo "src/stdlib_flags.h and stage0/src/stdlib_flags.h agree, nothing to do"
|
||||
echo "should_update_stage0=no" >> "$GITHUB_ENV"
|
||||
fi
|
||||
- name: Setup git user
|
||||
if: env.should_update_stage0 == 'yes'
|
||||
run: |
|
||||
git config --global user.name "Lean stage0 autoupdater"
|
||||
git config --global user.email "<>"
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
- name: Open Nix shell once
|
||||
if: env.should_update_stage0 == 'yes'
|
||||
run: true
|
||||
shell: 'nix develop -c bash -euxo pipefail {0}'
|
||||
- name: Set up NPROC
|
||||
if: env.should_update_stage0 == 'yes'
|
||||
run: |
|
||||
echo "NPROC=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)" >> $GITHUB_ENV
|
||||
shell: 'nix develop -c bash -euxo pipefail {0}'
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
run: cmake --preset release
|
||||
shell: 'nix develop -c bash -euxo pipefail {0}'
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
run: make -j$NPROC -C build/release update-stage0-commit
|
||||
shell: 'nix develop -c bash -euxo pipefail {0}'
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
run: git show --stat
|
||||
- if: env.should_update_stage0 == 'yes' && github.event_name == 'push'
|
||||
name: Sanity check # to avoid loops
|
||||
run: |
|
||||
diff -u src/stdlib_flags.h stage0/src/stdlib_flags.h || exit 1
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
run: git push origin
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -2,11 +2,7 @@
|
||||
\#*
|
||||
.#*
|
||||
*.lock
|
||||
.lake
|
||||
lake-manifest.json
|
||||
/build
|
||||
/src/lakefile.toml
|
||||
/lakefile.toml
|
||||
build
|
||||
GPATH
|
||||
GRTAGS
|
||||
GSYMS
|
||||
@@ -21,7 +17,6 @@ settings.json
|
||||
.gdb_history
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
script/__pycache__
|
||||
*.produced.out
|
||||
CMakeSettings.json
|
||||
CppProperties.json
|
||||
@@ -30,4 +25,4 @@ fwIn.txt
|
||||
fwOut.txt
|
||||
wdErr.txt
|
||||
wdIn.txt
|
||||
wdOut.txt
|
||||
wdOut.txt
|
||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "lake"]
|
||||
path = src/lake
|
||||
url = https://github.com/leanprover/lake.git
|
||||
ignore = untracked
|
||||
14
.gitpod.Dockerfile
vendored
14
.gitpod.Dockerfile
vendored
@@ -1,14 +0,0 @@
|
||||
# You can find the new timestamped tags here: https://hub.docker.com/r/gitpod/workspace-full/tags
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
USER root
|
||||
RUN apt-get update && apt-get install git libgmp-dev libuv1-dev cmake ccache clang -y && apt-get clean
|
||||
|
||||
USER gitpod
|
||||
|
||||
# Install and configure elan
|
||||
RUN curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain none
|
||||
ENV PATH="/home/gitpod/.elan/bin:${PATH}"
|
||||
# Create a dummy toolchain so that we can pre-register it with elan
|
||||
RUN mkdir -p /workspace/lean4/build/release/stage1/bin && touch /workspace/lean4/build/release/stage1/bin/lean && elan toolchain link lean4 /workspace/lean4/build/release/stage1
|
||||
RUN mkdir -p /workspace/lean4/build/release/stage0/bin && touch /workspace/lean4/build/release/stage0/bin/lean && elan toolchain link lean4-stage0 /workspace/lean4/build/release/stage0
|
||||
11
.gitpod.yml
11
.gitpod.yml
@@ -1,11 +0,0 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- leanprover.lean4
|
||||
|
||||
tasks:
|
||||
- name: Release build
|
||||
init: cmake --preset release
|
||||
command: make -C build/release -j$(nproc || sysctl -n hw.logicalcpu)
|
||||
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"[markdown]": {
|
||||
"rewrap.wrappingColumn": 70
|
||||
}
|
||||
}
|
||||
119
CMakeLists.txt
119
CMakeLists.txt
@@ -1,30 +1,20 @@
|
||||
cmake_minimum_required(VERSION 3.11)
|
||||
|
||||
option(USE_MIMALLOC "use mimalloc" ON)
|
||||
|
||||
# store all variables passed on the command line into CL_ARGS so we can pass them to the stage builds
|
||||
# https://stackoverflow.com/a/48555098/161659
|
||||
# MUST be done before call to 'project'
|
||||
# Use standard release build (discarding LEAN_EXTRA_CXX_FLAGS etc.) for stage0 by default since it is assumed to be "good", but still pass through CMake platform arguments (compiler, toolchain file, ..).
|
||||
# Use standard release build (discarding LEAN_CXX_EXTRA_FLAGS etc.) for stage0 by default since it is assumed to be "good", but still pass through CMake platform arguments (compiler, toolchain file, ..).
|
||||
# Use `STAGE0_` prefix to pass variables to stage0 explicitly.
|
||||
get_cmake_property(vars CACHE_VARIABLES)
|
||||
foreach(var ${vars})
|
||||
get_property(currentHelpString CACHE "${var}" PROPERTY HELPSTRING)
|
||||
if("${var}" MATCHES "STAGE0_(.*)")
|
||||
list(APPEND STAGE0_ARGS "-D${CMAKE_MATCH_1}=${${var}}")
|
||||
elseif("${var}" MATCHES "STAGE1_(.*)")
|
||||
list(APPEND STAGE1_ARGS "-D${CMAKE_MATCH_1}=${${var}}")
|
||||
elseif("${currentHelpString}" MATCHES "No help, variable specified on the command line." OR "${currentHelpString}" STREQUAL "")
|
||||
list(APPEND CL_ARGS "-D${var}=${${var}}")
|
||||
if("${var}" MATCHES "USE_GMP|CHECK_OLEAN_VERSION")
|
||||
if("${var}" STREQUAL "USE_GMP")
|
||||
# must forward options that generate incompatible .olean format
|
||||
list(APPEND STAGE0_ARGS "-D${var}=${${var}}")
|
||||
elseif("${var}" MATCHES "LLVM*|PKG_CONFIG|USE_LAKE|USE_MIMALLOC")
|
||||
list(APPEND STAGE0_ARGS "-D${var}=${${var}}")
|
||||
endif()
|
||||
elseif("${var}" MATCHES "USE_MIMALLOC")
|
||||
list(APPEND CL_ARGS "-D${var}=${${var}}")
|
||||
list(APPEND STAGE0_ARGS "-D${var}=${${var}}")
|
||||
elseif(("${var}" MATCHES "CMAKE_.*") AND NOT ("${var}" MATCHES "CMAKE_BUILD_TYPE") AND NOT ("${var}" MATCHES "CMAKE_HOME_DIRECTORY"))
|
||||
list(APPEND PLATFORM_ARGS "-D${var}=${${var}}")
|
||||
endif()
|
||||
@@ -33,100 +23,57 @@ endforeach()
|
||||
include(ExternalProject)
|
||||
project(LEAN CXX C)
|
||||
|
||||
if(NOT (DEFINED STAGE0_CMAKE_EXECUTABLE_SUFFIX))
|
||||
set(STAGE0_CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
endif()
|
||||
|
||||
# Don't do anything with cadical on wasm
|
||||
if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
|
||||
find_program(CADICAL cadical)
|
||||
if(NOT CADICAL)
|
||||
set(CADICAL_CXX c++)
|
||||
if (CADICAL_USE_CUSTOM_CXX)
|
||||
set(CADICAL_CXX ${CMAKE_CXX_COMPILER})
|
||||
set(CADICAL_CXXFLAGS "${LEAN_EXTRA_CXX_FLAGS}")
|
||||
set(CADICAL_LDFLAGS "-Wl,-rpath=\\$$ORIGIN/../lib")
|
||||
endif()
|
||||
find_program(CCACHE ccache)
|
||||
if(CCACHE)
|
||||
set(CADICAL_CXX "${CCACHE} ${CADICAL_CXX}")
|
||||
endif()
|
||||
# missing stdio locking API on Windows
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
|
||||
string(APPEND CADICAL_CXXFLAGS " -DNUNLOCKED")
|
||||
endif()
|
||||
string(APPEND CADICAL_CXXFLAGS " -DNCLOSEFROM")
|
||||
ExternalProject_add(cadical
|
||||
PREFIX cadical
|
||||
GIT_REPOSITORY https://github.com/arminbiere/cadical
|
||||
GIT_TAG rel-2.1.2
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND $(MAKE) -f ${CMAKE_SOURCE_DIR}/src/cadical.mk
|
||||
CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX}
|
||||
CXX=${CADICAL_CXX}
|
||||
CXXFLAGS=${CADICAL_CXXFLAGS}
|
||||
LDFLAGS=${CADICAL_LDFLAGS}
|
||||
BUILD_IN_SOURCE ON
|
||||
INSTALL_COMMAND "")
|
||||
set(CADICAL ${CMAKE_BINARY_DIR}/cadical/cadical${CMAKE_EXECUTABLE_SUFFIX} CACHE FILEPATH "path to cadical binary" FORCE)
|
||||
list(APPEND EXTRA_DEPENDS cadical)
|
||||
endif()
|
||||
list(APPEND CL_ARGS -DCADICAL=${CADICAL})
|
||||
endif()
|
||||
|
||||
if (USE_MIMALLOC)
|
||||
ExternalProject_add(mimalloc
|
||||
PREFIX mimalloc
|
||||
GIT_REPOSITORY https://github.com/microsoft/mimalloc
|
||||
GIT_TAG v2.2.3
|
||||
# just download, we compile it as part of each stage as it is small
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND "")
|
||||
list(APPEND EXTRA_DEPENDS mimalloc)
|
||||
endif()
|
||||
|
||||
if (NOT STAGE1_PREV_STAGE)
|
||||
ExternalProject_add(stage0
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}/stage0"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage0
|
||||
# do not rebuild stage0 when git hash changes; it's not from this commit anyway
|
||||
# (however, CI will override this as we need to embed the githash into the stage 1 library built
|
||||
# by stage 0)
|
||||
CMAKE_ARGS -DSTAGE=0 -DUSE_GITHASH=OFF ${PLATFORM_ARGS} ${STAGE0_ARGS}
|
||||
BUILD_ALWAYS ON # cmake doesn't auto-detect changes without a download method
|
||||
INSTALL_COMMAND "" # skip install
|
||||
DEPENDS ${EXTRA_DEPENDS}
|
||||
if("${CMAKE_SYSTEM_NAME}" MATCHES "Emscripten")
|
||||
# For Emscripten, we build GMP before any of the stages and reuse it in all of them.
|
||||
set(GMP_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/gmp-root)
|
||||
set(EMSCRIPTEN_FLAGS "-s ALLOW_MEMORY_GROWTH=1 -s MAIN_MODULE=1 -O3")
|
||||
ExternalProject_Add(
|
||||
gmp
|
||||
URL https://gmplib.org/download/gmp/gmp-6.2.1.tar.bz2
|
||||
URL_HASH SHA256=eae9326beb4158c386e39a356818031bd28f3124cf915f8c5b1dc4c7a36b4d7c
|
||||
BUILD_IN_SOURCE 1
|
||||
CONFIGURE_COMMAND emconfigure ./configure "CFLAGS=${EMSCRIPTEN_FLAGS}" --host=wasm32-unknown-emscripten --disable-assembly --prefix=${GMP_INSTALL_PREFIX}
|
||||
BUILD_COMMAND emmake make -j4
|
||||
INSTALL_COMMAND emmake make install
|
||||
)
|
||||
list(APPEND EXTRA_DEPENDS stage0)
|
||||
set(EXTRA_DEPENDS "gmp")
|
||||
list(APPEND CL_ARGS "-DGMP_INSTALL_PREFIX=${GMP_INSTALL_PREFIX}")
|
||||
list(APPEND PLATFORM_ARGS "-DGMP_INSTALL_PREFIX=${GMP_INSTALL_PREFIX}")
|
||||
endif()
|
||||
ExternalProject_add(stage0
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}/stage0"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage0
|
||||
# do not rebuild stage0 when git hash changes; it's not from this commit anyway
|
||||
CMAKE_ARGS -DSTAGE=0 -DUSE_GITHASH=OFF ${PLATFORM_ARGS} ${STAGE0_ARGS}
|
||||
BUILD_ALWAYS ON # cmake doesn't auto-detect changes without a download method
|
||||
INSTALL_COMMAND "" # skip install
|
||||
DEPENDS ${EXTRA_DEPENDS}
|
||||
)
|
||||
ExternalProject_add(stage1
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage1
|
||||
CMAKE_ARGS -DSTAGE=1 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage0 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${STAGE0_CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS} ${STAGE1_ARGS}
|
||||
CMAKE_ARGS -DSTAGE=1 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage0 ${CL_ARGS}
|
||||
BUILD_ALWAYS ON
|
||||
INSTALL_COMMAND ""
|
||||
DEPENDS ${EXTRA_DEPENDS}
|
||||
STEP_TARGETS configure
|
||||
DEPENDS stage0
|
||||
)
|
||||
ExternalProject_add(stage2
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage2
|
||||
CMAKE_ARGS -DSTAGE=2 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage1 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS}
|
||||
CMAKE_ARGS -DSTAGE=2 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage1 ${CL_ARGS}
|
||||
BUILD_ALWAYS ON
|
||||
INSTALL_COMMAND ""
|
||||
DEPENDS stage1
|
||||
EXCLUDE_FROM_ALL ON
|
||||
STEP_TARGETS configure
|
||||
)
|
||||
ExternalProject_add(stage3
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage3
|
||||
CMAKE_ARGS -DSTAGE=3 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage2 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS}
|
||||
CMAKE_ARGS -DSTAGE=3 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage2 ${CL_ARGS}
|
||||
BUILD_ALWAYS ON
|
||||
INSTALL_COMMAND ""
|
||||
DEPENDS stage2
|
||||
@@ -139,10 +86,6 @@ add_custom_target(update-stage0
|
||||
COMMAND $(MAKE) -C stage1 update-stage0
|
||||
DEPENDS stage1)
|
||||
|
||||
add_custom_target(update-stage0-commit
|
||||
COMMAND $(MAKE) -C stage1 update-stage0-commit
|
||||
DEPENDS stage1)
|
||||
|
||||
add_custom_target(test
|
||||
COMMAND $(MAKE) -C stage1 test
|
||||
DEPENDS stage1)
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 10,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "release",
|
||||
"displayName": "Default development optimized build config",
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/release"
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug build config",
|
||||
"cacheVariables": {
|
||||
"LEAN_EXTRA_CXX_FLAGS": "-DLEAN_DEFAULT_THREAD_STACK_SIZE=16*1024*1024",
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
},
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/debug"
|
||||
},
|
||||
{
|
||||
"name": "reldebug",
|
||||
"displayName": "Release with assertions enabled",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithAssert"
|
||||
},
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/reldebug"
|
||||
},
|
||||
{
|
||||
"name": "sanitize",
|
||||
"displayName": "Sanitize build config",
|
||||
"cacheVariables": {
|
||||
"LEAN_EXTRA_CXX_FLAGS": "-fsanitize=address,undefined -DLEAN_DEFAULT_THREAD_STACK_SIZE=16*1024*1024",
|
||||
"LEANC_EXTRA_CC_FLAGS": "-fsanitize=address,undefined",
|
||||
"LEAN_EXTRA_LINKER_FLAGS": "-fsanitize=address,undefined -fsanitize-link-c++-runtime",
|
||||
"SMALL_ALLOCATOR": "OFF",
|
||||
"USE_MIMALLOC": "OFF",
|
||||
"BSYMBOLIC": "OFF",
|
||||
"LEAN_TEST_VARS": "MAIN_STACK_SIZE=16000"
|
||||
},
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/sanitize"
|
||||
},
|
||||
{
|
||||
"name": "sandebug",
|
||||
"inherits": ["sanitize", "debug"],
|
||||
"displayName": "Sanitize+debug build config",
|
||||
"binaryDir": "${sourceDir}/build/sandebug"
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "release",
|
||||
"configurePreset": "release"
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"configurePreset": "debug"
|
||||
},
|
||||
{
|
||||
"name": "reldebug",
|
||||
"configurePreset": "reldebug"
|
||||
},
|
||||
{
|
||||
"name": "sanitize",
|
||||
"configurePreset": "sanitize"
|
||||
},
|
||||
{
|
||||
"name": "sandebug",
|
||||
"configurePreset": "sandebug"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "release",
|
||||
"configurePreset": "release",
|
||||
"output": {"outputOnFailure": true, "shortProgress": true}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"configurePreset": "debug",
|
||||
"inherits": "release"
|
||||
},
|
||||
{
|
||||
"name": "reldebug",
|
||||
"configurePreset": "reldebug",
|
||||
"inherits": "release"
|
||||
},
|
||||
{
|
||||
"name": "sanitize",
|
||||
"configurePreset": "sanitize",
|
||||
"inherits": "release"
|
||||
},
|
||||
{
|
||||
"name": "sandebug",
|
||||
"configurePreset": "sandebug",
|
||||
"inherits": "release"
|
||||
}
|
||||
]
|
||||
}
|
||||
47
CODEOWNERS
47
CODEOWNERS
@@ -1,47 +0,0 @@
|
||||
# Code Owners
|
||||
#
|
||||
# Documents responsible people per component.
|
||||
# Listed persons will automatically be asked by GitHub to review a PR touching these paths.
|
||||
# If multiple names are listed, a review by any of them is considered sufficient by default.
|
||||
|
||||
/.github/ @kim-em
|
||||
/RELEASES.md @kim-em
|
||||
/src/kernel/ @leodemoura
|
||||
/src/library/compiler/ @zwarich
|
||||
/src/lake/ @tydeu
|
||||
/src/Lean/Compiler/ @leodemoura @zwarich
|
||||
/src/Lean/Data/Lsp/ @mhuisi
|
||||
/src/Lean/Elab/Deriving/ @kim-em
|
||||
/src/Lean/Elab/Tactic/ @kim-em
|
||||
/src/Lean/Language/ @Kha
|
||||
/src/Lean/Meta/Tactic/ @leodemoura
|
||||
/src/Lean/PrettyPrinter/ @kmill
|
||||
/src/Lean/Server/ @mhuisi
|
||||
/src/Lean/Widget/ @Vtec234
|
||||
/src/Init/Data/ @kim-em
|
||||
/src/Init/Data/Array/Lemmas.lean @digama0
|
||||
/src/Init/Data/List/Lemmas.lean @digama0
|
||||
/src/Init/Data/List/BasicAux.lean @digama0
|
||||
/src/Init/Data/Array/Subarray.lean @david-christiansen
|
||||
/src/Lean/Elab/Tactic/RCases.lean @digama0
|
||||
/src/Init/RCases.lean @digama0
|
||||
/src/Lean/Elab/Tactic/Ext.lean @digama0
|
||||
/src/Init/Ext.lean @digama0
|
||||
/src/Lean/Elab/Tactic/Simpa.lean @digama0
|
||||
/src/Lean/Elab/Tactic/NormCast.lean @digama0
|
||||
/src/Lean/Meta/Tactic/NormCast.lean @digama0
|
||||
/src/Lean/Meta/Tactic/TryThis.lean @digama0
|
||||
/src/Lean/Elab/Tactic/SimpTrace.lean @digama0
|
||||
/src/Lean/Elab/Tactic/NoMatch.lean @digama0
|
||||
/src/Lean/Elab/Tactic/ShowTerm.lean @digama0
|
||||
/src/Lean/Elab/Tactic/Repeat.lean @digama0
|
||||
/src/Lean/Meta/Tactic/Repeat.lean @digama0
|
||||
/src/Lean/Meta/CoeAttr.lean @digama0
|
||||
/src/Lean/Elab/GuardMsgs.lean @digama0
|
||||
/src/Lean/Elab/Tactic/Guard.lean @digama0
|
||||
/src/Init/Guard.lean @digama0
|
||||
/src/Lean/Server/CodeActions/ @digama0
|
||||
/src/Std/ @TwoFX
|
||||
/src/Std/Tactic/BVDecide/ @hargoniX
|
||||
/src/Lean/Elab/Tactic/BVDecide/ @hargoniX
|
||||
/src/Std/Sat/ @hargoniX
|
||||
114
CONTRIBUTING.md
114
CONTRIBUTING.md
@@ -1,93 +1,57 @@
|
||||
External Contribution Guidelines
|
||||
============
|
||||
# Contribution Guidelines
|
||||
|
||||
In the past, we accepted most pull requests. This practice produced hard to maintain code, performance problems, and bugs. In order to improve the quality and maintainability of our codebase, we've established the following guidelines for external contributions.
|
||||
Thank you for your interest in contributing to Lean! There are many ways to contribute and we appreciate all of them.
|
||||
|
||||
Helpful links
|
||||
-------
|
||||
## Bug reports
|
||||
|
||||
* [Development Setup](./doc/dev/index.md)
|
||||
* [Testing](./doc/dev/testing.md)
|
||||
* [Commit convention](./doc/dev/commit_convention.md)
|
||||
Bug reports as new issues are always welcome. Please check the existing [issues](https://github.com/leanprover/lean4/issues) first.
|
||||
Reduce the issue to a self-contained, reproducible test case.
|
||||
If you have the chance, before reporting a bug, please search existing issues, as it's possible that
|
||||
someone else has already reported your error.
|
||||
If you're not sure if something is a bug or not, feel free to file a bug anyway. You may also want to discuss it with the Lean
|
||||
community using the [lean4 Zulip channel](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4).
|
||||
|
||||
Before You Submit a Pull Request (PR):
|
||||
-------
|
||||
## Simple fixes
|
||||
|
||||
**Start with an Issue**: Before submitting a PR, always open an issue discussing the problem you wish to solve or the feature you'd like to add. Use the prefix `RFC:` (request for comments) if you are proposing a new feature. Ask for feedback from other users. Take the time to summarize all the feedback. This allows the maintainers to evaluate your proposal more efficiently. When creating a RFC, consider the following questions:
|
||||
Simple fixes for **typos and clear bugs** are welcome.
|
||||
|
||||
- **User Experience**: How does this feature improve the user experience?
|
||||
## Documentation
|
||||
|
||||
- **Beneficiaries**: Which Lean users and projects do benefit most from this feature/change?
|
||||
Tutorial-like examples are very welcome.
|
||||
They are useful for finding rough edges and bugs in Lean 4, for highlighting new features, and for showing how to use Lean.
|
||||
If you want to store your tutorial in the Lean 4 repository to make sure future changes will not break it, we suggest the following workflow:
|
||||
* Contact one of the Lean developers on Zulip, and check whether your tutorial is a good match for the Lean 4 repository.
|
||||
* Send bug reports and report rough edges. We will work with you until the tutorial looks great.
|
||||
* Add plenty of comments and make sure others will be able to follow it.
|
||||
* Create a pull request in the Lean 4 repository. After merging, we will link it to the official documentation and make sure it becomes part of our test suite.
|
||||
|
||||
- **Community Feedback**: Have you sought feedback or insights from other Lean users?
|
||||
You can use `.lean` or `.md` files to create your tutorial. The `.md` files are ideal when you want to format your prose using markdown. For an example, see [this `.md` file](https://github.com/leanprover/lean4/blob/master/doc/lean3changes.md).
|
||||
|
||||
- **Maintainability**: Will this change streamline code maintenance or simplify its structure?
|
||||
Contributions to the reference manual are also welcome, but since Lean 4 is changing rapidly, please contact us first using Zulip
|
||||
to find out which parts are stable enough to document. We will work with you to get this kind of
|
||||
pull request merged. We are also happy to meet using Zoom, Skype or Google hangout to coordinate this kind of effort.
|
||||
|
||||
**Understand the Project**: Familiarize yourself with the project, existing issues, and latest commits. Ensure your contribution aligns with the project's direction and priorities.
|
||||
As Lean 4 matures, other forms of documentation (e.g., doc-strings) will be welcome too.
|
||||
|
||||
**Stay Updated**: Regularly fetch and merge changes from the main branch to ensure your branch is up-to-date and can be smoothly integrated.
|
||||
## "Help wanted"
|
||||
|
||||
**Help wanted**: We have issues tagged with ["help wanted"](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22), if you want to contribute to the project, please take a look at them. If you are interested in one of them, post comments, ask questions, and engage with the core developers there.
|
||||
For issues marked as [`help wanted`](https://github.com/leanprover/lean4/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22), pull requests (PR) are welcome and we will work with you to get a PR merged. Some of these issues are nontrivial. If you are interested, please consider adding comments to the issue and/or messaging the Lean developers in [Zulip](https://leanprover.zulipchat.com/#).
|
||||
|
||||
Quality Over Quantity:
|
||||
-----
|
||||
## Unexpected Pull Requests
|
||||
|
||||
**Focused Changes**: Each PR should address a single, clearly-defined issue or feature. Avoid making multiple unrelated changes in a single PR.
|
||||
We have very few core developers, and we cannot review arbitrary pull requests (PRs). Moreover, many features involve subtle tradeoffs, and it may require significant time and energy to even assess a proposed design. We suggest the following workflow:
|
||||
|
||||
**Write Tests**: Every new feature or bug fix should come with relevant tests. This ensures the robustness and reliability of the contribution.
|
||||
* First, discuss your idea with the Lean community on Zulip. Ask the community to help collect examples, document the requirements, and detect complications.
|
||||
* If there is broad support, create a detailed issue for it on the Lean 4 repository at GitHub, and tag the issue with `RFC`.
|
||||
* Ask the community for help documenting the requirements, and for collecting examples and concerns.
|
||||
* Wait for one of the core developers to give you a "go ahead". At this point, the core developers will work with you to make sure your PR gets merged.
|
||||
|
||||
**Documentation**: Update relevant documentation, including comments in the code, to explain the logic and reasoning behind your changes.
|
||||
We don't want to waste your time by you implementing a feature and then us not being able to merge it.
|
||||
|
||||
Coding Standards:
|
||||
----
|
||||
## How to Contribute
|
||||
|
||||
**Follow the Code Style**: Ensure that your code follows the established coding style of the project.
|
||||
|
||||
**Lean on Lean**: Use Lean's built-in features and libraries effectively, avoiding reinventions.
|
||||
|
||||
**Performance**: Make sure that your changes do not introduce performance regressions. If possible, optimize the solution for speed and resource usage.
|
||||
|
||||
PR Submission:
|
||||
---
|
||||
|
||||
**Descriptive Title and Summary**: The PR title should briefly explain the purpose of the PR. The summary should give more detailed information on what changes are made and why. Links to Zulip threads are not acceptable as a summary. You are responsible for summarizing the discussion, and getting support for it.
|
||||
|
||||
**Follow the commit convention**: Pull requests are squash merged, and the
|
||||
commit message is taken from the pull request title and body, so make sure they adhere to the [commit convention](https://github.com/leanprover/lean4/blob/master/doc/dev/commit_convention.md). Put questions and extra information, which should not be part of the final commit message, into a first comment rather than the Pull Request description.
|
||||
Because the change will be squashed, there is no need to polish the commit messages and history on the branch.
|
||||
|
||||
**Link to Relevant Issues**: Reference any issues that your PR addresses to provide context.
|
||||
|
||||
**Stay Responsive**: Once the PR is submitted, stay responsive to feedback and be prepared to make necessary revisions. We will close any PR that has been inactive (no response or updates from the submitter) for more than a month.
|
||||
|
||||
Reviews and Feedback:
|
||||
----
|
||||
|
||||
The lean4 repo is managed by the Lean FRO's *triage team* that aims to provide initial feedback on new bug reports, PRs, and RFCs weekly.
|
||||
This feedback generally consists of prioritizing the ticket using one of the following categories:
|
||||
* label `P-high`: We will work on this issue
|
||||
* label `P-medium`: We may work on this issue if we find the time
|
||||
* label `P-low`: We are not planning to work on this issue
|
||||
* *closed*: This issue is already fixed, it is not an issue, or is not sufficiently compatible with our roadmap for the project and we will not work on it nor accept external contributions on it
|
||||
|
||||
For *bug reports*, the listed priority reflects our commitment to fixing the issue.
|
||||
It is generally indicative but not necessarily identical to the priority an external contribution addressing this bug would receive.
|
||||
For *PRs* and *RFCs*, the priority reflects our commitment to reviewing them and getting them to an acceptable state.
|
||||
Accepted RFCs are marked with the label `RFC accepted` and afterwards assigned a new "implementation" priority as with bug reports.
|
||||
|
||||
General guidelines for interacting with reviews and feedback:
|
||||
|
||||
**Be Patient**: Given the limited number of full-time maintainers and the volume of PRs, reviews may take some time.
|
||||
|
||||
**Engage Constructively**: Always approach feedback positively and constructively. Remember, reviews are about ensuring the best quality for the project, not personal criticism.
|
||||
|
||||
**Continuous Integration**: Ensure that all CI checks pass on your PR. Failed checks will delay the review process. The maintainers will not check PRs containing failures.
|
||||
|
||||
What to Expect:
|
||||
----
|
||||
|
||||
**Not All PRs Get Merged**: While we appreciate every contribution, not all PRs will be merged. Ensure your changes align with the project's goals and quality standards.
|
||||
|
||||
**Feedback is a Gift**: It helps improve the project and can also help you grow as a developer or contributor.
|
||||
|
||||
**Community Involvement**: Engage with the Lean community on our communication channels. This can lead to better collaboration and understanding of the project's direction.
|
||||
* Always follow the [commit convention](https://leanprover.github.io/lean4/doc/dev/commit_convention.html).
|
||||
* Follow the style of the surrounding code. When in doubt, look at other files using the particular syntax as well.
|
||||
* Make sure your code is documented.
|
||||
* New features or bug fixes should come with appropriate tests.
|
||||
* Ensure all tests work before submitting a PR; see [Development Setup](https://leanprover.github.io/lean4/doc/make/index.html#development-setup) and [Fixing Tests](https://leanprover.github.io/lean4/doc/dev/fixing_tests.html).
|
||||
|
||||
30
LICENSES
30
LICENSES
@@ -1341,33 +1341,3 @@ whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
==============================================================================
|
||||
CaDiCaL is under the MIT License:
|
||||
==============================================================================
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2021 Armin Biere, Johannes Kepler University Linz, Austria
|
||||
Copyright (c) 2020-2021 Mathias Fleury, Johannes Kepler University Linz, Austria
|
||||
Copyright (c) 2020-2021 Nils Froleyks, Johannes Kepler University Linz, Austria
|
||||
Copyright (c) 2022-2024 Katalin Fazekas, Vienna University of Technology, Austria
|
||||
Copyright (c) 2021-2024 Armin Biere, University of Freiburg, Germany
|
||||
Copyright (c) 2021-2024 Mathias Fleury, University of Freiburg, Germany
|
||||
Copyright (c) 2023-2024 Florian Pollitt, University of Freiburg, Germany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
24
README.md
24
README.md
@@ -1,20 +1,22 @@
|
||||
This is the repository for **Lean 4**.
|
||||
This is the repository for **Lean 4**, which is currently being released as milestone releases towards a first stable release.
|
||||
[Lean 3](https://github.com/leanprover/lean) is still the latest stable release.
|
||||
|
||||
# About
|
||||
|
||||
- [Quickstart](https://lean-lang.org/documentation/setup/)
|
||||
- [Homepage](https://lean-lang.org)
|
||||
- [Theorem Proving Tutorial](https://lean-lang.org/theorem_proving_in_lean4/)
|
||||
- [Functional Programming in Lean](https://lean-lang.org/functional_programming_in_lean/)
|
||||
- [Documentation Overview](https://lean-lang.org/documentation/)
|
||||
- [Language Reference](https://lean-lang.org/doc/reference/latest/)
|
||||
- [Quickstart](https://github.com/leanprover/lean4/blob/master/doc/quickstart.md)
|
||||
- [Walkthrough installation video](https://www.youtube.com/watch?v=yZo6k48L0VY)
|
||||
- [Quick tour video](https://youtu.be/zyXtbb_eYbY)
|
||||
- [Homepage](https://leanprover.github.io)
|
||||
- [Theorem Proving Tutorial](https://leanprover.github.io/theorem_proving_in_lean4/)
|
||||
- [Functional Programming in Lean](https://leanprover.github.io/functional_programming_in_lean/) **first chapter is available!**
|
||||
- [Manual](https://leanprover.github.io/lean4/doc/)
|
||||
- [Release notes](RELEASES.md) starting at v4.0.0-m3
|
||||
- [Examples](https://lean-lang.org/documentation/examples/)
|
||||
- [External Contribution Guidelines](CONTRIBUTING.md)
|
||||
- [Examples](https://leanprover.github.io/lean4/doc/examples.html)
|
||||
- [FAQ](https://leanprover.github.io/lean4/doc/faq.html)
|
||||
|
||||
# Installation
|
||||
|
||||
See [Setting Up Lean](https://lean-lang.org/documentation/setup/).
|
||||
See [Setting Up Lean](https://leanprover.github.io/lean4/doc/setup.html).
|
||||
|
||||
# Contributing
|
||||
|
||||
@@ -22,4 +24,4 @@ Please read our [Contribution Guidelines](CONTRIBUTING.md) first.
|
||||
|
||||
# Building from Source
|
||||
|
||||
See [Building Lean](doc/make/index.md).
|
||||
See [Building Lean](https://leanprover.github.io/lean4/doc/make/index.html).
|
||||
|
||||
876
RELEASES.md
876
RELEASES.md
@@ -1,9 +1,873 @@
|
||||
# Lean 4 releases
|
||||
Unreleased
|
||||
---------
|
||||
|
||||
We intend to provide regular "minor version" releases of the Lean language at approximately monthly intervals.
|
||||
There is not yet a strong guarantee of backwards compatibility between versions,
|
||||
only an expectation that breaking changes will be documented in the release notes.
|
||||
* Add support for computed fields in inductives. Example:
|
||||
```lean
|
||||
inductive Exp
|
||||
| var (i : Nat)
|
||||
| app (a b : Exp)
|
||||
with
|
||||
@[computedField] hash : Exp → Nat
|
||||
| .var i => i
|
||||
| .app a b => a.hash * b.hash + 1
|
||||
```
|
||||
The result of the `Exp.hash` function is then stored as an extra "computed" field in the `.var` and `.app` constructors;
|
||||
`Exp.hash` accesses this field and thus runs in constant time (even on dag-like values).
|
||||
|
||||
[Release notes](https://lean-lang.org/doc/reference/latest/releases/#release-notes) are available in the Lean language reference.
|
||||
* Update `a[i]` notation. It is now based on the typeclass
|
||||
```lean
|
||||
class GetElem (Cont : Type u) (Idx : Type v) (Elem : outParam (Type w)) (Dom : outParam (Cont → Idx → Prop)) where
|
||||
getElem (xs : Cont) (i : Idx) (h : Dom xs i) : Elem
|
||||
```
|
||||
The notation `a[i]` is now defined as follows
|
||||
```lean
|
||||
macro:max x:term noWs "[" i:term "]" : term => `(getElem $x $i (by get_elem_tactic))
|
||||
```
|
||||
The proof that `i` is a valid index is synthesized using the tactic `get_elem_tactic`.
|
||||
For example, the type `Array α` has the following instances
|
||||
```lean
|
||||
instance : GetElem (Array α) Nat α fun xs i => LT.lt i xs.size where ...
|
||||
instance : GetElem (Array α) USize α fun xs i => LT.lt i.toNat xs.size where ...
|
||||
```
|
||||
You can use the notation `a[i]'h` to provide the proof manually.
|
||||
Two other notations were introduced: `a[i]!` and `a[i]?`, For `a[i]!`, a panic error message is produced at
|
||||
runtime if `i` is not a valid index. `a[i]?` has type `Option α`, and `a[i]?` evaluates to `none` if the
|
||||
index `i` is not valid.
|
||||
The three new notations are defined as follows:
|
||||
```lean
|
||||
@[inline] def getElem' [GetElem Cont Idx Elem Dom] (xs : Cont) (i : Idx) (h : Dom xs i) : Elem :=
|
||||
getElem xs i h
|
||||
|
||||
Release notes for the current release candidate are available on the GitHub [releases](https://github.com/leanprover/lean4/releases) page.
|
||||
@[inline] def getElem! [GetElem Cont Idx Elem Dom] [Inhabited Elem] (xs : Cont) (i : Idx) [Decidable (Dom xs i)] : Elem :=
|
||||
if h : _ then getElem xs i h else panic! "index out of bounds"
|
||||
|
||||
@[inline] def getElem? [GetElem Cont Idx Elem Dom] (xs : Cont) (i : Idx) [Decidable (Dom xs i)] : Option Elem :=
|
||||
if h : _ then some (getElem xs i h) else none
|
||||
|
||||
macro:max x:term noWs "[" i:term "]" noWs "?" : term => `(getElem? $x $i)
|
||||
macro:max x:term noWs "[" i:term "]" noWs "!" : term => `(getElem! $x $i)
|
||||
macro x:term noWs "[" i:term "]'" h:term:max : term => `(getElem' $x $i $h)
|
||||
```
|
||||
See discussion on [Zulip](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/String.2EgetOp/near/287855425).
|
||||
Examples:
|
||||
```lean
|
||||
example (a : Array Int) (i : Nat) : Int :=
|
||||
a[i] -- Error: failed to prove index is valid ...
|
||||
|
||||
example (a : Array Int) (i : Nat) (h : i < a.size) : Int :=
|
||||
a[i] -- Ok
|
||||
|
||||
example (a : Array Int) (i : Nat) : Int :=
|
||||
a[i]! -- Ok
|
||||
|
||||
example (a : Array Int) (i : Nat) : Option Int :=
|
||||
a[i]? -- Ok
|
||||
|
||||
example (a : Array Int) (h : a.size = 2) : Int :=
|
||||
a[0]'(by rw [h]; decide) -- Ok
|
||||
|
||||
example (a : Array Int) (h : a.size = 2) : Int :=
|
||||
have : 0 < a.size := by rw [h]; decide
|
||||
have : 1 < a.size := by rw [h]; decide
|
||||
a[0] + a[1] -- Ok
|
||||
|
||||
example (a : Array Int) (i : USize) (h : i.toNat < a.size) : Int :=
|
||||
a[i] -- Ok
|
||||
```
|
||||
The `get_elem_tactic` is defined as
|
||||
```lean
|
||||
macro "get_elem_tactic" : tactic =>
|
||||
`(first
|
||||
| get_elem_tactic_trivial
|
||||
| fail "failed to prove index is valid, ..."
|
||||
)
|
||||
```
|
||||
The `get_elem_tactic_trivial` auxiliary tactic can be extended using `macro_rules`. By default, it tries `trivial`, `simp_arith`, and a special case for `Fin`. In the future, it will also try `linarith`.
|
||||
You can extend `get_elem_tactic_trivial` using `my_tactic` as follows
|
||||
```lean
|
||||
macro_rules
|
||||
| `(tactic| get_elem_tactic_trivial) => `(tactic| my_tactic)
|
||||
```
|
||||
Note that `Idx`'s type in `GetElem` does not depend on `Cont`. So, you cannot write the instance `instance : GetElem (Array α) (Fin ??) α fun xs i => ...`, but the Lean library comes equipped with the following auxiliary instance:
|
||||
```lean
|
||||
instance [GetElem Cont Nat Elem Dom] : GetElem Cont (Fin n) Elem fun xs i => Dom xs i where
|
||||
getElem xs i h := getElem xs i.1 h
|
||||
```
|
||||
and helper tactic
|
||||
```lean
|
||||
macro_rules
|
||||
| `(tactic| get_elem_tactic_trivial) => `(tactic| apply Fin.val_lt_of_le; get_elem_tactic_trivial; done)
|
||||
```
|
||||
Example:
|
||||
```lean
|
||||
example (a : Array Nat) (i : Fin a.size) :=
|
||||
a[i] -- Ok
|
||||
|
||||
example (a : Array Nat) (h : n ≤ a.size) (i : Fin n) :=
|
||||
a[i] -- Ok
|
||||
```
|
||||
|
||||
* Better support for qualified names in recursive declarations. The following is now supported:
|
||||
```lean
|
||||
namespace Nat
|
||||
def fact : Nat → Nat
|
||||
| 0 => 1
|
||||
| n+1 => (n+1) * Nat.fact n
|
||||
end Nat
|
||||
```
|
||||
|
||||
* Update Lake to v3.2.1. See the [v3.2.1 release notes](https://github.com/leanprover/lake/releases/tag/v3.2.1) for detailed changes.
|
||||
|
||||
* Add support for `CommandElabM` monad at `#eval`. Example:
|
||||
```lean
|
||||
import Lean
|
||||
|
||||
open Lean Elab Command
|
||||
|
||||
#eval do
|
||||
let id := mkIdent `foo
|
||||
elabCommand (← `(def $id := 10))
|
||||
|
||||
#eval foo -- 10
|
||||
```
|
||||
|
||||
* Try to elaborate `do` notation even if the expected type is not available. We still delay elaboration when the expected type
|
||||
is not available. This change is particularly useful when writing examples such as
|
||||
```lean
|
||||
#eval do
|
||||
IO.println "hello"
|
||||
IO.println "world"
|
||||
```
|
||||
That is, we don't have to use the idiom `#eval show IO _ from do ...` anymore.
|
||||
Note that auto monadic lifting is less effective when the expected type is not available.
|
||||
Monadic polymorphic functions (e.g., `ST.Ref.get`) also require the expected type.
|
||||
|
||||
* On Linux, panics now print a backtrace by default, which can be disabled by setting the environment variable `LEAN_BACKTRACE` to `0`.
|
||||
Other platforms are TBD.
|
||||
|
||||
* The `group(·)` `syntax` combinator is now introduced automatically where necessary, such as when using multiple parsers inside `(...)+`.
|
||||
|
||||
* Add ["Typed Macros"](https://github.com/leanprover/lean4/pull/1251): syntax trees produced and accepted by syntax antiquotations now remember their syntax kinds, preventing accidental production of ill-formed syntax trees and reducing the need for explicit `:kind` antiquotation annotations. See PR for details.
|
||||
|
||||
* Aliases of protected definitions are protected too. Example:
|
||||
```lean
|
||||
protected def Nat.double (x : Nat) := 2*x
|
||||
|
||||
namespace Ex
|
||||
export Nat (double) -- Add alias Ex.double for Nat.double
|
||||
end Ex
|
||||
|
||||
open Ex
|
||||
#check Ex.double -- Ok
|
||||
#check double -- Error, `Ex.double` is alias for `Nat.double` which is protected
|
||||
```
|
||||
|
||||
* Use `IO.getRandomBytes` to initialize random seed for `IO.rand`. See discussion at [this PR](https://github.com/leanprover/lean4-samples/pull/2).
|
||||
|
||||
* Improve dot notation and aliases interaction. See discussion on [Zulip](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Namespace-based.20overloading.20does.20not.20find.20exports/near/282946185) for additional details.
|
||||
Example:
|
||||
```lean
|
||||
def Set (α : Type) := α → Prop
|
||||
def Set.union (s₁ s₂ : Set α) : Set α := fun a => s₁ a ∨ s₂ a
|
||||
def FinSet (n : Nat) := Fin n → Prop
|
||||
|
||||
namespace FinSet
|
||||
export Set (union) -- FinSet.union is now an alias for `Set.union`
|
||||
end FinSet
|
||||
|
||||
example (x y : FinSet 10) : FinSet 10 :=
|
||||
x.union y -- Works
|
||||
```
|
||||
|
||||
* `ext` and `enter` conv tactics can now go inside let-declarations. Example:
|
||||
```lean
|
||||
example (g : Nat → Nat) (y : Nat) (h : let x := y + 1; g (0+x) = x) : g (y + 1) = y + 1 := by
|
||||
conv at h => enter [x, 1, 1]; rw [Nat.zero_add]
|
||||
/-
|
||||
g : Nat → Nat
|
||||
y : Nat
|
||||
h : let x := y + 1;
|
||||
g x = x
|
||||
⊢ g (y + 1) = y + 1
|
||||
-/
|
||||
exact h
|
||||
```
|
||||
|
||||
* Add `zeta` conv tactic to expand let-declarations. Example:
|
||||
```lean
|
||||
example (h : let x := y + 1; 0 + x = y) : False := by
|
||||
conv at h => zeta; rw [Nat.zero_add]
|
||||
/-
|
||||
y : Nat
|
||||
h : y + 1 = y
|
||||
⊢ False
|
||||
-/
|
||||
simp_arith at h
|
||||
```
|
||||
|
||||
* Improve namespace resolution. See issue [#1224](https://github.com/leanprover/lean4/issues/1224). Example:
|
||||
```lean
|
||||
import Lean
|
||||
open Lean Parser Elab
|
||||
open Tactic -- now opens both `Lean.Parser.Tactic` and `Lean.Elab.Tactic`
|
||||
```
|
||||
|
||||
* Rename `constant` command to `opaque`. See discussion at [Zulip](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/What.20is.20.60opaque.60.3F/near/284926171).
|
||||
|
||||
* Extend `induction` and `cases` syntax: multiple left-hand-sides in a single alternative. This extension is very similar to the one implemented for `match` expressions. Examples:
|
||||
```lean
|
||||
inductive Foo where
|
||||
| mk1 (x : Nat) | mk2 (x : Nat) | mk3
|
||||
|
||||
def f (v : Foo) :=
|
||||
match v with
|
||||
| .mk1 x => x + 1
|
||||
| .mk2 x => 2*x + 1
|
||||
| .mk3 => 1
|
||||
|
||||
theorem f_gt_zero : f v > 0 := by
|
||||
cases v with
|
||||
| mk1 x | mk2 x => simp_arith! -- New feature used here!
|
||||
| mk3 => decide
|
||||
```
|
||||
|
||||
* [`let/if` indentation in `do` blocks in now supported.](https://github.com/leanprover/lean4/issues/1120)
|
||||
|
||||
* Add unnamed antiquotation `$_` for use in syntax quotation patterns.
|
||||
|
||||
* [Add unused variables linter](https://github.com/leanprover/lean4/pull/1159). Feedback welcome!
|
||||
|
||||
* Lean now generates an error if the body of a declaration body contains a universe parameter that does not occur in the declaration type, nor is an explicit parameter.
|
||||
Examples:
|
||||
```lean
|
||||
/-
|
||||
The following declaration now produces an error because `PUnit` is universe polymorphic,
|
||||
but the universe parameter does not occur in the function type `Nat → Nat`
|
||||
-/
|
||||
def f (n : Nat) : Nat :=
|
||||
let aux (_ : PUnit) : Nat := n + 1
|
||||
aux ⟨⟩
|
||||
|
||||
/-
|
||||
The following declaration is accepted because the universe parameter was explicitly provided in the
|
||||
function signature.
|
||||
-/
|
||||
def g.{u} (n : Nat) : Nat :=
|
||||
let aux (_ : PUnit.{u}) : Nat := n + 1
|
||||
aux ⟨⟩
|
||||
```
|
||||
|
||||
* Add `subst_vars` tactic.
|
||||
|
||||
* [Fix `autoParam` in structure fields lost in multiple inheritance.](https://github.com/leanprover/lean4/issues/1158).
|
||||
|
||||
* Add `[eliminator]` attribute. It allows users to specify default recursor/eliminators for the `induction` and `cases` tactics.
|
||||
It is an alternative for the `using` notation. Example:
|
||||
```lean
|
||||
@[eliminator] protected def recDiag {motive : Nat → Nat → Sort u}
|
||||
(zero_zero : motive 0 0)
|
||||
(succ_zero : (x : Nat) → motive x 0 → motive (x + 1) 0)
|
||||
(zero_succ : (y : Nat) → motive 0 y → motive 0 (y + 1))
|
||||
(succ_succ : (x y : Nat) → motive x y → motive (x + 1) (y + 1))
|
||||
(x y : Nat) : motive x y :=
|
||||
let rec go : (x y : Nat) → motive x y
|
||||
| 0, 0 => zero_zero
|
||||
| x+1, 0 => succ_zero x (go x 0)
|
||||
| 0, y+1 => zero_succ y (go 0 y)
|
||||
| x+1, y+1 => succ_succ x y (go x y)
|
||||
go x y
|
||||
termination_by go x y => (x, y)
|
||||
|
||||
def f (x y : Nat) :=
|
||||
match x, y with
|
||||
| 0, 0 => 1
|
||||
| x+1, 0 => f x 0
|
||||
| 0, y+1 => f 0 y
|
||||
| x+1, y+1 => f x y
|
||||
termination_by f x y => (x, y)
|
||||
|
||||
example (x y : Nat) : f x y > 0 := by
|
||||
induction x, y <;> simp [f, *]
|
||||
```
|
||||
|
||||
* Add support for `casesOn` applications to structural and well-founded recursion modules.
|
||||
This feature is useful when writing definitions using tactics. Example:
|
||||
```lean
|
||||
inductive Foo where
|
||||
| a | b | c
|
||||
| pair: Foo × Foo → Foo
|
||||
|
||||
def Foo.deq (a b : Foo) : Decidable (a = b) := by
|
||||
cases a <;> cases b
|
||||
any_goals apply isFalse Foo.noConfusion
|
||||
any_goals apply isTrue rfl
|
||||
case pair a b =>
|
||||
let (a₁, a₂) := a
|
||||
let (b₁, b₂) := b
|
||||
exact match deq a₁ b₁, deq a₂ b₂ with
|
||||
| isTrue h₁, isTrue h₂ => isTrue (by rw [h₁,h₂])
|
||||
| isFalse h₁, _ => isFalse (fun h => by cases h; cases (h₁ rfl))
|
||||
| _, isFalse h₂ => isFalse (fun h => by cases h; cases (h₂ rfl))
|
||||
```
|
||||
|
||||
* `Option` is again a monad. The auxiliary type `OptionM` has been removed. See [Zulip thread](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Do.20we.20still.20need.20OptionM.3F/near/279761084).
|
||||
|
||||
* Improve `split` tactic. It used to fail on `match` expressions of the form `match h : e with ...` where `e` is not a free variable.
|
||||
The failure used to occur during generalization.
|
||||
|
||||
|
||||
* New encoding for `match`-expressions that use the `h :` notation for discriminants. The information is not lost during delaboration,
|
||||
and it is the foundation for a better `split` tactic. at delaboration time. Example:
|
||||
```lean
|
||||
#print Nat.decEq
|
||||
/-
|
||||
protected def Nat.decEq : (n m : Nat) → Decidable (n = m) :=
|
||||
fun n m =>
|
||||
match h : Nat.beq n m with
|
||||
| true => isTrue (_ : n = m)
|
||||
| false => isFalse (_ : ¬n = m)
|
||||
-/
|
||||
```
|
||||
|
||||
* `exists` tactic is now takes a comma separated list of terms.
|
||||
|
||||
* Add `dsimp` and `dsimp!` tactics. They guarantee the result term is definitionally equal, and only apply
|
||||
`rfl`-theorems.
|
||||
|
||||
* Fix binder information for `match` patterns that use definitions tagged with `[matchPattern]` (e.g., `Nat.add`).
|
||||
We now have proper binder information for the variable `y` in the following example.
|
||||
```lean
|
||||
def f (x : Nat) : Nat :=
|
||||
match x with
|
||||
| 0 => 1
|
||||
| y + 1 => y
|
||||
```
|
||||
|
||||
* (Fix) the default value for structure fields may now depend on the structure parameters. Example:
|
||||
```lean
|
||||
structure Something (i: Nat) where
|
||||
n1: Nat := 1
|
||||
n2: Nat := 1 + i
|
||||
|
||||
def s : Something 10 := {}
|
||||
example : s.n2 = 11 := rfl
|
||||
```
|
||||
|
||||
* Apply `rfl` theorems at the `dsimp` auxiliary method used by `simp`. `dsimp` can be used anywhere in an expression
|
||||
because it preserves definitional equality.
|
||||
|
||||
* Refine auto bound implicit feature. It does not consider anymore unbound variables that have the same
|
||||
name of a declaration being defined. Example:
|
||||
```lean
|
||||
def f : f → Bool := -- Error at second `f`
|
||||
fun _ => true
|
||||
|
||||
inductive Foo : List Foo → Type -- Error at second `Foo`
|
||||
| x : Foo []
|
||||
```
|
||||
Before this refinement, the declarations above would be accepted and the
|
||||
second `f` and `Foo` would be treated as auto implicit variables. That is,
|
||||
`f : {f : Sort u} → f → Bool`, and
|
||||
`Foo : {Foo : Type u} → List Foo → Type`.
|
||||
|
||||
|
||||
* Fix syntax hightlighting for recursive declarations. Example
|
||||
```lean
|
||||
inductive List (α : Type u) where
|
||||
| nil : List α -- `List` is not highlighted as a variable anymore
|
||||
| cons (head : α) (tail : List α) : List α
|
||||
|
||||
def List.map (f : α → β) : List α → List β
|
||||
| [] => []
|
||||
| a::as => f a :: map f as -- `map` is not highlighted as a variable anymore
|
||||
```
|
||||
* Add `autoUnfold` option to `Lean.Meta.Simp.Config`, and the following macros
|
||||
- `simp!` for `simp (config := { autoUnfold := true })`
|
||||
- `simp_arith!` for `simp (config := { autoUnfold := true, arith := true })`
|
||||
- `simp_all!` for `simp_all (config := { autoUnfold := true })`
|
||||
- `simp_all_arith!` for `simp_all (config := { autoUnfold := true, arith := true })`
|
||||
|
||||
When the `autoUnfold` is set to true, `simp` tries to unfold the following kinds of definition
|
||||
- Recursive definitions defined by structural recursion.
|
||||
- Non-recursive definitions where the body is a `match`-expression. This
|
||||
kind of definition is only unfolded if the `match` can be reduced.
|
||||
Example:
|
||||
```lean
|
||||
def append (as bs : List α) : List α :=
|
||||
match as with
|
||||
| [] => bs
|
||||
| a :: as => a :: append as bs
|
||||
|
||||
theorem append_nil (as : List α) : append as [] = as := by
|
||||
induction as <;> simp_all!
|
||||
|
||||
theorem append_assoc (as bs cs : List α) : append (append as bs) cs = append as (append bs cs) := by
|
||||
induction as <;> simp_all!
|
||||
```
|
||||
|
||||
* Add `save` tactic for creating checkpoints more conveniently. Example:
|
||||
```lean
|
||||
example : <some-proposition> := by
|
||||
tac_1
|
||||
tac_2
|
||||
save
|
||||
tac_3
|
||||
...
|
||||
```
|
||||
is equivalent to
|
||||
```lean
|
||||
example : <some-proposition> := by
|
||||
checkpoint
|
||||
tac_1
|
||||
tac_2
|
||||
tac_3
|
||||
...
|
||||
```
|
||||
|
||||
* Remove support for `{}` annotation from inductive datatype contructors. This annotation was barely used, and we can control the binder information for parameter bindings using the new inductive family indices to parameter promotion. Example: the following declaration using `{}`
|
||||
```lean
|
||||
inductive LE' (n : Nat) : Nat → Prop where
|
||||
| refl {} : LE' n n -- Want `n` to be explicit
|
||||
| succ : LE' n m → LE' n (m+1)
|
||||
```
|
||||
can now be written as
|
||||
```lean
|
||||
inductive LE' : Nat → Nat → Prop where
|
||||
| refl (n : Nat) : LE' n n
|
||||
| succ : LE' n m → LE' n (m+1)
|
||||
```
|
||||
In both cases, the inductive family has one parameter and one index.
|
||||
Recall that the actual number of parameters can be retrieved using the command `#print`.
|
||||
|
||||
* Remove support for `{}` annotation in the `structure` command.
|
||||
|
||||
* Several improvements to LSP server. Examples: "jump to definition" in mutually recursive sections, fixed incorrect hover information in "match"-expression patterns, "jump to definition" for pattern variables, fixed auto-completion in function headers, etc.
|
||||
|
||||
* In `macro ... xs:p* ...` and similar macro bindings of combinators, `xs` now has the correct type `Array Syntax`
|
||||
|
||||
* Identifiers in syntax patterns now ignore macro scopes during matching.
|
||||
|
||||
* Improve binder names for constructor auto implicit parameters. Example, given the inductive datatype
|
||||
```lean
|
||||
inductive Member : α → List α → Type u
|
||||
| head : Member a (a::as)
|
||||
| tail : Member a bs → Member a (b::bs)
|
||||
```
|
||||
before:
|
||||
```lean
|
||||
#check @Member.head
|
||||
-- @Member.head : {x : Type u_1} → {a : x} → {as : List x} → Member a (a :: as)
|
||||
```
|
||||
now:
|
||||
```lean
|
||||
#check @Member.head
|
||||
-- @Member.head : {α : Type u_1} → {a : α} → {as : List α} → Member a (a :: as)
|
||||
```
|
||||
|
||||
* Improve error message when constructor parameter universe level is too big.
|
||||
|
||||
* Add support for `for h : i in [start:stop] do .. ` where `h : i ∈ [start:stop]`. This feature is useful for proving
|
||||
termination of functions such as:
|
||||
```lean
|
||||
inductive Expr where
|
||||
| app (f : String) (args : Array Expr)
|
||||
|
||||
def Expr.size (e : Expr) : Nat := Id.run do
|
||||
match e with
|
||||
| app f args =>
|
||||
let mut sz := 1
|
||||
for h : i in [: args.size] do
|
||||
-- h.upper : i < args.size
|
||||
sz := sz + size (args.get ⟨i, h.upper⟩)
|
||||
return sz
|
||||
```
|
||||
|
||||
* Add tactic `case'`. It is similar to `case`, but does not admit the goal on failure.
|
||||
For example, the new tactic is useful when writing tactic scripts where we need to use `case'`
|
||||
at `first | ... | ...`, and we want to take the next alternative when `case'` fails.
|
||||
|
||||
* Add tactic macro
|
||||
```lean
|
||||
macro "stop" s:tacticSeq : tactic => `(repeat sorry)
|
||||
```
|
||||
See discussion on [Zulip](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Partial.20evaluation.20of.20a.20file).
|
||||
|
||||
* When displaying goals, we do not display inaccessible proposition names
|
||||
if they do not have forward dependencies. We still display their types.
|
||||
For example, the goal
|
||||
```lean
|
||||
case node.inl.node
|
||||
β : Type u_1
|
||||
b : BinTree β
|
||||
k : Nat
|
||||
v : β
|
||||
left : Tree β
|
||||
key : Nat
|
||||
value : β
|
||||
right : Tree β
|
||||
ihl : BST left → Tree.find? (Tree.insert left k v) k = some v
|
||||
ihr : BST right → Tree.find? (Tree.insert right k v) k = some v
|
||||
h✝ : k < key
|
||||
a✝³ : BST left
|
||||
a✝² : ForallTree (fun k v => k < key) left
|
||||
a✝¹ : BST right
|
||||
a✝ : ForallTree (fun k v => key < k) right
|
||||
⊢ BST left
|
||||
```
|
||||
is now displayed as
|
||||
```lean
|
||||
case node.inl.node
|
||||
β : Type u_1
|
||||
b : BinTree β
|
||||
k : Nat
|
||||
v : β
|
||||
left : Tree β
|
||||
key : Nat
|
||||
value : β
|
||||
right : Tree β
|
||||
ihl : BST left → Tree.find? (Tree.insert left k v) k = some v
|
||||
ihr : BST right → Tree.find? (Tree.insert right k v) k = some v
|
||||
: k < key
|
||||
: BST left
|
||||
: ForallTree (fun k v => k < key) left
|
||||
: BST right
|
||||
: ForallTree (fun k v => key < k) right
|
||||
⊢ BST left
|
||||
```
|
||||
|
||||
* The hypothesis name is now optional in the `by_cases` tactic.
|
||||
|
||||
* [Fix inconsistency between `syntax` and kind names](https://github.com/leanprover/lean4/issues/1090).
|
||||
The node kinds `numLit`, `charLit`, `nameLit`, `strLit`, and `scientificLit` are now called
|
||||
`num`, `char`, `name`, `str`, and `scientific` respectively. Example: we now write
|
||||
```lean
|
||||
macro_rules | `($n:num) => `("hello")
|
||||
```
|
||||
instead of
|
||||
```lean
|
||||
macro_rules | `($n:numLit) => `("hello")
|
||||
```
|
||||
|
||||
* (Experimental) New `checkpoint <tactic-seq>` tactic for big interactive proofs.
|
||||
|
||||
* Rename tactic `nativeDecide` => `native_decide`.
|
||||
|
||||
* Antiquotations are now accepted in any syntax. The `incQuotDepth` `syntax` parser is therefore obsolete and has been removed.
|
||||
|
||||
* Renamed tactic `nativeDecide` => `native_decide`.
|
||||
|
||||
* "Cleanup" local context before elaborating a `match` alternative right-hand-side. Examples:
|
||||
```lean
|
||||
example (x : Nat) : Nat :=
|
||||
match g x with
|
||||
| (a, b) => _ -- Local context does not contain the auxiliary `_discr := g x` anymore
|
||||
|
||||
example (x : Nat × Nat) (h : x.1 > 0) : f x > 0 := by
|
||||
match x with
|
||||
| (a, b) => _ -- Local context does not contain the `h✝ : x.fst > 0` anymore
|
||||
```
|
||||
|
||||
* Improve `let`-pattern (and `have`-pattern) macro expansion. In the following example,
|
||||
```lean
|
||||
example (x : Nat × Nat) : f x > 0 := by
|
||||
let (a, b) := x
|
||||
done
|
||||
```
|
||||
The resulting goal is now `... |- f (a, b) > 0` instead of `... |- f x > 0`.
|
||||
|
||||
* Add cross-compiled [aarch64 Linux](https://github.com/leanprover/lean4/pull/1066) and [aarch64 macOS](https://github.com/leanprover/lean4/pull/1076) releases.
|
||||
|
||||
* [Add tutorial-like examples to our documentation](https://github.com/leanprover/lean4/tree/master/doc/examples), rendered using LeanInk+Alectryon.
|
||||
|
||||
v4.0.0-m4 (23 March 2022)
|
||||
---------
|
||||
|
||||
* `simp` now takes user-defined simp-attributes. You can define a new `simp` attribute by creating a file (e.g., `MySimp.lean`) containing
|
||||
```lean
|
||||
import Lean
|
||||
open Lean.Meta
|
||||
|
||||
initialize my_ext : SimpExtension ← registerSimpAttr `my_simp "my own simp attribute"
|
||||
```
|
||||
If you don't neet to acces `my_ext`, you can also use the macro
|
||||
```lean
|
||||
import Lean
|
||||
|
||||
register_simp_attr my_simp "my own simp attribute"
|
||||
```
|
||||
Recall that the new `simp` attribute is not active in the Lean file where it was defined.
|
||||
Here is a small example using the new feature.
|
||||
```lean
|
||||
import MySimp
|
||||
|
||||
def f (x : Nat) := x + 2
|
||||
def g (x : Nat) := x + 1
|
||||
|
||||
@[my_simp] theorem f_eq : f x = x + 2 := rfl
|
||||
@[my_simp] theorem g_eq : g x = x + 1 := rfl
|
||||
|
||||
example : f x + g x = 2*x + 3 := by
|
||||
simp_arith [my_simp]
|
||||
```
|
||||
|
||||
* Extend `match` syntax: multiple left-hand-sides in a single alternative. Example:
|
||||
```lean
|
||||
def fib : Nat → Nat
|
||||
| 0 | 1 => 1
|
||||
| n+2 => fib n + fib (n+1)
|
||||
```
|
||||
This feature was discussed at [issue 371](https://github.com/leanprover/lean4/issues/371). It was implemented as a macro expansion. Thus, the following is accepted.
|
||||
```lean
|
||||
inductive StrOrNum where
|
||||
| S (s : String)
|
||||
| I (i : Int)
|
||||
|
||||
def StrOrNum.asString (x : StrOrNum) :=
|
||||
match x with
|
||||
| I a | S a => toString a
|
||||
```
|
||||
|
||||
|
||||
* Improve `#eval` command. Now, when it fails to synthesize a `Lean.MetaEval` instance for the result type, it reduces the type and tries again. The following example now works without additional annotations
|
||||
```lean
|
||||
def Foo := List Nat
|
||||
|
||||
def test (x : Nat) : Foo :=
|
||||
[x, x+1, x+2]
|
||||
|
||||
#eval test 4
|
||||
```
|
||||
|
||||
* `rw` tactic can now apply auto-generated equation theorems for a given definition. Example:
|
||||
```lean
|
||||
example (a : Nat) (h : n = 1) : [a].length = n := by
|
||||
rw [List.length]
|
||||
trace_state -- .. |- [].length + 1 = n
|
||||
rw [List.length]
|
||||
trace_state -- .. |- 0 + 1 = n
|
||||
rw [h]
|
||||
```
|
||||
|
||||
* [Fuzzy matching for auto completion](https://github.com/leanprover/lean4/pull/1023)
|
||||
|
||||
* Extend dot-notation `x.field` for arrow types. If type of `x` is an arrow, we look up for `Function.field`.
|
||||
For example, given `f : Nat → Nat` and `g : Nat → Nat`, `f.comp g` is now notation for `Function.comp f g`.
|
||||
|
||||
* The new `.<identifier>` notation is now also accepted where a function type is expected.
|
||||
```lean
|
||||
example (xs : List Nat) : List Nat := .map .succ xs
|
||||
example (xs : List α) : Std.RBTree α ord := xs.foldl .insert ∅
|
||||
```
|
||||
|
||||
* [Add code folding support to the language server](https://github.com/leanprover/lean4/pull/1014).
|
||||
|
||||
* Support notation `let <pattern> := <expr> | <else-case>` in `do` blocks.
|
||||
|
||||
* Remove support for "auto" `pure`. In the [Zulip thread](https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/for.2C.20unexpected.20need.20for.20type.20ascription/near/269083574), the consensus seemed to be that "auto" `pure` is more confusing than it's worth.
|
||||
|
||||
* Remove restriction in `congr` theorems that all function arguments on the left-hand-side must be free variables. For example, the following theorem is now a valid `congr` theorem.
|
||||
```lean
|
||||
@[congr]
|
||||
theorem dep_congr [DecidableEq ι] {p : ι → Set α} [∀ i, Inhabited (p i)] :
|
||||
∀ {i j} (h : i = j) (x : p i) (y : α) (hx : x = y), Pi.single (f := (p ·)) i x = Pi.single (f := (p ·)) j ⟨y, hx ▸ h ▸ x.2⟩ :=
|
||||
```
|
||||
|
||||
* [Partially applied congruence theorems.](https://github.com/leanprover/lean4/issues/988)
|
||||
|
||||
* Improve elaboration postponement heuristic when expected type is a metavariable. Lean now reduces the expected type before performing the test.
|
||||
|
||||
* [Remove deprecated leanpkg](https://github.com/leanprover/lean4/pull/985) in favor of [Lake](https://github.com/leanprover/lake) now bundled with Lean.
|
||||
|
||||
* Various improvements to go-to-definition & find-all-references accuracy.
|
||||
|
||||
* Auto generated congruence lemmas with support for casts on proofs and `Decidable` instances (see [whishlist](https://github.com/leanprover/lean4/issues/988)).
|
||||
|
||||
* Rename option `autoBoundImplicitLocal` => `autoImplicit`.
|
||||
|
||||
* [Relax auto-implicit restrictions](https://github.com/leanprover/lean4/pull/1011). The command `set_option relaxedAutoImplicit false` disables the relaxations.
|
||||
|
||||
* `contradiction` tactic now closes the goal if there is a `False.elim` application in the target.
|
||||
|
||||
* Renamed tatic `byCases` => `by_cases` (motivation: enforcing naming convention).
|
||||
|
||||
* Local instances occurring in patterns are now considered by the type class resolution procedure. Example:
|
||||
```lean
|
||||
def concat : List ((α : Type) × ToString α × α) → String
|
||||
| [] => ""
|
||||
| ⟨_, _, a⟩ :: as => toString a ++ concat as
|
||||
```
|
||||
|
||||
* Notation for providing the motive for `match` expressions has changed.
|
||||
before:
|
||||
```lean
|
||||
match x, rfl : (y : Nat) → x = y → Nat with
|
||||
| 0, h => ...
|
||||
| x+1, h => ...
|
||||
```
|
||||
now:
|
||||
```lean
|
||||
match (motive := (y : Nat) → x = y → Nat) x, rfl with
|
||||
| 0, h => ...
|
||||
| x+1, h => ...
|
||||
```
|
||||
With this change, the notation for giving names to equality proofs in `match`-expressions is not whitespace sensitive anymore. That is,
|
||||
we can now write
|
||||
```lean
|
||||
match h : sort.swap a b with
|
||||
| (r₁, r₂) => ... -- `h : sort.swap a b = (r₁, r₂)`
|
||||
```
|
||||
|
||||
* `(generalizing := true)` is the default behavior for `match` expressions even if the expected type is not a proposition. In the following example, we used to have to include `(generalizing := true)` manually.
|
||||
```lean
|
||||
inductive Fam : Type → Type 1 where
|
||||
| any : Fam α
|
||||
| nat : Nat → Fam Nat
|
||||
|
||||
example (a : α) (x : Fam α) : α :=
|
||||
match x with
|
||||
| Fam.any => a
|
||||
| Fam.nat n => n
|
||||
```
|
||||
|
||||
* We now use `PSum` (instead of `Sum`) when compiling mutually recursive definitions using well-founded recursion.
|
||||
|
||||
* Better support for parametric well-founded relations. See [issue #1017](https://github.com/leanprover/lean4/issues/1017). This change affects the low-level `termination_by'` hint because the fixed prefix of the function parameters in not "packed" anymore when constructing the well-founded relation type. For example, in the following definition, `as` is part of the fixed prefix, and is not packed anymore. In previous versions, the `termination_by'` term would be written as `measure fun ⟨as, i, _⟩ => as.size - i`
|
||||
```lean
|
||||
def sum (as : Array Nat) (i : Nat) (s : Nat) : Nat :=
|
||||
if h : i < as.size then
|
||||
sum as (i+1) (s + as.get ⟨i, h⟩)
|
||||
else
|
||||
s
|
||||
termination_by' measure fun ⟨i, _⟩ => as.size - i
|
||||
```
|
||||
|
||||
* Add `while <cond> do <do-block>`, `repeat <do-block>`, and `repeat <do-block> until <cond>` macros for `do`-block. These macros are based on `partial` definitions, and consequently are useful only for writing programs we don't want to prove anything about.
|
||||
|
||||
* Add `arith` option to `Simp.Config`, the macro `simp_arith` expands to `simp (config := { arith := true })`. Only `Nat` and linear arithmetic is currently supported. Example:
|
||||
```lean
|
||||
example : 0 < 1 + x ∧ x + y + 2 ≥ y + 1 := by
|
||||
simp_arith
|
||||
```
|
||||
|
||||
* Add `fail <string>?` tactic that always fail.
|
||||
|
||||
* Add support for acyclicity at dependent elimination. See [issue #1022](https://github.com/leanprover/lean4/issues/1022).
|
||||
|
||||
* Add `trace <string>` tactic for debugging purposes.
|
||||
|
||||
* Add nontrivial `SizeOf` instance for types `Unit → α`, and add support for them in the auto-generated `SizeOf` instances for user-defined inductive types. For example, given the inductive datatype
|
||||
```lean
|
||||
inductive LazyList (α : Type u) where
|
||||
| nil : LazyList α
|
||||
| cons (hd : α) (tl : LazyList α) : LazyList α
|
||||
| delayed (t : Thunk (LazyList α)) : LazyList α
|
||||
```
|
||||
we now have `sizeOf (LazyList.delayed t) = 1 + sizeOf t` instead of `sizeOf (LazyList.delayed t) = 2`.
|
||||
|
||||
* Add support for guessing (very) simple well-founded relations when proving termination. For example, the following function does not require a `termination_by` annotation anymore.
|
||||
```lean
|
||||
def Array.insertAtAux (i : Nat) (as : Array α) (j : Nat) : Array α :=
|
||||
if h : i < j then
|
||||
let as := as.swap! (j-1) j;
|
||||
insertAtAux i as (j-1)
|
||||
else
|
||||
as
|
||||
```
|
||||
|
||||
* Add support for `for h : x in xs do ...` notation where `h : x ∈ xs`. This is mainly useful for showing termination.
|
||||
|
||||
* Auto implicit behavior changed for inductive families. An auto implicit argument occurring in inductive family index is also treated as an index (IF it is not fixed, see next item). For example
|
||||
```lean
|
||||
inductive HasType : Index n → Vector Ty n → Ty → Type where
|
||||
```
|
||||
is now interpreted as
|
||||
```lean
|
||||
inductive HasType : {n : Nat} → Index n → Vector Ty n → Ty → Type where
|
||||
```
|
||||
|
||||
* To make the previous feature more convenient to use, we promote a fixed prefix of inductive family indices to parameters. For example, the following declaration is now accepted by Lean
|
||||
```lean
|
||||
inductive Lst : Type u → Type u
|
||||
| nil : Lst α
|
||||
| cons : α → Lst α → Lst α
|
||||
```
|
||||
and `α` in `Lst α` is a parameter. The actual number of parameters can be inspected using the command `#print Lst`. This feature also makes sure we still accept the declaration
|
||||
```lean
|
||||
inductive Sublist : List α → List α → Prop
|
||||
| slnil : Sublist [] []
|
||||
| cons l₁ l₂ a : Sublist l₁ l₂ → Sublist l₁ (a :: l₂)
|
||||
| cons2 l₁ l₂ a : Sublist l₁ l₂ → Sublist (a :: l₁) (a :: l₂)
|
||||
```
|
||||
|
||||
* Added auto implicit "chaining". Unassigned metavariables occurring in the auto implicit types now become new auto implicit locals. Consider the following example:
|
||||
```lean
|
||||
inductive HasType : Fin n → Vector Ty n → Ty → Type where
|
||||
| stop : HasType 0 (ty :: ctx) ty
|
||||
| pop : HasType k ctx ty → HasType k.succ (u :: ctx) ty
|
||||
```
|
||||
`ctx` is an auto implicit local in the two constructors, and it has type `ctx : Vector Ty ?m`. Without auto implicit "chaining", the metavariable `?m` will remain unassigned. The new feature creates yet another implicit local `n : Nat` and assigns `n` to `?m`. So, the declaration above is shorthand for
|
||||
```lean
|
||||
inductive HasType : {n : Nat} → Fin n → Vector Ty n → Ty → Type where
|
||||
| stop : {ty : Ty} → {n : Nat} → {ctx : Vector Ty n} → HasType 0 (ty :: ctx) ty
|
||||
| pop : {n : Nat} → {k : Fin n} → {ctx : Vector Ty n} → {ty : Ty} → HasType k ctx ty → HasType k.succ (u :: ctx) ty
|
||||
```
|
||||
|
||||
* Eliminate auxiliary type annotations (e.g, `autoParam` and `optParam`) from recursor minor premises and projection declarations. Consider the following example
|
||||
```lean
|
||||
structure A :=
|
||||
x : Nat
|
||||
h : x = 1 := by trivial
|
||||
|
||||
example (a : A) : a.x = 1 := by
|
||||
have aux := a.h
|
||||
-- `aux` has now type `a.x = 1` instead of `autoParam (a.x = 1) auto✝`
|
||||
exact aux
|
||||
|
||||
example (a : A) : a.x = 1 := by
|
||||
cases a with
|
||||
| mk x h =>
|
||||
-- `h` has now type `x = 1` instead of `autoParam (x = 1) auto✝`
|
||||
assumption
|
||||
```
|
||||
|
||||
* We now accept overloaded notation in patterns, but we require the set of pattern variables in each alternative to be the same. Example:
|
||||
```lean
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
|
||||
infix:67 " :: " => Vector.cons -- Overloading the `::` notation
|
||||
|
||||
def head1 (x : List α) (h : x ≠ []) : α :=
|
||||
match x with
|
||||
| a :: as => a -- `::` is `List.cons` here
|
||||
|
||||
def head2 (x : Vector α (n+1)) : α :=
|
||||
match x with
|
||||
| a :: as => a -- `::` is `Vector.cons` here
|
||||
```
|
||||
|
||||
* New notation `.<identifier>` based on Swift. The namespace is inferred from the expected type. See [issue #944](https://github.com/leanprover/lean4/issues/944). Examples:
|
||||
```lean
|
||||
def f (x : Nat) : Except String Nat :=
|
||||
if x > 0 then
|
||||
.ok x
|
||||
else
|
||||
.error "x is zero"
|
||||
|
||||
namespace Lean.Elab
|
||||
open Lsp
|
||||
|
||||
def identOf : Info → Option (RefIdent × Bool)
|
||||
| .ofTermInfo ti => match ti.expr with
|
||||
| .const n .. => some (.const n, ti.isBinder)
|
||||
| .fvar id .. => some (.fvar id, ti.isBinder)
|
||||
| _ => none
|
||||
| .ofFieldInfo fi => some (.const fi.projName, false)
|
||||
| _ => none
|
||||
|
||||
def isImplicit (bi : BinderInfo) : Bool :=
|
||||
bi matches .implicit
|
||||
|
||||
end Lean.Elab
|
||||
```
|
||||
|
||||
9
default.nix
Normal file
9
default.nix
Normal file
@@ -0,0 +1,9 @@
|
||||
# used for `nix-shell https://github.com/leanprover/lean4/archive/master.tar.gz -A nix`
|
||||
{ nix = (import ./shell.nix {}).nix; } //
|
||||
(import (
|
||||
fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/c75e76f80c57784a6734356315b306140646ee84.tar.gz";
|
||||
sha256 = "071aal00zp2m9knnhddgr2wqzlx6i6qa1263lv1y7bdn2w20h10h"; }
|
||||
) {
|
||||
src = ./.;
|
||||
}).defaultNix
|
||||
@@ -1,4 +1,5 @@
|
||||
open Batteries
|
||||
import Std
|
||||
open Std
|
||||
open Lean
|
||||
|
||||
inductive BoolExpr where
|
||||
@@ -103,9 +104,9 @@ syntax entry := ident " ↦ " term:max
|
||||
syntax entry,* "⊢" term : term
|
||||
|
||||
macro_rules
|
||||
| `( $[$xs ↦ $vs],* ⊢ $p) =>
|
||||
| `( $[$xs:ident ↦ $vs:term],* ⊢ $p:term ) =>
|
||||
let xs := xs.map fun x => quote x.getId.toString
|
||||
`(denote (List.toAssocList [$[($xs, $vs)],*]) `[BExpr| $p])
|
||||
`(denote (List.toAssocList [$[( $xs , $vs )],*]) `[BExpr| $p])
|
||||
|
||||
#check b ↦ true ⊢ b ∨ b
|
||||
#eval a ↦ false, b ↦ false ⊢ b ∨ a
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Developer Documentation and Examples
|
||||
|
||||
This directory contains documentation that describes how to work on
|
||||
Lean itself, as well as examples that are included in documentation
|
||||
that's hosted on the Lean website. The `make` directory contains
|
||||
information on building Lean, and the `dev` directory describes how to
|
||||
work on Lean.
|
||||
|
||||
The [documentation section](https://lean-lang.org/documentation) has
|
||||
links to documentation that describes how to use Lean itself.
|
||||
83
doc/SUMMARY.md
Normal file
83
doc/SUMMARY.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Summary
|
||||
|
||||
- [What is Lean](./whatIsLean.md)
|
||||
- [Tour of Lean](./tour.md)
|
||||
- [Setting Up Lean](./quickstart.md)
|
||||
- [Extended Setup Notes](./setup.md)
|
||||
- [Theorem Proving in Lean](./tpil.md)
|
||||
- [Functional Programming in Lean](fplean.md)
|
||||
- [Examples](./examples.md)
|
||||
- [Palindromes](examples/palindromes.lean.md)
|
||||
- [Binary Search Trees](examples/bintree.lean.md)
|
||||
- [A Certified Type Checker](examples/tc.lean.md)
|
||||
- [The Well-Typed Interpreter](examples/interp.lean.md)
|
||||
- [Dependent de Bruijn Indices](examples/deBruijn.lean.md)
|
||||
- [Parametric Higher-Order Abstract Syntax](examples/phoas.lean.md)
|
||||
|
||||
# Language Manual
|
||||
<!-- - [Using Lean](./using_lean.md) -->
|
||||
<!-- - [Lexical Structure](./lexical_structure.md) -->
|
||||
<!-- - [Expressions](./expressions.md) -->
|
||||
<!-- - [Declarations](./declarations.md) -->
|
||||
- [Organizational features](./organization.md)
|
||||
- [Sections](./sections.md)
|
||||
- [Namespaces](./namespaces.md)
|
||||
- [Implicit Arguments](./implicit.md)
|
||||
- [Auto Bound Implicit Arguments](./autobound.md)
|
||||
<!-- - [Dependent Types](./deptypes.md) -->
|
||||
<!-- - [Simple Type Theory](./simptypes.md) -->
|
||||
<!-- - [Types as objects](./typeobjs.md) -->
|
||||
<!-- - [Function Abstraction and Evaluation](./funabst.md) -->
|
||||
<!-- - [Introducing Definitions](./introdef.md) -->
|
||||
<!-- - [What makes dependent type theory dependent?](./dep.md) -->
|
||||
<!-- - [Tactics](./tactics.md) -->
|
||||
- [Syntax Extensions](./syntax.md)
|
||||
- [The `do` Notation](./do.md)
|
||||
- [String Interpolation](./stringinterp.md)
|
||||
- [User-Defined Notation](./notation.md)
|
||||
- [Macro Overview](./macro_overview.md)
|
||||
- [Elaborators](./elaborators.md)
|
||||
- [Examples](./syntax_examples.md)
|
||||
- [Balanced Parentheses](./syntax_example.md)
|
||||
- [Arithmetic DSL](./metaprogramming-arith.md)
|
||||
- [Declaring New Types](./decltypes.md)
|
||||
- [Enumerated Types](./enum.md)
|
||||
- [Inductive Types](./inductive.md)
|
||||
- [Structures](./struct.md)
|
||||
- [Type classes](./typeclass.md)
|
||||
- [Unification Hints](./unifhint.md)
|
||||
- [Builtin Types](./builtintypes.md)
|
||||
- [Natural number](./nat.md)
|
||||
- [Integer](./int.md)
|
||||
- [Fixed precision unsigned integer](./uint.md)
|
||||
- [Float](./float.md)
|
||||
- [Array](./array.md)
|
||||
- [List](./list.md)
|
||||
- [Character](./char.md)
|
||||
- [String](./string.md)
|
||||
- [Option](./option.md)
|
||||
- [Thunk](./thunk.md)
|
||||
- [Task and Thread](./task.md)
|
||||
- [Functions](./functions.md)
|
||||
|
||||
# Other
|
||||
|
||||
- [Frequently Asked Questions](./faq.md)
|
||||
- [Significant Changes from Lean 3](./lean3changes.md)
|
||||
- [Syntax Highlighting Lean in LaTeX](./syntax_highlight_in_latex.md)
|
||||
|
||||
# Development
|
||||
|
||||
- [Development Guide](./dev/index.md)
|
||||
- [Building Lean](./make/index.md)
|
||||
- [Ubuntu Setup](./make/ubuntu.md)
|
||||
- [macOS Setup](./make/osx-10.9.md)
|
||||
- [Windows MSYS2 Setup](./make/msys2.md)
|
||||
- [Windows with WSL](./make/wsl.md)
|
||||
- [Nix Setup (*Experimental*)](./make/nix.md)
|
||||
- [Bootstrapping](./dev/bootstrap.md)
|
||||
- [Testing](./dev/testing.md)
|
||||
- [Debugging](./dev/debugging.md)
|
||||
- [Commit Convention](./dev/commit_convention.md)
|
||||
- [Building This Manual](./dev/mdbook.md)
|
||||
- [Foreign Function Interface](./dev/ffi.md)
|
||||
786
doc/alectryon.css
Normal file
786
doc/alectryon.css
Normal file
@@ -0,0 +1,786 @@
|
||||
@charset "UTF-8";
|
||||
/*
|
||||
Copyright © 2019 Clément Pit-Claudel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*******************************/
|
||||
/* CSS reset for .alectryon-io */
|
||||
/*******************************/
|
||||
|
||||
.content {
|
||||
/*
|
||||
Use `initial` instead of `contents` to avoid a browser bug which removes
|
||||
the element from the accessibility tree.
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/display#display_contents
|
||||
*/
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.alectryon-io blockquote {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.alectryon-io blockquote:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-io label {
|
||||
display: inline;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.alectryon-io a {
|
||||
text-decoration: none !important;
|
||||
font-style: oblique !important;
|
||||
color: unset;
|
||||
}
|
||||
|
||||
/* Undo <small> and <blockquote>, added to improve RSS rendering. */
|
||||
|
||||
.alectryon-io small.alectryon-output,
|
||||
.alectryon-io small.alectryon-type-info {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.alectryon-io blockquote.alectryon-goal,
|
||||
.alectryon-io blockquote.alectryon-message {
|
||||
font-weight: normal;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
/***************/
|
||||
/* Main styles */
|
||||
/***************/
|
||||
|
||||
.alectryon-coqdoc .doc .code,
|
||||
.alectryon-coqdoc .doc .comment,
|
||||
.alectryon-coqdoc .doc .inlinecode,
|
||||
.alectryon-mref,
|
||||
.alectryon-block, .alectryon-io,
|
||||
.alectryon-toggle-label, .alectryon-banner {
|
||||
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
|
||||
font-size: 0.875em;
|
||||
font-feature-settings: "COQX" 1 /* Coq ligatures */, "XV00" 1 /* Legacy */, "calt" 1 /* Fallback */;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.alectryon-io, .alectryon-block, .alectryon-toggle-label, .alectryon-banner {
|
||||
overflow: visible;
|
||||
overflow-wrap: break-word;
|
||||
position: relative;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/*
|
||||
CoqIDE doesn't turn off the unicode bidirectional algorithm (and PG simply
|
||||
respects the user's `bidi-display-reordering` setting), so don't turn it off
|
||||
here either. But beware unexpected results like `Definition test_אב := 0.`
|
||||
|
||||
.alectryon-io span {
|
||||
direction: ltr;
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
|
||||
In any case, make an exception for comments:
|
||||
|
||||
.highlight .c {
|
||||
direction: embed;
|
||||
unicode-bidi: initial;
|
||||
}
|
||||
*/
|
||||
|
||||
.alectryon-mref,
|
||||
.alectryon-mref-marker {
|
||||
align-self: center;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-size: 80%;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 0 1pt black;
|
||||
padding: 1pt 0.3em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.alectryon-block .alectryon-mref-marker,
|
||||
.alectryon-io .alectryon-mref-marker {
|
||||
user-select: none;
|
||||
margin: -0.25em 0 -0.25em 0.5em;
|
||||
}
|
||||
|
||||
.alectryon-inline .alectryon-mref-marker {
|
||||
margin: -0.25em 0.15em -0.25em 0.625em; /* 625 = 0.5em / 80% */
|
||||
}
|
||||
|
||||
.alectryon-mref {
|
||||
color: inherit;
|
||||
margin: -0.5em 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-goal:target .goal-separator .alectryon-mref-marker,
|
||||
:target > .alectryon-mref-marker {
|
||||
animation: blink 0.2s step-start 0s 3 normal none;
|
||||
background-color: #fcaf3e;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
box-shadow: 0 0 0 3pt #fcaf3e, 0 0 0 4pt black;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.alectryon-toggle,
|
||||
.alectryon-io .alectryon-extra-goal-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-bubble,
|
||||
.alectryon-io label,
|
||||
.alectryon-toggle-label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.alectryon-toggle-label {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-input {
|
||||
padding: 0.1em 0; /* Enlarge the hitbox slightly to fill interline gaps */
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-token {
|
||||
white-space: pre-wrap;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-input {
|
||||
/* FIXME if keywords were ‘bolder’ we wouldn't need !important */
|
||||
font-weight: bold !important; /* Use !important to avoid a * selector */
|
||||
}
|
||||
|
||||
.alectryon-bubble:before,
|
||||
.alectryon-toggle-label:before,
|
||||
.alectryon-io label.alectryon-input:after,
|
||||
.alectryon-io .alectryon-goal > label:before {
|
||||
border: 1px solid #babdb6;
|
||||
border-radius: 1em;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
height: 0.25em;
|
||||
margin-bottom: 0.15em;
|
||||
vertical-align: middle;
|
||||
width: 0.75em;
|
||||
}
|
||||
|
||||
.alectryon-toggle-label:before,
|
||||
.alectryon-io .alectryon-goal > label:before {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-goal > label:before {
|
||||
margin-top: 0.125em;
|
||||
}
|
||||
|
||||
.alectryon-io label.alectryon-input {
|
||||
padding-right: 1em; /* Prevent line wraps before the checkbox bubble */
|
||||
}
|
||||
|
||||
.alectryon-io label.alectryon-input:after {
|
||||
margin-left: 0.25em;
|
||||
margin-right: -1em; /* Compensate for the anti-wrapping space */
|
||||
}
|
||||
|
||||
.alectryon-failed {
|
||||
/* Underlines are broken in Chrome (they reset at each element boundary)… */
|
||||
/* text-decoration: red wavy underline; */
|
||||
/* … but it isn't too noticeable with dots */
|
||||
text-decoration: red dotted underline;
|
||||
text-decoration-skip-ink: none;
|
||||
/* Chrome prints background images in low resolution, yielding a blurry underline */
|
||||
/* background: bottom / 0.3em auto repeat-x url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyLjY0NiAxLjg1MiIgaGVpZ2h0PSI4IiB3aWR0aD0iMTAiPjxwYXRoIGQ9Ik0wIC4yNjVjLjc5NCAwIC41MyAxLjMyMiAxLjMyMyAxLjMyMi43OTQgMCAuNTMtMS4zMjIgMS4zMjMtMS4zMjIiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmVkIiBzdHJva2Utd2lkdGg9Ii41MjkiLz48L3N2Zz4=); */
|
||||
}
|
||||
|
||||
/* Wrapping :hover rules in a media query ensures that tapping a Coq sentence
|
||||
doesn't trigger its :hover state (otherwise, on mobile, tapping a sentence to
|
||||
hide its output causes it to remain visible (its :hover state gets triggered.
|
||||
We only do it for the default style though, since other styles don't put the
|
||||
output over the main text, so showing too much is not an issue. */
|
||||
@media (any-hover: hover) {
|
||||
.alectryon-bubble:hover:before,
|
||||
.alectryon-toggle-label:hover:before,
|
||||
.alectryon-io label.alectryon-input:hover:after {
|
||||
background: #eeeeec;
|
||||
}
|
||||
|
||||
.alectryon-io label.alectryon-input:hover {
|
||||
text-decoration: underline dotted #babdb6;
|
||||
text-shadow: 0 0 1px rgb(46, 52, 54, 0.3); /* #2e3436 + opacity */
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper {
|
||||
z-index: 2; /* Place hovered goals above .alectryon-sentence.alectryon-target ones */
|
||||
}
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + .alectryon-toggle-label:before,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked + label.alectryon-input:after,
|
||||
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal > label:before {
|
||||
background-color: #babdb6;
|
||||
border-color: #babdb6;
|
||||
}
|
||||
|
||||
/* Disable clicks on sentences when the document-wide toggle is set. */
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input {
|
||||
cursor: unset;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Hide individual checkboxes when the document-wide toggle is set. */
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* .alectryon-output is displayed by toggles, :hover, and .alectryon-target rules */
|
||||
.alectryon-io .alectryon-output {
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
padding: 0.25em 0;
|
||||
overflow: visible; /* Let box-shadows overflow */
|
||||
z-index: 1; /* Default to an index lower than that used by :hover */
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper.full-width {
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info .goal-separator {
|
||||
height: unset;
|
||||
margin-top: 0em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
box-sizing: border-box;
|
||||
bottom: 100%;
|
||||
position: absolute;
|
||||
/*padding: 0.25em 0;*/
|
||||
visibility: hidden;
|
||||
overflow: visible; /* Let box-shadows overflow */
|
||||
z-index: 1; /* Default to an index lower than that used by :hover */
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper .alectryon-type-info .alectryon-goal.alectryon-docstring {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
@media (any-hover: hover) { /* See note above about this @media query */
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alectryon-io.output-hidden .alectryon-sentence:hover .alectryon-output:not(:hover) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.alectryon-io.type-info-hidden .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info,
|
||||
.alectryon-io.type-info-hidden .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
/*visibility: hidden !important;*/
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
visibility: visible;
|
||||
transition-delay: 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Indicate active (hovered or targeted) goals with a shadow. */
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-messages,
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-messages,
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-goals,
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-goals,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
box-shadow: 2px 2px 2px gray;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goals .alectryon-goal .goal-hyps {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goals .alectryon-extra-goal-toggle:not(:checked) + .alectryon-goal label.goal-separator hr {
|
||||
/* Dashes indicate that the hypotheses are hidden */
|
||||
border-top-style: dashed;
|
||||
}
|
||||
|
||||
|
||||
/* Show just a small preview of the other goals; this is undone by the
|
||||
"extra-goal" toggle and by :hover and .alectryon-target in windowed mode. */
|
||||
.alectryon-io .alectryon-extra-goals .alectryon-goal .goal-conclusion {
|
||||
max-height: 5.2em;
|
||||
overflow-y: auto;
|
||||
/* Combining ‘overflow-y: auto’ with ‘display: inline-block’ causes extra space
|
||||
to be added below the box. ‘vertical-align: middle’ gets rid of it. */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-goals,
|
||||
.alectryon-io .alectryon-messages {
|
||||
background: #f6f7f6;
|
||||
/*border: thin solid #d3d7cf; /* Convenient when pre's background is already #EEE */
|
||||
display: block;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-message::before {
|
||||
content: '';
|
||||
float: right;
|
||||
/* etc/svg/square-bubble-xl.svg */
|
||||
background: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 3.704 3.704' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill-rule='evenodd' stroke='%23000' stroke-width='.264'%3E%3Cpath d='M.794.934h2.115M.794 1.463h1.455M.794 1.992h1.852'/%3E%3C/g%3E%3Cpath d='M.132.14v2.646h.794v.661l.926-.661h1.72V.14z' fill='none' stroke='%23000' stroke-width='.265'/%3E%3C/svg%3E") top right no-repeat;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container {
|
||||
width: unset;
|
||||
}
|
||||
|
||||
/* Show goals when a toggle is set */
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input + .alectryon-output,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output {
|
||||
display: block;
|
||||
position: static;
|
||||
width: unset;
|
||||
background: unset; /* Override the backgrounds set in floating in windowed mode */
|
||||
padding: 0.25em 0; /* Re-assert so that later :hover rules don't override this padding */
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input + .alectryon-output .goal-hyps,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output .goal-hyps {
|
||||
/* Overridden back in windowed style */
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence .alectryon-output > div,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output > div {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-hyps {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-conclusion {
|
||||
max-height: unset;
|
||||
overflow-y: unset;
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence > .alectryon-toggle ~ .alectryon-wsp,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-wsp {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-messages,
|
||||
.alectryon-io .alectryon-message,
|
||||
.alectryon-io .alectryon-goals,
|
||||
.alectryon-io .alectryon-goal,
|
||||
.alectryon-io .goal-hyps > span,
|
||||
.alectryon-io .goal-conclusion {
|
||||
border-radius: 0.15em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-goal,
|
||||
.alectryon-io .alectryon-message {
|
||||
align-items: center;
|
||||
background: #f6f7f6;
|
||||
border: 0em;
|
||||
display: block;
|
||||
flex-direction: column;
|
||||
margin: 0.25em;
|
||||
padding: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps {
|
||||
align-content: space-around;
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
flex-flow: column nowrap; /* re-stated in windowed mode */
|
||||
justify-content: space-around;
|
||||
/* LATER use a ‘gap’ property instead of margins once supported */
|
||||
margin: -0.15em -0.25em; /* -0.15em to cancel the item spacing */
|
||||
padding-bottom: 0.35em; /* 0.5em-0.15em to cancel the 0.5em of .goal-separator */
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > br {
|
||||
display: none; /* Only for RSS readers */
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span,
|
||||
.alectryon-io .goal-conclusion {
|
||||
/*background: #eeeeec;*/
|
||||
display: inline-block;
|
||||
padding: 0.15em 0.35em;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span {
|
||||
align-items: baseline;
|
||||
display: inline-flex;
|
||||
margin: 0.15em 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-block var,
|
||||
.alectryon-inline var,
|
||||
.alectryon-io .goal-hyps > span > var {
|
||||
font-weight: 600;
|
||||
font-style: unset;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span > var {
|
||||
/* Shrink the list of names, but let it grow as long as space is available. */
|
||||
flex-basis: min-content;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span b {
|
||||
font-weight: 600;
|
||||
margin: 0 0 0 0.5em;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.alectryon-io .hyp-body,
|
||||
.alectryon-io .hyp-type {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-separator {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 1em; /* Fixed height to ignore goal name and markers */
|
||||
margin-top: -0.5em; /* Compensated in .goal-hyps when shown */
|
||||
}
|
||||
|
||||
.alectryon-io .goal-separator hr {
|
||||
border: none;
|
||||
border-top: thin solid #555753;
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-separator .goal-name {
|
||||
font-size: 0.75em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
/**********/
|
||||
/* Banner */
|
||||
/**********/
|
||||
|
||||
.alectryon-banner {
|
||||
background: #eeeeec;
|
||||
border: 1px solid #babcbd;
|
||||
font-size: 0.75em;
|
||||
padding: 0.25em;
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.alectryon-banner a {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.alectryon-banner kbd {
|
||||
background: #d3d7cf;
|
||||
border-radius: 0.15em;
|
||||
border: 1px solid #babdb6;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-family: inherit;
|
||||
font-size: 0.9em;
|
||||
height: 1.3em;
|
||||
line-height: 1.2em;
|
||||
margin: -0.25em 0;
|
||||
padding: 0 0.25em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/**********/
|
||||
/* Toggle */
|
||||
/**********/
|
||||
|
||||
.alectryon-toggle-label {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/******************/
|
||||
/* Floating style */
|
||||
/******************/
|
||||
|
||||
/* If there's space, display goals to the right of the code, not below it. */
|
||||
@media (min-width: 80rem) {
|
||||
/* Unlike the windowed case, we don't want to move output blocks to the side
|
||||
when they are both :checked and -targeted, since it gets confusing as
|
||||
things jump around; hence the commented-output part of the selector,
|
||||
which would otherwise increase specificity */
|
||||
.alectryon-floating .alectryon-sentence.alectryon-target /* > .alectryon-toggle ~ */ .alectryon-output,
|
||||
.alectryon-floating .alectryon-sentence:hover .alectryon-output {
|
||||
top: 0;
|
||||
left: 100%;
|
||||
right: -100%;
|
||||
padding: 0 0.5em;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.alectryon-floating .alectryon-output {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.alectryon-floating .alectryon-sentence:hover .alectryon-output {
|
||||
background: white; /* Ensure that short goals hide long ones */
|
||||
}
|
||||
|
||||
/* This odd margin-bottom property prevents the sticky div from bumping
|
||||
against the bottom of its container (.alectryon-output). The alternative
|
||||
would be enlarging .alectryon-output, but that would cause overflows,
|
||||
enlarging scrollbars and yielding scrolling towards the bottom of the
|
||||
page. Doing things this way instead makes it possible to restrict
|
||||
.alectryon-output to a reasonable size (100%, through top = bottom = 0).
|
||||
See also https://stackoverflow.com/questions/43909940/. */
|
||||
/* See note on specificity above */
|
||||
.alectryon-floating .alectryon-sentence.alectryon-target /* > .alectryon-toggle ~ */ .alectryon-output > div,
|
||||
.alectryon-floating .alectryon-sentence:hover .alectryon-output > div {
|
||||
margin-bottom: -200%;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.alectryon-floating .alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence .alectryon-output > div,
|
||||
.alectryon-floating .alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output > div {
|
||||
margin-bottom: unset; /* Undo the margin */
|
||||
}
|
||||
|
||||
/* Float underneath the current fragment
|
||||
@media (max-width: 80rem) {
|
||||
.alectryon-floating .alectryon-output {
|
||||
top: 100%;
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* Multi-pane style */
|
||||
/********************/
|
||||
|
||||
.alectryon-windowed {
|
||||
border: 0 solid #2e3436;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-sentence:hover .alectryon-output {
|
||||
background: white; /* Ensure that short goals hide long ones */
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-output {
|
||||
position: fixed; /* Overwritten by the ‘:checked’ rules */
|
||||
}
|
||||
|
||||
/* See note about specificity below */
|
||||
.alectryon-windowed .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-windowed .alectryon-sentence.alectryon-target > .alectryon-toggle ~ .alectryon-output {
|
||||
padding: 0.5em;
|
||||
overflow-y: auto; /* Windowed contents may need to scroll */
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-messages,
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-messages,
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-goals,
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-goals {
|
||||
box-shadow: none; /* A shadow is unnecessary here and incompatible with overflow-y set to auto */
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .goal-hyps {
|
||||
/* Restated to override the :checked style */
|
||||
flex-flow: column nowrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
|
||||
.alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-extra-goals .alectryon-goal .goal-conclusion
|
||||
/* Like .alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-conclusion */ {
|
||||
max-height: unset;
|
||||
overflow-y: unset;
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-output > div {
|
||||
display: flex; /* Put messages after goals */
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
/*********************/
|
||||
/* Standalone styles */
|
||||
/*********************/
|
||||
|
||||
.alectryon-standalone {
|
||||
font-family: 'IBM Plex Serif', 'PT Serif', 'Merriweather', 'DejaVu Serif', serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 50rem) {
|
||||
html.alectryon-standalone {
|
||||
/* Prevent flickering when hovering a block causes scrollbars to appear. */
|
||||
margin-left: calc(100vw - 100%);
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Coqdoc */
|
||||
|
||||
.alectryon-coqdoc .doc .code,
|
||||
.alectryon-coqdoc .doc .inlinecode,
|
||||
.alectryon-coqdoc .doc .comment {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.alectryon-coqdoc .doc .comment {
|
||||
color: #eeeeec;
|
||||
}
|
||||
|
||||
.alectryon-coqdoc .doc .paragraph {
|
||||
height: 0.75em;
|
||||
}
|
||||
|
||||
/* Centered, Floating */
|
||||
|
||||
.alectryon-standalone .alectryon-centered,
|
||||
.alectryon-standalone .alectryon-floating {
|
||||
max-width: 50rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 80rem) {
|
||||
.alectryon-standalone .alectryon-floating {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-floating > * {
|
||||
width: 50%;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Windowed */
|
||||
|
||||
.alectryon-standalone .alectryon-windowed {
|
||||
display: block;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed > * {
|
||||
/* Override properties of docutils_basic.css */
|
||||
margin-left: 0;
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-io {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* No need to predicate the ‘:hover’ rules below on ‘:not(:checked)’, since ‘left’,
|
||||
‘right’, ‘top’, and ‘bottom’ will be inactived by the :checked rules setting
|
||||
‘position’ to ‘static’ */
|
||||
|
||||
|
||||
/* Specificity: We want the output to stay inline when hovered while unfolded
|
||||
(:checked), but we want it to move when it's targeted (i.e. when the user
|
||||
is browsing goals one by one using the keyboard, in which case we want to
|
||||
goals to appear in consistent locations). The selectors below ensure
|
||||
that :hover < :checked < -targeted in terms of specificity. */
|
||||
/* LATER: Reimplement this stuff with CSS variables */
|
||||
.alectryon-windowed .alectryon-sentence.alectryon-target > .alectryon-toggle ~ .alectryon-output {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 60rem) {
|
||||
.alectryon-standalone .alectryon-windowed {
|
||||
border-right-width: thin;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 50%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-output {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 60rem) {
|
||||
.alectryon-standalone .alectryon-windowed {
|
||||
border-bottom-width: 1px;
|
||||
bottom: 40%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-output {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 60%;
|
||||
}
|
||||
}
|
||||
190
doc/alectryon.js
Normal file
190
doc/alectryon.js
Normal file
@@ -0,0 +1,190 @@
|
||||
var Alectryon;
|
||||
(function(Alectryon) {
|
||||
(function (slideshow) {
|
||||
function anchor(sentence) { return "#" + sentence.id; }
|
||||
|
||||
function current_sentence() { return slideshow.sentences[slideshow.pos]; }
|
||||
|
||||
function unhighlight() {
|
||||
var sentence = current_sentence();
|
||||
if (sentence) sentence.classList.remove("alectryon-target");
|
||||
slideshow.pos = -1;
|
||||
}
|
||||
|
||||
function highlight(sentence) {
|
||||
sentence.classList.add("alectryon-target");
|
||||
}
|
||||
|
||||
function scroll(sentence) {
|
||||
// Put the top of the current fragment close to the top of the
|
||||
// screen, but scroll it out of view if showing it requires pushing
|
||||
// the sentence past half of the screen. If sentence is already in
|
||||
// a reasonable position, don't move.
|
||||
var parent = sentence.parentElement;
|
||||
/* We want to scroll the whole document, so start at root… */
|
||||
while (parent && !parent.classList.contains("alectryon-root"))
|
||||
parent = parent.parentElement;
|
||||
/* … and work up from there to find a scrollable element.
|
||||
parent.scrollHeight can be greater than parent.clientHeight
|
||||
without showing scrollbars, so we add a 10px buffer. */
|
||||
while (parent && parent.scrollHeight <= parent.clientHeight + 10)
|
||||
parent = parent.parentElement;
|
||||
/* <body> and <html> elements can have their client rect overflow
|
||||
* the window if their height is unset, so scroll the window
|
||||
* instead */
|
||||
if (parent && (parent.nodeName == "BODY" || parent.nodeName == "HTML"))
|
||||
parent = null;
|
||||
|
||||
var rect = function(e) { return e.getBoundingClientRect(); };
|
||||
var parent_box = parent ? rect(parent) : { y: 0, height: window.innerHeight },
|
||||
sentence_y = rect(sentence).y - parent_box.y,
|
||||
fragment_y = rect(sentence.parentElement).y - parent_box.y;
|
||||
|
||||
// The assertion below sometimes fails for the first element in a block.
|
||||
// console.assert(sentence_y >= fragment_y);
|
||||
|
||||
if (sentence_y < 0.1 * parent_box.height ||
|
||||
sentence_y > 0.7 * parent_box.height) {
|
||||
(parent || window).scrollBy(
|
||||
0, Math.max(sentence_y - 0.5 * parent_box.height,
|
||||
fragment_y - 0.1 * parent_box.height));
|
||||
}
|
||||
}
|
||||
|
||||
function highlighted(pos) {
|
||||
return slideshow.pos == pos;
|
||||
}
|
||||
|
||||
function navigate(pos, inhibitScroll) {
|
||||
unhighlight();
|
||||
slideshow.pos = Math.min(Math.max(pos, 0), slideshow.sentences.length - 1);
|
||||
var sentence = current_sentence();
|
||||
highlight(sentence);
|
||||
if (!inhibitScroll)
|
||||
scroll(sentence);
|
||||
}
|
||||
|
||||
var keys = {
|
||||
PAGE_UP: 33,
|
||||
PAGE_DOWN: 34,
|
||||
ARROW_UP: 38,
|
||||
ARROW_DOWN: 40,
|
||||
h: 72, l: 76, p: 80, n: 78
|
||||
};
|
||||
|
||||
function onkeydown(e) {
|
||||
e = e || window.event;
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (e.keyCode == keys.ARROW_UP)
|
||||
slideshow.previous();
|
||||
else if (e.keyCode == keys.ARROW_DOWN)
|
||||
slideshow.next();
|
||||
else
|
||||
return;
|
||||
} else {
|
||||
// if (e.keyCode == keys.PAGE_UP || e.keyCode == keys.p || e.keyCode == keys.h)
|
||||
// slideshow.previous();
|
||||
// else if (e.keyCode == keys.PAGE_DOWN || e.keyCode == keys.n || e.keyCode == keys.l)
|
||||
// slideshow.next();
|
||||
// else
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function start() {
|
||||
slideshow.navigate(0);
|
||||
}
|
||||
|
||||
function toggleHighlight(idx) {
|
||||
if (highlighted(idx))
|
||||
unhighlight();
|
||||
else
|
||||
navigate(idx, true);
|
||||
}
|
||||
|
||||
function handleClick(evt) {
|
||||
if (evt.ctrlKey || evt.metaKey) {
|
||||
var sentence = evt.currentTarget;
|
||||
|
||||
// Ensure that the goal is shown on the side, not inline
|
||||
var checkbox = sentence.getElementsByClassName("alectryon-toggle")[0];
|
||||
if (checkbox)
|
||||
checkbox.checked = false;
|
||||
|
||||
toggleHighlight(sentence.alectryon_index);
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.onkeydown = onkeydown;
|
||||
slideshow.pos = -1;
|
||||
slideshow.sentences = Array.from(document.getElementsByClassName("alectryon-sentence"));
|
||||
slideshow.sentences.forEach(function (s, idx) {
|
||||
s.addEventListener('click', handleClick, false);
|
||||
s.alectryon_index = idx;
|
||||
});
|
||||
}
|
||||
|
||||
slideshow.start = start;
|
||||
slideshow.end = unhighlight;
|
||||
slideshow.navigate = navigate;
|
||||
slideshow.next = function() { navigate(slideshow.pos + 1); };
|
||||
slideshow.previous = function() { navigate(slideshow.pos + -1); };
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
})(Alectryon.slideshow || (Alectryon.slideshow = {}));
|
||||
|
||||
(function (styles) {
|
||||
var styleNames = ["centered", "floating", "windowed"];
|
||||
|
||||
function className(style) {
|
||||
return "alectryon-" + style;
|
||||
}
|
||||
|
||||
function setStyle(style) {
|
||||
var root = document.getElementsByClassName("alectryon-root")[0];
|
||||
styleNames.forEach(function (s) {
|
||||
root.classList.remove(className(s)); });
|
||||
root.classList.add(className(style));
|
||||
}
|
||||
|
||||
function init() {
|
||||
var banner = document.getElementsByClassName("alectryon-banner")[0];
|
||||
if (banner) {
|
||||
banner.append(" Style: ");
|
||||
styleNames.forEach(function (styleName, idx) {
|
||||
var s = styleName;
|
||||
var a = document.createElement("a");
|
||||
a.onclick = function() { setStyle(s); };
|
||||
a.append(styleName);
|
||||
if (idx > 0) banner.append("; ");
|
||||
banner.appendChild(a);
|
||||
});
|
||||
banner.append(".");
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
styles.setStyle = setStyle;
|
||||
})(Alectryon.styles || (Alectryon.styles = {}));
|
||||
})(Alectryon || (Alectryon = {}));
|
||||
|
||||
function setHidden(elements, isVisible, token) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (isVisible) {
|
||||
elements[i].classList.remove(token)
|
||||
} else {
|
||||
elements[i].classList.add(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleShowTypes(checkbox) {
|
||||
setHidden(document.getElementsByClassName("alectryon-io"), checkbox.checked, "type-info-hidden")
|
||||
}
|
||||
|
||||
function toggleShowGoals(checkbox) {
|
||||
setHidden(document.getElementsByClassName("alectryon-io"), checkbox.checked, "output-hidden")
|
||||
}
|
||||
77
doc/array.md
Normal file
77
doc/array.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Arrays
|
||||
|
||||
The `Array` type implements a *dynamic* (aka growable) array.
|
||||
It is defined as
|
||||
```lean
|
||||
# namespace hidden
|
||||
structure Array (α : Type u) where
|
||||
data : List α
|
||||
# end hidden
|
||||
```
|
||||
but its execution time representation is optimized, and it is similar to C++ `std::vector<T>` and Rust `Vec<T>`.
|
||||
The Lean type checker has no special support for reducing `Array`s.
|
||||
|
||||
You can create arrays in several ways. You can create a small array by listing consecutive values between
|
||||
`#[` and `]` and separated by commas, as shown in the following examples.
|
||||
|
||||
```lean
|
||||
#check #[1, 2, 3] -- Array Nat
|
||||
|
||||
#check #[] -- Array ?m
|
||||
```
|
||||
|
||||
The type of the array elements is inferred from the literals used and must be consistent.
|
||||
```lean
|
||||
#check #["hello", "world"] -- Array String
|
||||
|
||||
-- The following is not valid
|
||||
#check_failure #[10, "hello"]
|
||||
```
|
||||
Recall that the command `#check_failure <term>` only succeeds when the given term is not type correct.
|
||||
|
||||
To create an array of size `n` in which all the elements are initialized to some value `a`, use `mkArray`.
|
||||
```lean
|
||||
#eval mkArray 5 'a'
|
||||
-- #['a', 'a', 'a', 'a', 'a']
|
||||
```
|
||||
|
||||
## Accessing elements
|
||||
|
||||
You can access array elements by using brackets (`[` and `]`).
|
||||
```lean
|
||||
def f (a : Array Nat) (i : Fin a.size) :=
|
||||
a[i] + a[i]
|
||||
```
|
||||
Note that the index `i` has type `Fin a.size`, i.e., it is natural number less than `a.size`.
|
||||
You can also write
|
||||
```lean
|
||||
def f (a : Array Nat) (i : Nat) (h : i < a.size) :=
|
||||
a[i] + a[i]
|
||||
```
|
||||
The bracket operator is whitespace sensitive.
|
||||
|
||||
```lean
|
||||
def f (xs : List Nat) : List Nat :=
|
||||
xs ++ xs
|
||||
|
||||
def as : Array Nat :=
|
||||
#[1, 2, 3, 4]
|
||||
|
||||
def idx : Fin 4 :=
|
||||
2
|
||||
|
||||
#eval f [1, 2, 3] -- This is a function application
|
||||
|
||||
#eval as[idx] -- This is an array access
|
||||
```
|
||||
The notation `a[i]` has two variants: `a[i]!` and `a[i]?`. In both cases, `i` has type `Nat`. The first one
|
||||
produces a panic error message if the index `i` is out of bounds. The latter returns an `Option` type.
|
||||
|
||||
```lean
|
||||
#eval #['a', 'b', 'c'][1]?
|
||||
-- some 'b'
|
||||
#eval #['a', 'b', 'c'][5]?
|
||||
-- none
|
||||
#eval #['a', 'b', 'c'][1]!
|
||||
-- 'b!
|
||||
```
|
||||
45
doc/autobound.md
Normal file
45
doc/autobound.md
Normal file
@@ -0,0 +1,45 @@
|
||||
## Auto Bound Implicit Arguments
|
||||
|
||||
In the previous section, we have shown how implicit arguments make functions more convenient to use.
|
||||
However, functions such as `compose` are still quite verbose to define. Note that the universe
|
||||
polymorphic `compose` is even more verbose than the one previously defined.
|
||||
|
||||
```lean
|
||||
universe u v w
|
||||
def compose {α : Type u} {β : Type v} {γ : Type w}
|
||||
(g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
g (f x)
|
||||
```
|
||||
|
||||
You can avoid the `universe` command by providing the universe parameters when defining `compose`.
|
||||
|
||||
```lean
|
||||
def compose.{u, v, w}
|
||||
{α : Type u} {β : Type v} {γ : Type w}
|
||||
(g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
g (f x)
|
||||
```
|
||||
|
||||
Lean 4 supports a new feature called *auto bound implicit arguments*. It makes functions such as
|
||||
`compose` much more convenient to write. When Lean processes the header of a declaration,
|
||||
any unbound identifier is automatically added as an implicit argument *if* it is a single lower case or
|
||||
greek letter. With this feature, we can write `compose` as
|
||||
|
||||
```lean
|
||||
def compose (g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
g (f x)
|
||||
|
||||
#check @compose
|
||||
-- {β : Sort u_1} → {γ : Sort u_2} → {α : Sort u_3} → (β → γ) → (α → β) → α → γ
|
||||
```
|
||||
Note that, Lean inferred a more general type using `Sort` instead of `Type`.
|
||||
|
||||
Although we love this feature and use it extensively when implementing Lean,
|
||||
we realize some users may feel uncomfortable with it. Thus, you can disable it using
|
||||
the command `set_option autoImplicit false`.
|
||||
```lean
|
||||
set_option autoImplicit false
|
||||
/- The following definition produces `unknown identifier` errors -/
|
||||
-- def compose (g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
-- g (f x)
|
||||
```
|
||||
14
doc/bin/README.md
Normal file
14
doc/bin/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
Lean binary distribution
|
||||
------------------------
|
||||
|
||||
The binary distribution package contains:
|
||||
|
||||
- Lean executable (located in the sub-directory bin)
|
||||
- Standard library (located in the sub-directory lib/lean/library)
|
||||
|
||||
Assuming you are in the same directory this file is located,
|
||||
the following command executes a simple set of examples
|
||||
|
||||
% bin/lean examples/ex.lean
|
||||
|
||||
For more information on Lean and supported editors, please see https://leanprover.github.io/documentation/.
|
||||
21
doc/book.toml
Normal file
21
doc/book.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[book]
|
||||
authors = ["Leonardo de Moura", "Sebastian Ullrich"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "."
|
||||
title = "Lean Manual"
|
||||
|
||||
[build]
|
||||
build-dir = "out"
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/leanprover/lean4"
|
||||
additional-css = ["alectryon.css", "pygments.css"]
|
||||
additional-js = ["alectryon.js"]
|
||||
|
||||
[output.html.fold]
|
||||
enable = true
|
||||
level = 0
|
||||
|
||||
[output.html.playground.boring-prefixes]
|
||||
lean = "# "
|
||||
1
doc/bool.md
Normal file
1
doc/bool.md
Normal file
@@ -0,0 +1 @@
|
||||
# Booleans
|
||||
25
doc/builtintypes.md
Normal file
25
doc/builtintypes.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Builtin Types
|
||||
|
||||
## Numeric Operations
|
||||
|
||||
Lean supports the basic mathematical operations you’d expect for all of the number types: addition, subtraction, multiplication, division, and remainder.
|
||||
The following code shows how you’d use each one in a `def` commands:
|
||||
|
||||
```lean
|
||||
-- addition
|
||||
def sum := 5 + 10
|
||||
|
||||
-- subtraction
|
||||
def difference := 95.5 - 4.3
|
||||
|
||||
-- multiplication
|
||||
def product := 4 * 30
|
||||
|
||||
-- division
|
||||
def quotient := 53.7 / 32.2
|
||||
|
||||
-- remainder/modulo
|
||||
def modulo := 43 % 5
|
||||
```
|
||||
|
||||
Each expression in these statements uses a mathematical operator and evaluates to a single value.
|
||||
1
doc/char.md
Normal file
1
doc/char.md
Normal file
@@ -0,0 +1 @@
|
||||
# Characters
|
||||
818
doc/declarations.md
Normal file
818
doc/declarations.md
Normal file
@@ -0,0 +1,818 @@
|
||||
# Declarations
|
||||
|
||||
-- TODO (fix)
|
||||
|
||||
Declaration Names
|
||||
=================
|
||||
|
||||
A declaration name is a hierarchical [identifier](lexical_structure.md#identifiers) that is interpreted relative to the current namespace as well as (during lookup) to the set of open namespaces.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace a
|
||||
constant b.c : Nat
|
||||
#print b.c -- constant a.b.c : Nat
|
||||
end a
|
||||
|
||||
#print a.b.c -- constant a.b.c : Nat
|
||||
open a
|
||||
#print b.c -- constant a.b.c : Nat
|
||||
|
||||
Declaration names starting with an underscore are reserved for internal use. Names starting with the special atomic name ``_root_`` are interpreted as absolute names.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
constant a : Nat
|
||||
namespace a
|
||||
constant a : Int
|
||||
#print _root_.a -- constant a : Nat
|
||||
#print a.a -- constant a.a : Int
|
||||
end a
|
||||
|
||||
Contexts and Telescopes
|
||||
=======================
|
||||
|
||||
When processing user input, Lean first parses text to a raw expression format. It then uses background information and type constants to disambiguate overloaded symbols and infer implicit arguments, resulting in a fully-formed expression. This process is known as *elaboration*.
|
||||
|
||||
As hinted in [Expression Syntax](expressions.md#expression_syntax),
|
||||
expressions are parsed and elaborated with respect to an *environment*
|
||||
and a *local context*. Roughly speaking, an environment represents the
|
||||
state of Lean at the point where an expression is parsed, including
|
||||
previously declared axioms, constants, definitions, and theorems. In a
|
||||
given environment, a *local context* consists of a sequence ``(a₁ :
|
||||
α₁) (a₂ : α₂) ... (aₙ : αₙ)`` where each ``aᵢ`` is a name denoting a
|
||||
local constant and each ``αᵢ`` is an expression of type ``Sort u`` for
|
||||
some ``u`` which can involve elements of the environment and the local
|
||||
constants ``aⱼ`` for ``j < i``.
|
||||
|
||||
Intuitively, a local context is a list of variables that are held constant while an expression is being elaborated. Consider the following
|
||||
|
||||
```lean
|
||||
def f (a b : Nat) : Nat → Nat := fun c => a + (b + c)
|
||||
```
|
||||
|
||||
Here the expression ``fun c => a + (b + c)`` is elaborated in the context ``(a : Nat) (b : Nat)`` and the expression ``a + (b + c)`` is elaborated in the context ``(a : Nat) (b : Nat) (c : Nat)``. If you replace the expression ``a + (b + c)`` with an underscore, the error message from Lean will include the current *goal*:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
a b c : Nat
|
||||
⊢ Nat
|
||||
|
||||
Here ``a b c : Nat`` indicates the local context, and the second ``Nat`` indicates the expected type of the result.
|
||||
|
||||
A *context* is sometimes called a *telescope*, but the latter is used more generally to include a sequence of declarations occuring relative to a given context. For example, relative to the context ``(a₁ : α₁) (a₂ : α₂) ... (aₙ : αₙ)``, the types ``βᵢ`` in a telescope ``(b₁ : β₁) (b₂ : β₂) ... (bₙ : βₙ)`` can refer to ``a₁, ..., aₙ``. Thus a context can be viewed as a telescope relative to the empty context.
|
||||
|
||||
Telescopes are often used to describe a list of arguments, or parameters, to a declaration. In such cases, it is often notationally convenient to let ``(a : α)`` stand for a telescope rather than just a single argument. In general, the annotations described in [Implicit Arguments](expressions.md#implicit_arguments) can be used to mark arguments as implicit.
|
||||
|
||||
.. _basic_declarations:
|
||||
|
||||
Basic Declarations
|
||||
==================
|
||||
|
||||
Lean provides ways of adding new objects to the environment. The following provide straightforward ways of declaring new objects:
|
||||
|
||||
* ``axiom c : α`` : declare a constant named ``c`` of type ``α``, it is postulating that `α` is not an empty type.
|
||||
* ``def c : α := v`` : defines ``c`` to denote ``v``, which should have type ``α``.
|
||||
* ``theorem c : p := v`` : similar to ``def``, but intended to be used when ``p`` is a proposition.
|
||||
* ``constant c : α (:= v)?`` : declares a opaque constant named ``c`` of type ``α``, the optional value `v` is must have type `α`
|
||||
and can be viewed as a certificate that ``α`` is not an empty type. If the value is not provided, Lean tries to find one
|
||||
using a proceture based on type class resolution. The value `v` is hidden from the type checker. You can assume that
|
||||
Lean "forgets" `v` after type checking this kind of declaration.
|
||||
|
||||
It is sometimes useful to be able to simulate a definition or theorem without naming it or adding it to the environment.
|
||||
|
||||
* ``example : α := t`` : elaborates ``t`` and checks that it has sort ``α`` (often a proposition), without adding it to the environment.
|
||||
|
||||
In ``def``, the type (``α`` or ``p``, respectively) can be omitted when it can be inferred by Lean. Constants declared with ``theorem`` are marked as ``irreducible``.
|
||||
|
||||
Any of ``def``, ``theorem``, ``axiom``, or ``example`` can take a list of arguments (that is, a context) before the colon. If ``(a : α)`` is a context, the definition ``def foo (a : α) : β := t``
|
||||
is interpreted as ``def foo : (a : α) → β := fun a : α => t``. Similarly, a theorem ``theorem foo (a : α) : p := t`` is interpreted as ``theorem foo : ∀ a : α, p := fun a : α => t``.
|
||||
|
||||
```lean
|
||||
constant c : Nat
|
||||
constant d : Nat
|
||||
axiom cd_eq : c = d
|
||||
|
||||
def foo : Nat := 5
|
||||
def bar := 6
|
||||
def baz (x y : Nat) (s : List Nat) := [x, y] ++ s
|
||||
|
||||
theorem foo_eq_five : foo = 5 := rfl
|
||||
theorem baz_theorem (x y : Nat) : baz x y [] = [x, y] := rfl
|
||||
|
||||
example (x y : Nat) : baz x y [] = [x, y] := rfl
|
||||
```
|
||||
|
||||
Inductive Types
|
||||
===============
|
||||
|
||||
Lean's axiomatic foundation allows users to declare arbitrary
|
||||
inductive families, following the pattern described by [Dybjer]_. To
|
||||
make the presentation more manageable, we first describe inductive
|
||||
*types*, and then describe the generalization to inductive *families*
|
||||
in the next section. The declaration of an inductive type has the
|
||||
following form:
|
||||
|
||||
```
|
||||
inductive Foo (a : α) where
|
||||
| constructor₁ : (b : β₁) → Foo a
|
||||
| constructor₂ : (b : β₂) → Foo a
|
||||
...
|
||||
| constructorₙ : (b : βₙ) → Foo a
|
||||
```
|
||||
|
||||
Here ``(a : α)`` is a context and each ``(b : βᵢ)`` is a telescope in the context ``(a : α)`` together with ``Foo``, subject to the following constraints.
|
||||
|
||||
Suppose the telescope ``(b : βᵢ)`` is ``(b₁ : βᵢ₁) ... (bᵤ : βᵢᵤ)``. Each argument in the telescope is either *nonrecursive* or *recursive*.
|
||||
|
||||
- An argument ``(bⱼ : βᵢⱼ)`` is *nonrecursive* if ``βᵢⱼ`` does not refer to ``foo,`` the inductive type being defined. In that case, ``βᵢⱼ`` can be any type, so long as it does not refer to any nonrecursive arguments.
|
||||
|
||||
- An argument ``(bⱼ : βᵢⱼ)`` is *recursive* if it ``βᵢⱼ`` of the form ``Π (d : δ), foo`` where ``(d : δ)`` is a telescope which does not refer to ``foo`` or any nonrecursive arguments.
|
||||
|
||||
The inductive type ``foo`` represents a type that is freely generated by the constructors. Each constructor can take arbitrary data and facts as arguments (the nonrecursive arguments), as well as indexed sequences of elements of ``foo`` that have been previously constructed (the recursive arguments). In set theoretic models, such sets can be represented by well-founded trees labeled by the constructor data, or they can defined using other transfinite or impredicative means.
|
||||
|
||||
The declaration of the type ``foo`` as above results in the addition of the following constants to the environment:
|
||||
|
||||
- the *type former* ``foo : Π (a : α), Sort u``
|
||||
- for each ``i``, the *constructor* ``foo.constructorᵢ : Π (a : α) (b : βᵢ), foo a``
|
||||
- the *eliminator* ``foo.rec``, which takes arguments
|
||||
|
||||
+ ``(a : α)`` (the parameters)
|
||||
+ ``{C : foo a → Type u}`` (the *motive* of the elimination)
|
||||
+ for each ``i``, the *minor premise* corresponding to ``constructorᵢ``
|
||||
+ ``(x : foo)`` (the *major premise*)
|
||||
|
||||
and returns an element of ``C x``. Here, The ith minor premise is a function which takes
|
||||
|
||||
+ ``(b : βᵢ)`` (the arguments to the constructor)
|
||||
+ an argument of type ``Π (d : δ), C (bⱼ d)`` corresponding to each recursive argument ``(bⱼ : βᵢⱼ)``, where ``βᵢⱼ`` is of the form ``Π (d : δ), foo`` (the recursive values of the function being defined)
|
||||
|
||||
and returns an element of ``C (constructorᵢ a b)``, the intended value of the function at ``constructorᵢ a b``.
|
||||
|
||||
The eliminator represents a principle of recursion: to construct an element of ``C x`` where ``x : foo a``, it suffices to consider each of the cases where ``x`` is of the form ``constructorᵢ a b`` and to provide an auxiliary construction in each case. In the case where some of the arguments to ``constructorᵢ`` are recursive, we can assume that we have already constructed values of ``C y`` for each value ``y`` constructed at an earlier stage.
|
||||
|
||||
Under the propositions-as-type correspondence, when ``C x`` is an element of ``Prop``, the eliminator represents a principle of induction. In order to show ``∀ x, C x``, it suffices to show that ``C`` holds for each constructor, under the inductive hypothesis that it holds for all recursive inputs to the constructor.
|
||||
|
||||
The eliminator and constructors satisfy the following identities, in which all the arguments are shown explicitly. Suppose we set ``F := foo.rec a C f₁ ... fₙ``. Then for each constructor, we have the definitional reduction:
|
||||
|
||||
.. code-block :: text
|
||||
|
||||
F (constructorᵢ a b) = fᵢ b ... (fun d : δᵢⱼ => F (bⱼ d)) ...
|
||||
|
||||
where the ellipses include one entry for each recursive argument.
|
||||
|
||||
Below are some common examples of inductive types, many of which are defined in the core library.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace hide
|
||||
universes u v
|
||||
|
||||
-- BEGIN
|
||||
inductive empty : Type
|
||||
|
||||
inductive unit : Type
|
||||
| star : unit
|
||||
|
||||
inductive bool : Type
|
||||
| ff : bool
|
||||
| tt : bool
|
||||
|
||||
inductive prod (α : Type u) (β : Type v) : Type (max u v)
|
||||
| mk : α → β → prod
|
||||
|
||||
inductive sum (α : Type u) (β : Type v)
|
||||
| inl : α → sum
|
||||
| inr : β → sum
|
||||
|
||||
inductive sigma (α : Type u) (β : α → Type v)
|
||||
| mk : Π a : α, β a → sigma
|
||||
|
||||
inductive false : Prop
|
||||
|
||||
inductive true : Prop
|
||||
| trivial : true
|
||||
|
||||
inductive and (p q : Prop) : Prop
|
||||
| intro : p → q → and
|
||||
|
||||
inductive or (p q : Prop) : Prop
|
||||
| inl : p → or
|
||||
| inr : q → or
|
||||
|
||||
inductive Exists (α : Type u) (p : α → Prop) : Prop
|
||||
| intro : ∀ x : α, p x → Exists
|
||||
|
||||
inductive subtype (α : Type u) (p : α → Prop) : Type u
|
||||
| intro : ∀ x : α, p x → subtype
|
||||
|
||||
inductive nat : Type
|
||||
| zero : nat
|
||||
| succ : nat → nat
|
||||
|
||||
inductive list (α : Type u)
|
||||
| nil : list
|
||||
| cons : α → list → list
|
||||
|
||||
-- full binary tree with nodes and leaves labeled from α
|
||||
inductive bintree (α : Type u)
|
||||
| leaf : α → bintree
|
||||
| node : bintree → α → bintree → bintree
|
||||
|
||||
-- every internal node has subtrees indexed by Nat
|
||||
inductive cbt (α : Type u)
|
||||
| leaf : α → cbt
|
||||
| node : (Nat → cbt) → cbt
|
||||
-- END
|
||||
end hide
|
||||
|
||||
Note that in the syntax of the inductive definition ``foo``, the context ``(a : α)`` is left implicit. In other words, constructors and recursive arguments are written as though they have return type ``foo`` rather than ``foo a``.
|
||||
|
||||
Elements of the context ``(a : α)`` can be marked implicit as described in [Implicit Arguments](#implicit.md#implicit_arguments). These annotations bear only on the type former, ``foo``. Lean uses a heuristic to determine which arguments to the constructors should be marked implicit, namely, an argument is marked implicit if it can be inferred from the type of a subsequent argument. If the annotation ``{}`` appears after the constructor, a argument is marked implicit if it can be inferred from the type of a subsequent argument *or the return type*. For example, it is useful to let ``nil`` denote the empty list of any type, since the type can usually be inferred in the context in which it appears. These heuristics are imperfect, and you may sometimes wish to define your own constructors in terms of the default ones. In that case, use the ``[pattern]`` [attribute](TODO: missing link) to ensure that these will be used appropriately by the [Equation Compiler](#the-equation-compiler).
|
||||
|
||||
There are restrictions on the universe ``u`` in the return type ``Sort u`` of the type former. There are also restrictions on the universe ``u`` in the return type ``Sort u`` of the motive of the eliminator. These will be discussed in the next section in the more general setting of inductive families.
|
||||
|
||||
Lean allows some additional syntactic conveniences. You can omit the return type of the type former, ``Sort u``, in which case Lean will infer the minimal possible nonzero value for ``u``. As with function definitions, you can list arguments to the constructors before the colon. In an enumerated type (that is, one where the constructors have no arguments), you can also leave out the return type of the constructors.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace hide
|
||||
universe u
|
||||
|
||||
-- BEGIN
|
||||
inductive weekday
|
||||
| sunday | monday | tuesday | wednesday
|
||||
| thursday | friday | saturday
|
||||
|
||||
inductive nat
|
||||
| zero
|
||||
| succ (n : nat) : nat
|
||||
|
||||
inductive list (α : Type u)
|
||||
| nil {} : list
|
||||
| cons (a : α) (l : list) : list
|
||||
|
||||
@[pattern]
|
||||
def list.nil' (α : Type u) : list α := list.nil
|
||||
|
||||
def length {α : Type u} : list α → Nat
|
||||
| (list.nil' .(α)) := 0
|
||||
| (list.cons a l) := 1 + length l
|
||||
-- END
|
||||
|
||||
end hide
|
||||
|
||||
The type former, constructors, and eliminator are all part of Lean's axiomatic foundation, which is to say, they are part of the trusted kernel. In addition to these axiomatically declared constants, Lean automatically defines some additional objects in terms of these, and adds them to the environment. These include the following:
|
||||
|
||||
- ``foo.rec_on`` : a variant of the eliminator, in which the major premise comes first
|
||||
- ``foo.cases_on`` : a restricted version of the eliminator which omits any recursive calls
|
||||
- ``foo.no_confusion_type``, ``foo.no_confusion`` : functions which witness the fact that the inductive type is freely generated, i.e. that the constructors are injective and that distinct constructors produce distinct objects
|
||||
- ``foo.below``, ``foo.ibelow`` : functions used by the equation compiler to implement structural recursion
|
||||
- ``foo.sizeof`` : a measure which can be used for well-founded recursion
|
||||
|
||||
Note that it is common to put definitions and theorems related to a datatype ``foo`` in a namespace of the same name. This makes it possible to use projection notation described in [Structures](struct.md#structures) and [Namespaces](namespaces.md#namespaces).
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace hide
|
||||
universe u
|
||||
|
||||
-- BEGIN
|
||||
inductive nat
|
||||
| zero
|
||||
| succ (n : nat) : nat
|
||||
|
||||
#check nat
|
||||
#check nat.rec
|
||||
#check nat.zero
|
||||
#check nat.succ
|
||||
|
||||
#check nat.rec_on
|
||||
#check nat.cases_on
|
||||
#check nat.no_confusion_type
|
||||
#check @nat.no_confusion
|
||||
#check nat.brec_on
|
||||
#check nat.below
|
||||
#check nat.ibelow
|
||||
#check nat.sizeof
|
||||
|
||||
-- END
|
||||
|
||||
end hide
|
||||
|
||||
.. _inductive_families:
|
||||
|
||||
Inductive Families
|
||||
==================
|
||||
|
||||
In fact, Lean implements a slight generalization of the inductive types described in the previous section, namely, inductive *families*. The declaration of an inductive family in Lean has the following form:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
inductive foo (a : α) : Π (c : γ), Sort u
|
||||
| constructor₁ : Π (b : β₁), foo t₁
|
||||
| constructor₂ : Π (b : β₂), foo t₂
|
||||
...
|
||||
| constructorₙ : Π (b : βₙ), foo tₙ
|
||||
|
||||
Here ``(a : α)`` is a context, ``(c : γ)`` is a telescope in context ``(a : α)``, each ``(b : βᵢ)`` is a telescope in the context ``(a : α)`` together with ``(foo : Π (c : γ), Sort u)`` subject to the constraints below, and each ``tᵢ`` is a tuple of terms in the context ``(a : α) (b : βᵢ)`` having the types ``γ``. Instead of defining a single inductive type ``foo a``, we are now defining a family of types ``foo a c`` indexed by elements ``c : γ``. Each constructor, ``constructorᵢ``, places its result in the type ``foo a tᵢ``, the member of the family with index ``tᵢ``.
|
||||
|
||||
The modifications to the scheme in the previous section are straightforward. Suppose the telescope ``(b : βᵢ)`` is ``(b₁ : βᵢ₁) ... (bᵤ : βᵢᵤ)``.
|
||||
|
||||
- As before, an argument ``(bⱼ : βᵢⱼ)`` is *nonrecursive* if ``βᵢⱼ`` does not refer to ``foo,`` the inductive type being defined. In that case, ``βᵢⱼ`` can be any type, so long as it does not refer to any nonrecursive arguments.
|
||||
|
||||
- An argument ``(bⱼ : βᵢⱼ)`` is *recursive* if ``βᵢⱼ`` is of the form ``Π (d : δ), foo s`` where ``(d : δ)`` is a telescope which does not refer to ``foo`` or any nonrecursive arguments and ``s`` is a tuple of terms in context ``(a : α)`` and the previous nonrecursive ``bⱼ``'s with types ``γ``.
|
||||
|
||||
The declaration of the type ``foo`` as above results in the addition of the following constants to the environment:
|
||||
|
||||
- the *type former* ``foo : Π (a : α) (c : γ), Sort u``
|
||||
- for each ``i``, the *constructor* ``foo.constructorᵢ : Π (a : α) (b : βᵢ), foo a tᵢ``
|
||||
- the *eliminator* ``foo.rec``, which takes arguments
|
||||
|
||||
+ ``(a : α)`` (the parameters)
|
||||
+ ``{C : Π (c : γ), foo a c → Type u}`` (the motive of the elimination)
|
||||
+ for each ``i``, the minor premise corresponding to ``constructorᵢ``
|
||||
+ ``(x : foo a)`` (the major premise)
|
||||
|
||||
and returns an element of ``C x``. Here, The ith minor premise is a function which takes
|
||||
|
||||
+ ``(b : βᵢ)`` (the arguments to the constructor)
|
||||
+ an argument of type ``Π (d : δ), C s (bⱼ d)`` corresponding to each recursive argument ``(bⱼ : βᵢⱼ)``, where ``βᵢⱼ`` is of the form ``Π (d : δ), foo s``
|
||||
|
||||
and returns an element of ``C tᵢ (constructorᵢ a b)``.
|
||||
|
||||
Suppose we set ``F := foo.rec a C f₁ ... fₙ``. Then for each constructor, we have the definitional reduction, as before:
|
||||
|
||||
.. code-block :: text
|
||||
|
||||
F (constructorᵢ a b) = fᵢ b ... (fun d : δᵢⱼ => F (bⱼ d)) ...
|
||||
|
||||
where the ellipses include one entry for each recursive argument.
|
||||
|
||||
The following are examples of inductive families.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace hide
|
||||
universe u
|
||||
|
||||
-- BEGIN
|
||||
inductive vector (α : Type u) : Nat → Type u
|
||||
| nil : vector 0
|
||||
| succ : Π n, vector n → vector (n + 1)
|
||||
|
||||
-- 'is_prod s n' means n is a product of elements of s
|
||||
inductive is_prod (s : set Nat) : Nat → Prop
|
||||
| base : ∀ n ∈ s, is_prod n
|
||||
| step : ∀ m n, is_prod m → is_prod n → is_prod (m * n)
|
||||
|
||||
inductive eq {α : Sort u} (a : α) : α → Prop
|
||||
| refl : eq a
|
||||
-- END
|
||||
|
||||
end hide
|
||||
|
||||
We can now describe the constraints on the return type of the type former, ``Sort u``. We can always take ``u`` to be ``0``, in which case we are defining an inductive family of propositions. If ``u`` is nonzero, however, it must satisfy the following constraint: for each type ``βᵢⱼ : Sort v`` occurring in the constructors, we must have ``u ≥ v``. In the set-theoretic interpretation, this ensures that the universe in which the resulting type resides is large enough to contain the inductively generated family, given the number of distinctly-labeled constructors. The restriction does not hold for inductively defined propositions, since these contain no data.
|
||||
|
||||
Putting an inductive family in ``Prop``, however, does impose a restriction on the eliminator. Generally speaking, for an inductive family in ``Prop``, the motive in the eliminator is required to be in ``Prop``. But there is an exception to this rule: you are allowed to eliminate from an inductively defined ``Prop`` to an arbitrary ``Sort`` when there is only one constructor, and each argument to that constructor is either in ``Prop`` or an index. The intuition is that in this case the elimination does not make use of any information that is not already given by the mere fact that the type of argument is inhabited. This special case is known as *singleton elimination*.
|
||||
|
||||
.. _mutual_and_nested_inductive_definitions:
|
||||
|
||||
Mutual and Nested Inductive Definitions
|
||||
=======================================
|
||||
|
||||
Lean supports two generalizations of the inductive families described above, namely, *mutual* and *nested* inductive definitions. These are *not* implemented natively in the kernel. Rather, the definitions are compiled down to the primitive inductive types and families.
|
||||
|
||||
The first generalization allows for multiple inductive types to be defined simultaneously.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
mutual inductive foo, bar (a : α)
|
||||
with foo : Π (c : γ), Sort u
|
||||
| constructor₁₁ : Π (b : β₁₁), foo t₁₁
|
||||
| constructor₁₂ : Π (b : β₁₂), foo t₁₂
|
||||
...
|
||||
| constructor₁ₙ : Π (b : β₁ₙ), foo t₁ₙ
|
||||
with bar :
|
||||
| constructor₂₁ : Π (b : β₂₁), bar t₂₁
|
||||
| constructor₂₂ : Π (b : β₂₂), bar t₂₂
|
||||
...
|
||||
| constructor₂ₘ : Π (b : β₂ₘ), bar t₂ₘ
|
||||
|
||||
Here the syntax is shown for defining two inductive families, ``foo`` and ``bar``, but any number is allowed. The restrictions are almost the same as for ordinary inductive families. For example, each ``(b : βᵢⱼ)`` is a telescope relative to the context ``(a : α)``. The difference is that the constructors can now have recursive arguments whose return types are any of the inductive families currently being defined, in this case ``foo`` and ``bar``. Note that all of the inductive definitions share the same parameters ``(a : α)``, though they may have different indices.
|
||||
|
||||
A mutual inductive definition is compiled down to an ordinary inductive definition using an extra finite-valued index to distinguish the components. The details of the internal construction are meant to be hidden from most users. Lean defines the expected type formers ``foo`` and ``bar`` and constructors ``constructorᵢⱼ`` from the internal inductive definition. There is no straightforward elimination principle, however. Instead, Lean defines an appropriate ``sizeof`` measure, meant for use with well-founded recursion, with the property that the recursive arguments to a constructor are smaller than the constructed value.
|
||||
|
||||
The second generalization relaxes the restriction that in the recursive definition of ``foo``, ``foo`` can only occur strictly positively in the type of any of its recursive arguments. Specifically, in a nested inductive definition, ``foo`` can appear as an argument to another inductive type constructor, so long as the corresponding parameter occurs strictly positively in the constructors for *that* inductive type. This process can be iterated, so that additional type constructors can be applied to those, and so on.
|
||||
|
||||
A nested inductive definition is compiled down to an ordinary inductive definition using a mutual inductive definition to define copies of all the nested types simultaneously. Lean then constructs isomorphisms between the mutually defined nested types and their independently defined counterparts. Once again, the internal details are not meant to be manipulated by users. Rather, the type former and constructors are made available and work as expected, while an appropriate ``sizeof`` measure is generated for use with well-founded recursion.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
universe u
|
||||
-- BEGIN
|
||||
mutual inductive even, odd
|
||||
with even : Nat → Prop
|
||||
| even_zero : even 0
|
||||
| even_succ : ∀ n, odd n → even (n + 1)
|
||||
with odd : Nat → Prop
|
||||
| odd_succ : ∀ n, even n → odd (n + 1)
|
||||
|
||||
inductive tree (α : Type u)
|
||||
| mk : α → list tree → tree
|
||||
|
||||
inductive double_tree (α : Type u)
|
||||
| mk : α → list double_tree × list double_tree → double_tree
|
||||
-- END
|
||||
|
||||
.. _the_equation_compiler:
|
||||
|
||||
The Equation Compiler
|
||||
=====================
|
||||
|
||||
The equation compiler takes an equational description of a function or proof and tries to define an object meeting that specification. It expects input with the following syntax:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
def foo (a : α) : Π (b : β), γ
|
||||
| [patterns₁] := t₁
|
||||
...
|
||||
| [patternsₙ] := tₙ
|
||||
|
||||
Here ``(a : α)`` is a telescope, ``(b : β)`` is a telescope in the context ``(a : α)``, and ``γ`` is an expression in the context ``(a : α) (b : β)`` denoting a ``Type`` or a ``Prop``.
|
||||
|
||||
Each ``patternsᵢ`` is a sequence of patterns of the same length as ``(b : β)``. A pattern is either:
|
||||
|
||||
- a variable, denoting an arbitrary value of the relevant type,
|
||||
- an underscore, denoting a *wildcard* or *anonymous variable*,
|
||||
- an inaccessible term (see below), or
|
||||
- a constructor for the inductive type of the corresponding argument, applied to a sequence of patterns.
|
||||
|
||||
In the last case, the pattern must be enclosed in parentheses.
|
||||
|
||||
Each term ``tᵢ`` is an expression in the context ``(a : α)`` together with the variables introduced on the left-hand side of the token ``:=``. The term ``tᵢ`` can also include recursive calls to ``foo``, as described below. The equation compiler does case splitting on the variables ``(b : β)`` as necessary to match the patterns, and defines ``foo`` so that it has the value ``tᵢ`` in each of the cases. In ideal circumstances (see below), the equations hold definitionally. Whether they hold definitionally or only propositionally, the equation compiler proves the relevant equations and assigns them internal names. They are accessible by the ``rewrite`` and ``simp`` tactics under the name ``foo`` (see [Rewrite](tactics.md#rewrite) and _[TODO: where is simplifier tactic documented?]_. If some of the patterns overlap, the equation compiler interprets the definition so that the first matching pattern applies in each case. Thus, if the last pattern is a variable, it covers all the remaining cases. If the patterns that are presented do not cover all possible cases, the equation compiler raises an error.
|
||||
|
||||
When identifiers are marked with the ``[pattern]`` attribute, the equation compiler unfolds them in the hopes of exposing a constructor. For example, this makes it possible to write ``n+1`` and ``0`` instead of ``nat.succ n`` and ``nat.zero`` in patterns.
|
||||
|
||||
For a nonrecursive definition involving case splits, the defining equations will hold definitionally. With inductive types like ``char``, ``string``, and ``fin n``, a case split would produce definitions with an inordinate number of cases. To avoid this, the equation compiler uses ``if ... then ... else`` instead of ``cases_on`` when defining the function. In this case, the defining equations hold definitionally as well.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
open nat
|
||||
|
||||
def sub2 : Nat → Nat
|
||||
| zero := 0
|
||||
| (succ zero) := 0
|
||||
| (succ (succ a)) := a
|
||||
|
||||
def bar : Nat → list Nat → bool → Nat
|
||||
| 0 _ ff := 0
|
||||
| 0 (b :: _) _ := b
|
||||
| 0 [] tt := 7
|
||||
| (a+1) [] ff := a
|
||||
| (a+1) [] tt := a + 1
|
||||
| (a+1) (b :: _) _ := a + b
|
||||
|
||||
def baz : char → Nat
|
||||
| 'A' := 1
|
||||
| 'B' := 2
|
||||
| _ := 3
|
||||
|
||||
If any of the terms ``tᵢ`` in the template above contain a recursive call to ``foo``, the equation compiler tries to interpret the definition as a structural recursion. In order for that to succeed, the recursive arguments must be subterms of the corresponding arguments on the left-hand side. The function is then defined using a *course of values* recursion, using automatically generated functions ``below`` and ``brec`` in the namespace corresponding to the inductive type of the recursive argument. In this case the defining equations hold definitionally, possibly with additional case splits.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace hide
|
||||
|
||||
-- BEGIN
|
||||
def fib : nat → nat
|
||||
| 0 := 1
|
||||
| 1 := 1
|
||||
| (n+2) := fib (n+1) + fib n
|
||||
|
||||
def append {α : Type} : list α → list α → list α
|
||||
| [] l := l
|
||||
| (h::t) l := h :: append t l
|
||||
|
||||
example : append [(1 : Nat), 2, 3] [4, 5] = [1, 2, 3, 4, 5] := rfl
|
||||
-- END
|
||||
|
||||
end hide
|
||||
|
||||
If structural recursion fails, the equation compiler falls back on well-founded recursion. It tries to infer an instance of ``has_sizeof`` for the type of each argument, and then show that each recursive call is decreasing under the lexicographic order of the arguments with respect to ``sizeof`` measure. If it fails, the error message provides information as to the goal that Lean tried to prove. Lean uses information in the local context, so you can often provide the relevant proof manually using ``have`` in the body of the definition. In this case of well-founded recursion, the defining equations hold only propositionally, and can be accessed using ``simp`` and ``rewrite`` with the name ``foo``.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
namespace hide
|
||||
open nat
|
||||
|
||||
-- BEGIN
|
||||
def div : Nat → Nat → Nat
|
||||
| x y :=
|
||||
if h : 0 < y ∧ y ≤ x then
|
||||
have x - y < x,
|
||||
from sub_lt (lt_of_lt_of_le h.left h.right) h.left,
|
||||
div (x - y) y + 1
|
||||
else
|
||||
0
|
||||
|
||||
example (x y : Nat) :
|
||||
div x y = if 0 < y ∧ y ≤ x then div (x - y) y + 1 else 0 :=
|
||||
by rw [div]
|
||||
-- END
|
||||
|
||||
end hide
|
||||
|
||||
Note that recursive definitions can in general require nested recursions, that is, recursion on different arguments of ``foo`` in the template above. The equation compiler handles this by abstracting later arguments, and recursively defining higher-order functions to meet the specification.
|
||||
|
||||
The equation compiler also allows mutual recursive definitions, with a syntax similar to that of [Mutual and Nested Inductive Definitions](#mutual-and-nested-inductive-definitions). They are compiled using well-founded recursion, and so once again the defining equations hold only propositionally.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
mutual def even, odd
|
||||
with even : Nat → bool
|
||||
| 0 := tt
|
||||
| (a+1) := odd a
|
||||
with odd : Nat → bool
|
||||
| 0 := ff
|
||||
| (a+1) := even a
|
||||
|
||||
example (a : Nat) : even (a + 1) = odd a :=
|
||||
by simp [even]
|
||||
|
||||
example (a : Nat) : odd (a + 1) = even a :=
|
||||
by simp [odd]
|
||||
|
||||
Well-founded recursion is especially useful with [Mutual and Nested Inductive Definitions](#mutual-and-nested-inductive-definitions), since it provides the canonical way of defining functions on these types.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
mutual inductive even, odd
|
||||
with even : Nat → Prop
|
||||
| even_zero : even 0
|
||||
| even_succ : ∀ n, odd n → even (n + 1)
|
||||
with odd : Nat → Prop
|
||||
| odd_succ : ∀ n, even n → odd (n + 1)
|
||||
|
||||
open even odd
|
||||
|
||||
theorem not_odd_zero : ¬ odd 0.
|
||||
|
||||
mutual theorem even_of_odd_succ, odd_of_even_succ
|
||||
with even_of_odd_succ : ∀ n, odd (n + 1) → even n
|
||||
| _ (odd_succ n h) := h
|
||||
with odd_of_even_succ : ∀ n, even (n + 1) → odd n
|
||||
| _ (even_succ n h) := h
|
||||
|
||||
inductive term
|
||||
| const : string → term
|
||||
| app : string → list term → term
|
||||
|
||||
open term
|
||||
|
||||
mutual def num_consts, num_consts_lst
|
||||
with num_consts : term → nat
|
||||
| (term.const n) := 1
|
||||
| (term.app n ts) := num_consts_lst ts
|
||||
with num_consts_lst : list term → nat
|
||||
| [] := 0
|
||||
| (t::ts) := num_consts t + num_consts_lst ts
|
||||
|
||||
The case where patterns are matched against an argument whose type is an inductive family is known as *dependent pattern matching*. This is more complicated, because the type of the function being defined can impose constraints on the patterns that are matched. In this case, the equation compiler will detect inconsistent cases and rule them out.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
universe u
|
||||
|
||||
inductive vector (α : Type u) : Nat → Type u
|
||||
| nil {} : vector 0
|
||||
| cons : Π {n}, α → vector n → vector (n+1)
|
||||
|
||||
namespace vector
|
||||
|
||||
def head {α : Type} : Π {n}, vector α (n+1) → α
|
||||
| n (cons h t) := h
|
||||
|
||||
def tail {α : Type} : Π {n}, vector α (n+1) → vector α n
|
||||
| n (cons h t) := t
|
||||
|
||||
def map {α β γ : Type} (f : α → β → γ) :
|
||||
Π {n}, vector α n → vector β n → vector γ n
|
||||
| 0 nil nil := nil
|
||||
| (n+1) (cons a va) (cons b vb) := cons (f a b) (map va vb)
|
||||
|
||||
end vector
|
||||
|
||||
An expression of the form ``.(t)`` in a pattern is known as an *inaccessible term*. It is not viewed as part of the pattern; rather, it is explicit information that is used by the elaborator and equation compiler when interpreting the definition. Inaccessible terms do not participate in pattern matching. They are sometimes needed for a pattern to make sense, for example, when a constructor depends on a parameter that is not a pattern-matching variable. In other cases, they can be used to inform the equation compiler that certain arguments do not require a case split, and they can be used to make a definition more readable.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
universe u
|
||||
|
||||
inductive vector (α : Type u) : Nat → Type u
|
||||
| nil {} : vector 0
|
||||
| cons : Π {n}, α → vector n → vector (n+1)
|
||||
|
||||
namespace vector
|
||||
|
||||
-- BEGIN
|
||||
variable {α : Type u}
|
||||
|
||||
def add [has_add α] :
|
||||
Π {n : Nat}, vector α n → vector α n → vector α n
|
||||
| ._ nil nil := nil
|
||||
| ._ (cons a v) (cons b w) := cons (a + b) (add v w)
|
||||
|
||||
def add' [has_add α] :
|
||||
Π {n : Nat}, vector α n → vector α n → vector α n
|
||||
| .(0) nil nil := nil
|
||||
| .(n+1) (@cons .(α) n a v) (cons b w) := cons (a + b) (add' v w)
|
||||
-- END
|
||||
|
||||
end vector
|
||||
|
||||
.. _match_expressions:
|
||||
|
||||
Match Expressions
|
||||
=================
|
||||
|
||||
Lean supports a ``match ... with ...`` construct similar to ones found in most functional programming languages. The syntax is as follows:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
match t₁, ..., tₙ with
|
||||
| p₁₁, ..., p₁ₙ := s₁
|
||||
...
|
||||
| pₘ₁, ..., pₘₙ := sₘ
|
||||
|
||||
Here ``t₁, ..., tₙ`` are any terms in the context in which the expression appears, the expressions ``pᵢⱼ`` are patterns, and the terms ``sᵢ`` are expressions in the local context together with variables introduced by the patterns on the left-hand side. Each ``sᵢ`` should have the expected type of the entire ``match`` expression.
|
||||
|
||||
Any ``match`` expression is interpreted using the equation compiler, which generalizes ``t₁, ..., tₙ``, defines an internal function meeting the specification, and then applies it to ``t₁, ..., tₙ``. In contrast to the definitions in [The Equation Compiler](declarations.md#the-equation-compiler), the terms ``tᵢ`` are arbitrary terms rather than just variables, and the expression can occur anywhere within a Lean expression, not just at the top level of a definition. Note that the syntax here is somewhat different: both the terms ``tᵢ`` and the patterns ``pᵢⱼ`` are separated by commas.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
def foo (n : Nat) (b c : bool) :=
|
||||
5 + match n - 5, b && c with
|
||||
| 0, tt := 0
|
||||
| m+1, tt := m + 7
|
||||
| 0, ff := 5
|
||||
| m+1, ff := m + 3
|
||||
end
|
||||
|
||||
When a ``match`` has only one line, the vertical bar may be left out. In that case, Lean provides alternative syntax with a destructuring ``let``, as well as a destructuring lambda abstraction. Thus the following definitions all have the same net effect.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
def bar₁ : Nat × Nat → Nat
|
||||
| (m, n) := m + n
|
||||
|
||||
def bar₂ (p : Nat × Nat) : Nat :=
|
||||
match p with (m, n) := m + n end
|
||||
|
||||
def bar₃ : Nat × Nat → Nat :=
|
||||
fun ⟨m, n⟩ => m + n
|
||||
|
||||
def bar₄ (p : Nat × Nat) : Nat :=
|
||||
let ⟨m, n⟩ := p in m + n
|
||||
|
||||
Information about the term being matched can be preserved in each branch using the syntax `match h : t with`. For example, a user may want to match a term `ns ++ ms : List Nat`, while tracking the hypothesis `ns ++ ms = []` or `ns ++ ms= h :: t` in the respective match arm:
|
||||
|
||||
```lean
|
||||
def foo (ns ms : List Nat) (h1 : ns ++ ms ≠ []) (k : Nat -> Char) : Char :=
|
||||
match h2 : ns ++ ms with
|
||||
-- in this arm, we have the hypothesis `h2 : ns ++ ms = []`
|
||||
| [] => absurd h2 h1
|
||||
-- in this arm, we have the hypothesis `h2 : ns ++ ms = h :: t`
|
||||
| h :: t => k h
|
||||
|
||||
-- '7'
|
||||
#eval foo [7, 8, 9] [] (by decide) Nat.digitChar
|
||||
```
|
||||
|
||||
.. _structures_and_records:
|
||||
|
||||
Structures and Records
|
||||
======================
|
||||
|
||||
The ``structure`` command in Lean is used to define an inductive data type with a single constructor and to define its projections at the same time. The syntax is as follows:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
structure foo (a : α) extends bar, baz : Sort u :=
|
||||
constructor :: (field₁ : β₁) ... (fieldₙ : βₙ)
|
||||
|
||||
Here ``(a : α)`` is a telescope, that is, the parameters to the inductive definition. The name ``constructor`` followed by the double colon is optional; if it is not present, the name ``mk`` is used by default. The keyword ``extends`` followed by a list of previously defined structures is also optional; if it is present, an instance of each of these structures is included among the fields to ``foo,`` and the types ``βᵢ`` can refer to their fields as well. The output type, ``Sort u``, can be omitted, in which case Lean infers to smallest non-``Prop`` sort possible. Finally, ``(field₁ : β₁) ... (fieldₙ : βₙ)`` is a telescope relative to ``(a : α)`` and the fields in ``bar`` and ``baz``.
|
||||
|
||||
The declaration above is syntactic sugar for an inductive type declaration, and so results in the addition of the following constants to the environment:
|
||||
|
||||
- the type former : ``foo : Π (a : α), Sort u``
|
||||
- the single constructor :
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
foo.constructor : Π (a : α) (_to_foo : foo) (_to_bar : bar)
|
||||
(field₁ : β₁) ... (fieldₙ : βₙ), foo a
|
||||
|
||||
- the eliminator ``foo.rec`` for the inductive type with that constructor
|
||||
|
||||
In addition, Lean defines
|
||||
|
||||
- the projections : ``fieldᵢ : Π (a : α) (c : foo) : βᵢ`` for each ``i``
|
||||
|
||||
where any other fields mentioned in ``βᵢ`` are replaced by the relevant projections from ``c``.
|
||||
|
||||
Given ``c : foo``, Lean offers the following convenient syntax for the projection ``foo.fieldᵢ c``:
|
||||
|
||||
- *anonymous projections* : ``c.fieldᵢ``
|
||||
- *numbered projections* : ``c.i``
|
||||
|
||||
These can be used in any situation where Lean can infer that the type of ``c`` is of the form ``foo a``. The convention for anonymous projections is extended to any function ``f`` defined in the namespace ``foo``, as described in [Namespaces](namespaces.md).
|
||||
|
||||
Similarly, Lean offers the following convenient syntax for constructing elements of ``foo``. They are equivalent to ``foo.constructor b₁ b₂ f₁ f₁ ... fₙ``, where ``b₁ : foo``, ``b₂ : bar``, and each ``fᵢ : βᵢ`` :
|
||||
|
||||
- *anonymous constructor*: ``⟨ b₁, b₂, f₁, ..., fₙ ⟩``
|
||||
- *record notation*:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{ foo . to_bar := b₁, to_baz := b₂, field₁ := f₁, ...,
|
||||
fieldₙ := fₙ }
|
||||
|
||||
The anonymous constructor can be used in any context where Lean can infer that the expression should have a type of the form ``foo a``. The unicode brackets are entered as ``\<`` and ``\>`` respectively. The tokens ``(|`` and ``|)`` are ascii equivalents.
|
||||
|
||||
When using record notation, you can omit the annotation ``foo .`` when Lean can infer that the expression should have a type of the form ``foo a``. You can replace either ``to_bar`` or ``to_baz`` by assignments to *their* fields as well, essentially acting as though the fields of ``bar`` and ``baz`` are simply imported into ``foo``. Finally, record notation also supports
|
||||
|
||||
- *record updates*: ``{ t with ... fieldᵢ := fᵢ ...}``
|
||||
|
||||
Here ``t`` is a term of type ``foo a`` for some ``a``. The notation instructs Lean to take values from ``t`` for any field assignment that is omitted from the list.
|
||||
|
||||
Lean also allows you to specify a default value for any field in a structure by writing ``(fieldᵢ : βᵢ := t)``. Here ``t`` specifies the value to use when the field ``fieldᵢ`` is left unspecified in an instance of record notation.
|
||||
|
||||
.. code-block:: lean
|
||||
|
||||
universes u v
|
||||
|
||||
structure vec (α : Type u) (n : Nat) :=
|
||||
(l : list α) (h : l.length = n)
|
||||
|
||||
structure foo (α : Type u) (β : Nat → Type v) : Type (max u v) :=
|
||||
(a : α) (n : Nat) (b : β n)
|
||||
|
||||
structure bar :=
|
||||
(c : Nat := 8) (d : Nat)
|
||||
|
||||
structure baz extends foo Nat (vec Nat), bar :=
|
||||
(v : vec Nat n)
|
||||
|
||||
#check foo
|
||||
#check @foo.mk
|
||||
#check @foo.rec
|
||||
|
||||
#check foo.a
|
||||
#check foo.n
|
||||
#check foo.b
|
||||
|
||||
#check baz
|
||||
#check @baz.mk
|
||||
#check @baz.rec
|
||||
|
||||
#check baz.to_foo
|
||||
#check baz.to_bar
|
||||
#check baz.v
|
||||
|
||||
def bzz := vec.mk [1, 2, 3] rfl
|
||||
|
||||
#check vec.l bzz
|
||||
#check vec.h bzz
|
||||
#check bzz.l
|
||||
#check bzz.h
|
||||
#check bzz.1
|
||||
#check bzz.2
|
||||
|
||||
example : vec Nat 3 := vec.mk [1, 2, 3] rfl
|
||||
example : vec Nat 3 := ⟨[1, 2, 3], rfl⟩
|
||||
example : vec Nat 3 := (| [1, 2, 3], rfl |)
|
||||
example : vec Nat 3 := { vec . l := [1, 2, 3], h := rfl }
|
||||
example : vec Nat 3 := { l := [1, 2, 3], h := rfl }
|
||||
|
||||
example : foo Nat (vec Nat) := ⟨1, 3, bzz⟩
|
||||
|
||||
example : baz := ⟨⟨1, 3, bzz⟩, ⟨5, 7⟩, bzz⟩
|
||||
example : baz := { a := 1, n := 3, b := bzz, c := 5, d := 7, v := bzz}
|
||||
def fzz : foo Nat (vec Nat) := {a := 1, n := 3, b := bzz}
|
||||
|
||||
example : foo Nat (vec Nat) := { fzz with a := 7 }
|
||||
example : baz := { fzz with c := 5, d := 7, v := bzz }
|
||||
|
||||
example : bar := { c := 8, d := 9 }
|
||||
example : bar := { d := 9 } -- uses the default value for c
|
||||
|
||||
.. _type_classes:
|
||||
|
||||
Type Classes
|
||||
============
|
||||
|
||||
(Classes and instances. Anonymous instances. Local instances.)
|
||||
|
||||
|
||||
.. [Dybjer] Dybjer, Peter, *Inductive Families*. Formal Aspects of Computing 6, 1994, pages 440-465.
|
||||
29
doc/decltypes.md
Normal file
29
doc/decltypes.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Declaring New Types
|
||||
|
||||
In Lean's library, every concrete type other than the universes and every type constructor other than the dependent function type is
|
||||
an instance of a general family of type constructions known as *inductive types*. It is remarkable that it is possible to develop
|
||||
complex programs and formalize mathematics based on nothing more than the type universes, dependent function types,
|
||||
and inductive types; everything else follows from those.
|
||||
|
||||
Intuitively, an inductive type is built up from a specified list of constructors. In Lean, the basic syntax for specifying such a type is as follows:
|
||||
```
|
||||
inductive NewType where
|
||||
| constructor_1 : ... → NewType
|
||||
| constructor_2 : ... → NewType
|
||||
...
|
||||
| constructor_n : ... → NewType
|
||||
```
|
||||
|
||||
The intuition is that each constructor specifies a way of building new objects of ``NewType``, possibly from previously constructed values.
|
||||
The type ``NewType`` consists of nothing more than the objects that are constructed in this way.
|
||||
|
||||
We will see below that the arguments to the constructors can include objects of type ``NewType``,
|
||||
subject to a certain "positivity" constraint, which guarantees that elements of ``NewType`` are built
|
||||
from the bottom up. Roughly speaking, each ``...`` can be any function type constructed from ``NewType``
|
||||
and previously defined types, in which ``NewType`` appears, if at all, only as the "target" of the function type.
|
||||
|
||||
We will provide a number of examples of inductive types. We will also consider slight generalizations of the scheme above,
|
||||
to mutually defined inductive types, and so-called *inductive families*.
|
||||
|
||||
Every inductive type comes with constructors, which show how to construct an element of the type, and elimination rules,
|
||||
which show how to "use" an element of the type in another construction.
|
||||
1
doc/definitions.md
Normal file
1
doc/definitions.md
Normal file
@@ -0,0 +1 @@
|
||||
# Definitions
|
||||
66
doc/dep.md
Normal file
66
doc/dep.md
Normal file
@@ -0,0 +1,66 @@
|
||||
## What makes dependent type theory dependent?
|
||||
|
||||
The short explanation is that what makes dependent type theory dependent is that types can depend on parameters.
|
||||
You have already seen a nice example of this: the type ``List α`` depends on the argument ``α``, and
|
||||
this dependence is what distinguishes ``List Nat`` and ``List Bool``.
|
||||
For another example, consider the type ``Vector α n``, the type of vectors of elements of ``α`` of length ``n``.
|
||||
This type depends on *two* parameters: the type ``α : Type`` of the elements in the vector and the length ``n : Nat``.
|
||||
|
||||
Suppose we wish to write a function ``cons`` which inserts a new element at the head of a list.
|
||||
What type should ``cons`` have? Such a function is *polymorphic*: we expect the ``cons`` function for ``Nat``, ``Bool``,
|
||||
or an arbitrary type ``α`` to behave the same way.
|
||||
So it makes sense to take the type to be the first argument to ``cons``, so that for any type, ``α``, ``cons α``
|
||||
is the insertion function for lists of type ``α``. In other words, for every ``α``, ``cons α`` is the function that takes an element ``a : α``
|
||||
and a list ``as : List α``, and returns a new list, so we have ``cons α a as : list α``.
|
||||
|
||||
It is clear that ``cons α`` should have type ``α → List α → List α``. But what type should ``cons`` have?
|
||||
A first guess might be ``Type → α → list α → list α``, but, on reflection, this does not make sense:
|
||||
the ``α`` in this expression does not refer to anything, whereas it should refer to the argument of type ``Type``.
|
||||
In other words, *assuming* ``α : Type`` is the first argument to the function, the type of the next two elements are ``α`` and ``List α``.
|
||||
These types vary depending on the first argument, ``α``.
|
||||
|
||||
This is an instance of a *dependent function type*, or *dependent arrow type*. Given ``α : Type`` and ``β : α → Type``,
|
||||
think of ``β`` as a family of types over ``α``, that is, a type ``β a`` for each ``a : α``.
|
||||
In that case, the type ``(a : α) → β a`` denotes the type of functions ``f`` with the property that,
|
||||
for each ``a : α``, ``f a`` is an element of ``β a``. In other words, the type of the value returned by ``f`` depends on its input.
|
||||
|
||||
Notice that ``(a : α) → β`` makes sense for any expression ``β : Type``. When the value of ``β`` depends on ``a``
|
||||
(as does, for example, the expression ``β a`` in the previous paragraph), ``(a : α) → β`` denotes a dependent function type.
|
||||
When ``β`` doesn't depend on ``a``, ``(a : α) → β`` is no different from the type ``α → β``.
|
||||
Indeed, in dependent type theory (and in Lean), ``α → β`` is just notation for ``(a : α) → β`` when ``β`` does not depend on ``a``.
|
||||
|
||||
Returning to the example of lists, we can use the command `#check` to inspect the type of the following `List` functions
|
||||
We will explain the ``@`` symbol and the difference between the round and curly braces momentarily.
|
||||
|
||||
```lean
|
||||
#check @List.cons -- {α : Type u_1} → α → List α → List α
|
||||
#check @List.nil -- {α : Type u_1} → List α
|
||||
#check @List.length -- {α : Type u_1} → List α → Nat
|
||||
#check @List.append -- {α : Type u_1} → List α → List α → List α
|
||||
```
|
||||
|
||||
Just as dependent function types ``(a : α) → β a`` generalize the notion of a function type ``α → β`` by allowing ``β`` to depend on ``α``,
|
||||
dependent Cartesian product types ``(a : α) × β a`` generalize the Cartesian product ``α × β`` in the same way. Dependent products are also
|
||||
called *sigma* types, and you can also write them as `Σ a : α, β a`. You can use `⟨a, b⟩` or `Sigma.mk a b` to create a dependent pair.
|
||||
|
||||
```lean
|
||||
universe u v
|
||||
|
||||
def f (α : Type u) (β : α → Type v) (a : α) (b : β a) : (a : α) × β a :=
|
||||
⟨a, b⟩
|
||||
|
||||
def g (α : Type u) (β : α → Type v) (a : α) (b : β a) : Σ a : α, β a :=
|
||||
Sigma.mk a b
|
||||
|
||||
#reduce f
|
||||
#reduce g
|
||||
|
||||
#reduce f Type (fun α => α) Nat 10
|
||||
#reduce g Type (fun α => α) Nat 10
|
||||
|
||||
#reduce (f Type (fun α => α) Nat 10).1 -- Nat
|
||||
#reduce (g Type (fun α => α) Nat 10).1 -- Nat
|
||||
#reduce (f Type (fun α => α) Nat 10).2 -- 10
|
||||
#reduce (g Type (fun α => α) Nat 10).2 -- 10
|
||||
```
|
||||
The function `f` and `g` above denote the same function.
|
||||
3
doc/deptypes.md
Normal file
3
doc/deptypes.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Dependent Types
|
||||
|
||||
In this section, we introduce simple type theory, types as objects, definitions, and explain what makes dependent type theory *dependent*.
|
||||
@@ -22,7 +22,7 @@ stage1/
|
||||
lib/
|
||||
lean/**/*.olean # the Lean library (incl. the compiler) compiled by the previous stage's `lean`
|
||||
temp/**/*.{c,o} # the library extracted to C and compiled by `leanc`
|
||||
libInit.a libLean.a # static libraries of the Lean library
|
||||
libInit.a libStd.a libLean.a # static libraries of the Lean library
|
||||
libleancpp.a # a static library of the C++ sources of Lean
|
||||
libleanshared.so # a dynamic library including the static libraries above
|
||||
bin/
|
||||
@@ -65,59 +65,19 @@ You now have a Lean binary and library that include your changes, though their
|
||||
own compilation was not influenced by them, that you can use to test your
|
||||
changes on test programs whose compilation *will* be influenced by the changes.
|
||||
|
||||
## Updating stage0
|
||||
|
||||
Finally, when we want to use new language features in the library, we need to
|
||||
update the archived C source code of the stage 0 compiler in `stage0/src`.
|
||||
|
||||
The github repository will automatically update stage0 on `master` once
|
||||
`src/stdlib_flags.h` and `stage0/src/stdlib_flags.h` are out of sync.
|
||||
|
||||
If you have write access to the lean4 repository, you can also manually
|
||||
trigger that process, for example to be able to use new features in the compiler itself.
|
||||
You can do that on <https://github.com/leanprover/lean4/actions/workflows/update-stage0.yml>
|
||||
or using Github CLI with
|
||||
```
|
||||
gh workflow run update-stage0.yml
|
||||
```
|
||||
|
||||
Leaving stage0 updates to the CI automation is preferable, but should you need
|
||||
to do it locally, you can use `make update-stage0-commit` in `build/release` to
|
||||
update `stage0` from `stage1` or `make -C stageN update-stage0-commit` to
|
||||
update from another stage. This command will automatically stage the updated files
|
||||
and introduce a commit,so make sure to commit your work before that.
|
||||
|
||||
If you rebased the branch (either onto a newer version of `master`, or fixing
|
||||
up some commits prior to the stage0 update, recreate the stage0 update commits.
|
||||
The script `script/rebase-stage0.sh` can be used for that.
|
||||
|
||||
The CI should prevent PRs with changes to stage0 (besides `stdlib_flags.h`)
|
||||
from entering `master` through the (squashing!) merge queue, and label such PRs
|
||||
with the `changes-stage0` label. Such PRs should have a cleaned up history,
|
||||
with separate stage0 update commits; then coordinate with the admins to merge
|
||||
your PR using rebase merge, bypassing the merge queue.
|
||||
|
||||
update the stage 0 compiler, which can be done via `make -C stageN update-stage0`.
|
||||
`make update-stage0` without `-C` defaults to stage1.
|
||||
|
||||
## Further Bootstrapping Complications
|
||||
|
||||
As written above, changes in meta code in the current stage usually will only
|
||||
affect later stages. This is an issue in two specific cases.
|
||||
|
||||
* For the special case of *quotations*, it is desirable to have changes in builtin parsers affect them immediately: when the changes in the parser become active in the next stage, builtin macros implemented via quotations should generate syntax trees compatible with the new parser, and quotation patterns in builtin macros and elaborators should be able to match syntax created by the new parser and macros.
|
||||
Since quotations capture the syntax tree structure during execution of the current stage and turn it into code for the next stage, we need to run the current stage's builtin parsers in quotations via the interpreter for this to work.
|
||||
Caveats:
|
||||
* We activate this behavior by default when building stage 1 by setting `-Dinternal.parseQuotWithCurrentStage=true`.
|
||||
We force-disable it inside `macro/macro_rules/elab/elab_rules` via `suppressInsideQuot` as they are guaranteed not to run in the next stage and may need to be run in the current one, so the stage 0 parser is the correct one to use for them.
|
||||
It may be necessary to extend this disabling to functions that contain quotations and are (exclusively) used by one of the mentioned commands. A function using quotations should never be used by both builtin and non-builtin macros/elaborators. Example: https://github.com/leanprover/lean4/blob/f70b7e5722da6101572869d87832494e2f8534b7/src/Lean/Elab/Tactic/Config.lean#L118-L122
|
||||
* The parser needs to be reachable via an `import` statement, otherwise the version of the previous stage will silently be used.
|
||||
* Only the parser code (`Parser.fn`) is affected; all metadata such as leading tokens is taken from the previous stage.
|
||||
|
||||
For an example, see https://github.com/leanprover/lean4/commit/f9dcbbddc48ccab22c7674ba20c5f409823b4cc1#diff-371387aed38bb02bf7761084fd9460e4168ae16d1ffe5de041b47d3ad2d22422R13
|
||||
|
||||
* For *non-builtin* meta code such as `notation`s or `macro`s in
|
||||
`Notation.lean`, we expect changes to affect the current file and all later
|
||||
files of the same stage immediately, just like outside the stdlib. To ensure
|
||||
this, we build stage 1 using `-Dinterpreter.prefer_native=false` -
|
||||
this, we need to build the stage using `-Dinterpreter.prefer_native=false` -
|
||||
otherwise, when executing a macro, the interpreter would notice that there is
|
||||
already a native symbol available for this function and run it instead of the
|
||||
new IR, but the symbol is from the previous stage!
|
||||
@@ -135,11 +95,26 @@ affect later stages. This is an issue in two specific cases.
|
||||
further stages (e.g. after an `update-stage0`) will then need to be compiled
|
||||
with the flag set to `false` again since they will expect the new signature.
|
||||
|
||||
When enabling `prefer_native`, we usually want to *disable* `parseQuotWithCurrentStage` as it would otherwise make quotations use the interpreter after all.
|
||||
However, there is a specific case where we want to set both options to `true`: when we make changes to a non-builtin parser like `simp` that has a builtin elaborator, we cannot have the new parser be active outside of quotations in stage 1 as the builtin elaborator from stage 0 would not understand them; on the other hand, we need quotations in e.g. the builtin `simp` elaborator to produce the new syntax in the next stage.
|
||||
As this issue usually affects only tactics, enabling `debug.byAsSorry` instead of `prefer_native` can be a simpler solution.
|
||||
For an example, see https://github.com/leanprover/lean4/commit/da4c46370d85add64ef7ca5e7cc4638b62823fbb.
|
||||
|
||||
For a `prefer_native` example, see https://github.com/leanprover/lean4/commit/da4c46370d85add64ef7ca5e7cc4638b62823fbb.
|
||||
* For the special case of *quotations*, it is desirable to have changes in
|
||||
built-in parsers affect them immediately: when the changes in the parser
|
||||
become active in the next stage, macros implemented via quotations should
|
||||
generate syntax trees compatible with the new parser, and quotation patterns
|
||||
in macro and elaborators should be able to match syntax created by the new
|
||||
parser and macros. Since quotations capture the syntax tree structure during
|
||||
execution of the current stage and turn it into code for the next stage, we
|
||||
need to run the current stage's built-in parsers in quotation via the
|
||||
interpreter for this to work. Caveats:
|
||||
* Since interpreting full parsers is not nearly as cheap and we rarely change
|
||||
built-in syntax, this needs to be opted in using `-Dinternal.parseQuotWithCurrentStage=true`.
|
||||
* The parser needs to be reachable via an `import` statement, otherwise the
|
||||
version of the previous stage will silently be used.
|
||||
* Only the parser code (`Parser.fn`) is affected; all metadata such as leading
|
||||
tokens is taken from the previous stage.
|
||||
|
||||
For an example, see https://github.com/leanprover/lean4/commit/f9dcbbddc48ccab22c7674ba20c5f409823b4cc1#diff-371387aed38bb02bf7761084fd9460e4168ae16d1ffe5de041b47d3ad2d22422
|
||||
(from before the flag defaulted to `false`).
|
||||
|
||||
To modify either of these flags both for building and editing the stdlib, adjust
|
||||
the code in `stage0/src/stdlib_flags.h`. The flags will automatically be reset
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
Git Commit Convention
|
||||
=====================
|
||||
|
||||
We are using the following convention for writing git commit messages. For pull
|
||||
requests, make sure the pull request title and description follow this
|
||||
convention, as the squash-merge commit will inherit title and body from the
|
||||
pull request.
|
||||
|
||||
This convention is based on the one from the AngularJS project ([doc][angularjs-doc],
|
||||
We are using the following convention for writing git-commit messages.
|
||||
It is based on the one from AngularJS project([doc][angularjs-doc],
|
||||
[commits][angularjs-git]).
|
||||
|
||||
|
||||
[angularjs-git]: https://github.com/angular/angular.js/commits/master
|
||||
[angularjs-doc]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
|
||||
|
||||
@@ -33,9 +28,6 @@ Format of the commit message
|
||||
- chore (maintain, ex: travis-ci)
|
||||
- perf (performance improvement, optimization, ...)
|
||||
|
||||
Every `feat` or `fix` commit must have a `changelog-*` label, and a commit message
|
||||
beginning with "This PR " that will be included in the changelog.
|
||||
|
||||
``<subject>`` has the following constraints:
|
||||
|
||||
- use imperative, present tense: "change" not "changed" nor "changes"
|
||||
@@ -47,7 +39,6 @@ beginning with "This PR " that will be included in the changelog.
|
||||
- just as in ``<subject>``, use imperative, present tense
|
||||
- includes motivation for the change and contrasts with previous
|
||||
behavior
|
||||
- If a `changelog-*` label is present, the body must begin with "This PR ".
|
||||
|
||||
``<footer>`` is optional and may contain two items:
|
||||
|
||||
@@ -64,21 +55,17 @@ Examples
|
||||
|
||||
fix: add declarations for operator<<(std::ostream&, expr const&) and operator<<(std::ostream&, context const&) in the kernel
|
||||
|
||||
This PR adds declarations `operator<<` for raw printing.
|
||||
The actual implementation of these two operators is outside of the
|
||||
kernel. They are implemented in the file 'library/printer.cpp'.
|
||||
|
||||
We declare them in the kernel to prevent the following problem.
|
||||
Suppose there is a file 'foo.cpp' that does not include 'library/printer.h',
|
||||
kernel. They are implemented in the file 'library/printer.cpp'. We
|
||||
declare them in the kernel to prevent the following problem. Suppose
|
||||
there is a file 'foo.cpp' that does not include 'library/printer.h',
|
||||
but contains
|
||||
```cpp
|
||||
expr a;
|
||||
...
|
||||
std::cout << a << "\n";
|
||||
...
|
||||
```
|
||||
|
||||
expr a;
|
||||
...
|
||||
std::cout << a << "\n";
|
||||
...
|
||||
|
||||
The compiler does not generate an error message. It silently uses the
|
||||
operator bool() to coerce the expression into a Boolean. This produces
|
||||
counter-intuitive behavior, and may confuse developers.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Some notes on how to debug Lean, which may also be applicable to debugging Lean
|
||||
|
||||
## Tracing
|
||||
|
||||
In `CoreM` and derived monads, we use `trace[traceCls] "msg with {interpolations}"` to fill the structured trace viewable with `set_option trace.traceCls true`.
|
||||
In `CoreM` and derived monads, we use `trace![traceCls] "msg with {interpolations}"` to fill the structured trace viewable with `set_option trace.traceCls true`.
|
||||
New trace classes have to be registered using `registerTraceClass` first.
|
||||
|
||||
Notable trace classes:
|
||||
@@ -22,9 +22,7 @@ Notable trace classes:
|
||||
|
||||
In pure contexts or when execution is aborted before the messages are finally printed, one can instead use the term `dbg_trace "msg with {interpolations}"; val` (`;` can also be replaced by a newline), which will print the message to stderr before evaluating `val`. `dbgTraceVal val` can be used as a shorthand for `dbg_trace "{val}"; val`.
|
||||
Note that if the return value is not actually used, the trace code is silently dropped as well.
|
||||
|
||||
By default, such stderr output is buffered and shown as messages after a command has been elaborated, which is necessary to ensure deterministic ordering of messages under parallelism.
|
||||
If Lean aborts the process before it can finish the command or takes too long to do that, using `-DstderrAsMessages=false` avoids this buffering and shows `dbg_trace` output (but not `trace`s or other diagnostics) immediately.
|
||||
In the language server, stderr output is buffered and shown as messages after a command has been elaborated, unless the option `server.stderrAsMessages` is deactivated.
|
||||
|
||||
## Debuggers
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ There are two primary attributes for interoperating with other languages:
|
||||
It can also be used with `def` to provide an internal definition, but ensuring consistency of both definitions is up to the user.
|
||||
* `@[export sym] def leanSym : ...` exports `leanSym` under the unmangled symbol name `sym`.
|
||||
|
||||
For simple examples of how to call foreign code from Lean and vice versa, see <https://github.com/leanprover/lean4/blob/master/src/lake/examples/ffi> and <https://github.com/leanprover/lean4/blob/master/src/lake/examples/reverse-ffi>, respectively.
|
||||
|
||||
## The Lean ABI
|
||||
|
||||
The Lean Application Binary Interface (ABI) describes how the signature of a Lean declaration is encoded as a native calling convention.
|
||||
@@ -23,7 +21,7 @@ If `n` is 0, the corresponding C declaration is
|
||||
extern s sym;
|
||||
```
|
||||
where `s` is the C translation of `β` as specified in the next section.
|
||||
In the case of an `@[extern]` definition, the symbol's value is guaranteed to be initialized only after calling the Lean module's initializer or that of an importing module; see [Initialization](#initialization).
|
||||
In the case of an `@[extern]` definition, the symbol's value is guaranteed to be initialized only after calling the Lean module's initializer or that of an importing module; see [Initialization](.#init).
|
||||
|
||||
If `n` is greater than 0, the corresponding C declaration is
|
||||
```c
|
||||
@@ -34,7 +32,7 @@ In the case of `@[extern]` all *irrelevant* types are removed first; see next se
|
||||
|
||||
### Translating Types from Lean to C
|
||||
|
||||
* The integer types `UInt8`, ..., `UInt64`, `USize` are represented by the C types `uint8_t`, ..., `uint64_t`, `size_t`, respectively
|
||||
* The integer types `UInt8`, ..., `UInt64`, `USize` are represented by the C types `uint8_t`, ..., `uint64_t`, `usize_t`, respectively
|
||||
* `Char` is represented by `uint32_t`
|
||||
* `Float` is represented by `double`
|
||||
* An *enum* inductive type of at least 2 and at most 2^32 constructors, each of which with no parameters, is represented by the first type of `uint8_t`, `uint16_t`, `uint32_t` that is sufficient to represent all constructor indices.
|
||||
@@ -45,68 +43,17 @@ In the case of `@[extern]` all *irrelevant* types are removed first; see next se
|
||||
* it is none of the types described above
|
||||
* it is not marked `unsafe`
|
||||
* it has a single constructor with a single parameter of *relevant* type
|
||||
|
||||
is represented by the representation of that parameter's type.
|
||||
|
||||
|
||||
For example, `{ x : α // p }`, the `Subtype` structure of a value of type `α` and an irrelevant proof, is represented by the representation of `α`.
|
||||
Similarly, the signed integer types `Int8`, ..., `Int64`, `ISize` are also represented by the unsigned C types `uint8_t`, ..., `uint64_t`, `size_t`, respectively, because they have a trivial structure.
|
||||
* `Nat` and `Int` are represented by `lean_object *`.
|
||||
Their runtime values is either a pointer to an opaque bignum object or, if the lowest bit of the "pointer" is 1 (`lean_is_scalar`), an encoded unboxed natural number or integer (`lean_box`/`lean_unbox`).
|
||||
* `Nat` is represented by `lean_object *`.
|
||||
Its runtime value is either a pointer to an opaque bignum object or, if the lowest bit of the "pointer" is 1 (`lean_is_scalar`), an encoded unboxed natural number (`lean_box`/`lean_unbox`).
|
||||
* A universe `Sort u`, type constructor `... → Sort u`, or proposition `p : Prop` is *irrelevant* and is either statically erased (see above) or represented as a `lean_object *` with the runtime value `lean_box(0)`
|
||||
* Any other type is represented by `lean_object *`.
|
||||
Its runtime value is a pointer to an object of a subtype of `lean_object` (see the "Inductive types" section below) or the unboxed value `lean_box(cidx)` for the `cidx`th constructor of an inductive type if this constructor does not have any relevant parameters.
|
||||
Its runtime value is a pointer to an object of a subtype of `lean_object` (see respective declarations in `lean.h`) or the unboxed value `lean_box(cidx)` for the `cidx`th constructor of an inductive type if this constructor does not have any relevant parameters.
|
||||
|
||||
Example: the runtime value of `u : Unit` is always `lean_box(0)`.
|
||||
|
||||
#### Inductive types
|
||||
|
||||
For inductive types which are in the fallback `lean_object *` case above and not trivial constructors, the type is stored as a `lean_ctor_object`, and `lean_is_ctor` will return true. A `lean_ctor_object` stores the constructor index in the header, and the fields are stored in the `m_objs` portion of the object.
|
||||
|
||||
The memory order of the fields is derived from the types and order of the fields in the declaration. They are ordered as follows:
|
||||
|
||||
* Non-scalar fields stored as `lean_object *`
|
||||
* Fields of type `USize`
|
||||
* Other scalar fields, in decreasing order by size
|
||||
|
||||
Within each group the fields are ordered in declaration order. **Warning**: Trivial wrapper types still count toward a field being treated as non-scalar for this purpose.
|
||||
|
||||
* To access fields of the first kind, use `lean_ctor_get(val, i)` to get the `i`th non-scalar field.
|
||||
* To access `USize` fields, use `lean_ctor_get_usize(val, n+i)` to get the `i`th usize field and `n` is the total number of fields of the first kind.
|
||||
* To access other scalar fields, use `lean_ctor_get_uintN(val, off)` or `lean_ctor_get_usize(val, off)` as appropriate. Here `off` is the byte offset of the field in the structure, starting at `n*sizeof(void*)` where `n` is the number of fields of the first two kinds.
|
||||
|
||||
For example, a structure such as
|
||||
```lean
|
||||
structure S where
|
||||
ptr_1 : Array Nat
|
||||
usize_1 : USize
|
||||
sc64_1 : UInt64
|
||||
ptr_2 : { x : UInt64 // x > 0 } -- wrappers don't count as scalars
|
||||
sc64_2 : Float -- `Float` is 64 bit
|
||||
sc8_1 : Bool
|
||||
sc16_1 : UInt16
|
||||
sc8_2 : UInt8
|
||||
sc64_3 : UInt64
|
||||
usize_2 : USize
|
||||
ptr_3 : Char -- trivial wrapper around `UInt32`
|
||||
sc32_1 : UInt32
|
||||
sc16_2 : UInt16
|
||||
```
|
||||
would get re-sorted into the following memory order:
|
||||
|
||||
* `S.ptr_1` - `lean_ctor_get(val, 0)`
|
||||
* `S.ptr_2` - `lean_ctor_get(val, 1)`
|
||||
* `S.ptr_3` - `lean_ctor_get(val, 2)`
|
||||
* `S.usize_1` - `lean_ctor_get_usize(val, 3)`
|
||||
* `S.usize_2` - `lean_ctor_get_usize(val, 4)`
|
||||
* `S.sc64_1` - `lean_ctor_get_uint64(val, sizeof(void*)*5)`
|
||||
* `S.sc64_2` - `lean_ctor_get_float(val, sizeof(void*)*5 + 8)`
|
||||
* `S.sc64_3` - `lean_ctor_get_uint64(val, sizeof(void*)*5 + 16)`
|
||||
* `S.sc32_1` - `lean_ctor_get_uint32(val, sizeof(void*)*5 + 24)`
|
||||
* `S.sc16_1` - `lean_ctor_get_uint16(val, sizeof(void*)*5 + 28)`
|
||||
* `S.sc16_2` - `lean_ctor_get_uint16(val, sizeof(void*)*5 + 30)`
|
||||
* `S.sc8_1` - `lean_ctor_get_uint8(val, sizeof(void*)*5 + 32)`
|
||||
* `S.sc8_2` - `lean_ctor_get_uint8(val, sizeof(void*)*5 + 33)`
|
||||
|
||||
### Borrowing
|
||||
|
||||
By default, all `lean_object *` parameters of an `@[extern]` function are considered *owned*, i.e. the external code is passed a "virtual RC token" and is responsible for passing this token along to another consuming function (exactly once) or freeing it via `lean_dec`.
|
||||
@@ -122,13 +69,13 @@ When including Lean code as part of a larger program, modules must be *initializ
|
||||
Module initialization entails
|
||||
* initialization of all "constants" (nullary functions), including closed terms lifted out of other functions
|
||||
* execution of all `[init]` functions
|
||||
* execution of all `[builtin_init]` functions, if the `builtin` parameter of the module initializer has been set
|
||||
* execution of all `[builtinInit]` functions, if the `builtin` parameter of the module initializer has been set
|
||||
|
||||
The module initializer is automatically run with the `builtin` flag for executables compiled from Lean code and for "plugins" loaded with `lean --plugin`.
|
||||
For all other modules imported by `lean`, the initializer is run without `builtin`.
|
||||
Thus `[init]` functions are run iff their module is imported, regardless of whether they have native code available or not, while `[builtin_init]` functions are only run for native executable or plugins, regardless of whether their module is imported or not.
|
||||
Thus `[init]` functions are run iff their module is imported, regardless of whether they have native code available or not, while `[builtinInit]` functions are only run for native executable or plugins, regardless of whether their module is imported or not.
|
||||
`lean` uses built-in initializers for e.g. registering basic parsers that should be available even without importing their module (which is necessary for bootstrapping).
|
||||
|
||||
|
||||
The initializer for module `A.B` is called `initialize_A_B` and will automatically initialize any imported modules.
|
||||
Module initializers are idempotent (when run with the same `builtin` flag), but not thread-safe.
|
||||
Together with initialization of the Lean runtime, you should execute code like the following exactly once before accessing any Lean declarations:
|
||||
@@ -140,7 +87,7 @@ lean_object * initialize_C(uint8_t builtin, lean_object *);
|
||||
...
|
||||
|
||||
lean_initialize_runtime_module();
|
||||
//lean_initialize(); // necessary (and replaces `lean_initialize_runtime_module`) if you (indirectly) access the `Lean` package
|
||||
//lean_initialize(); // necessary if you (indirectly) access the `Lean` package
|
||||
|
||||
lean_object * res;
|
||||
// use same default as for Lean executables
|
||||
@@ -161,15 +108,6 @@ if (lean_io_result_is_ok(res)) {
|
||||
lean_io_mark_end_initialization();
|
||||
```
|
||||
|
||||
In addition, any other thread not spawned by the Lean runtime itself must be initialized for Lean use by calling
|
||||
```c
|
||||
void lean_initialize_thread();
|
||||
```
|
||||
and should be finalized in order to free all thread-local resources by calling
|
||||
```c
|
||||
void lean_finalize_thread();
|
||||
```
|
||||
|
||||
## `@[extern]` in the Interpreter
|
||||
|
||||
The interpreter can run Lean declarations for which symbols are available in loaded shared libraries, which includes `@[extern]` declarations.
|
||||
@@ -180,4 +118,4 @@ Thus to e.g. run `#eval` on such a declaration, you need to
|
||||
Note that it is not sufficient to load the foreign library containing the external symbol because the interpreter depends on code that is emitted for each `@[extern]` declaration.
|
||||
Thus it is not possible to interpret an `@[extern]` declaration in the same file.
|
||||
|
||||
See [`tests/compiler/foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) for an example.
|
||||
See `tests/compiler/foreign` for an example.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Development Workflow
|
||||
|
||||
If you want to make changes to Lean itself, start by [building Lean](../make/index.md) from a clean checkout to make sure that everything is set up correctly.
|
||||
If you want to make changes to Lean itself, start by [building Lean](../make/index.html) from a clean checkout to make sure that everything is set up correctly.
|
||||
After that, read on below to find out how to set up your editor for changing the Lean source code, followed by further sections of the development manual where applicable such as on the [test suite](testing.md) and [commit convention](commit_convention.md).
|
||||
|
||||
If you are planning to make any changes that may affect the compilation of Lean itself, e.g. changes to the parser, elaborator, or compiler, you should first read about the [bootstrapping pipeline](bootstrap.md).
|
||||
@@ -30,14 +30,20 @@ powershell -f elan-init.ps1 --default-toolchain none
|
||||
del elan-init.ps1
|
||||
```
|
||||
|
||||
The `lean-toolchain` files in the Lean 4 repository are set up to use the `lean4-stage0`
|
||||
toolchain for editing files in `src` and the `lean4` toolchain for editing files in `tests`.
|
||||
|
||||
Run the following commands to make `lean4` point at `stage1` and `lean4-stage0` point at `stage0`:
|
||||
You can use `elan toolchain link` to give a specific stage build
|
||||
directory a reference name, then use `elan override set` to associate
|
||||
such a name to the current directory. We usually want to use `stage0`
|
||||
for editing files in `src` and `stage1` for everything else (e.g.
|
||||
tests).
|
||||
```bash
|
||||
# in the Lean rootdir
|
||||
elan toolchain link lean4 build/release/stage1
|
||||
elan toolchain link lean4-stage0 build/release/stage0
|
||||
# make `lean` etc. point to stage1 in the rootdir and subdirs
|
||||
elan override set lean4
|
||||
cd src
|
||||
# make `lean` etc. point to stage0 anywhere inside `src`
|
||||
elan override set lean4-stage0
|
||||
```
|
||||
|
||||
You can also use the `+toolchain` shorthand (e.g. `lean +lean4-debug`) to switch
|
||||
@@ -51,39 +57,3 @@ You might find that debugging through elan, e.g. via `gdb lean`, disables some
|
||||
things like symbol autocompletion because at first only the elan proxy binary
|
||||
is loaded. You can instead pass the explicit path to `bin/lean` in your build
|
||||
folder to gdb, or use `gdb $(elan which lean)`.
|
||||
|
||||
It is also possible to generate releases that others can use,
|
||||
simply by pushing a tag to your fork of the Lean 4 github repository
|
||||
(and waiting about an hour; check the `Actions` tab for completion).
|
||||
If you push `my-tag` to a fork in your github account `my_name`,
|
||||
you can then put `my_name/lean4:my-tag` in your `lean-toolchain` file in a project using `lake`.
|
||||
(You must use a tag name that does not start with a numeral, or contain `_`).
|
||||
|
||||
### VS Code
|
||||
|
||||
There is a `lean.code-workspace` file that correctly sets up VS Code with workspace roots for the stage0/stage1 setup described above as well as with other settings.
|
||||
You should always load it when working on Lean, such as by invoking
|
||||
```
|
||||
code lean.code-workspace
|
||||
```
|
||||
on the command line.
|
||||
|
||||
### `ccache`
|
||||
|
||||
Lean's build process uses [`ccache`](https://ccache.dev/) if it is
|
||||
installed to speed up recompilation of the generated C code. Without
|
||||
`ccache`, you'll likely spend more time than necessary waiting on
|
||||
rebuilds - it's a good idea to make sure it's installed.
|
||||
|
||||
### `prelude`
|
||||
Unlike most Lean projects, all submodules of the `Lean` module begin with the
|
||||
`prelude` keyword. This disables the automated import of `Init`, meaning that
|
||||
developers need to figure out their own subset of `Init` to import. This is done
|
||||
such that changing files in `Init` doesn't force a full rebuild of `Lean`.
|
||||
|
||||
### Testing against Mathlib/Batteries
|
||||
You can test a Lean PR against Mathlib and Batteries by rebasing your PR
|
||||
on to `nightly-with-mathlib` branch. (It is fine to force push after rebasing.)
|
||||
CI will generate a branch of Mathlib and Batteries called `lean-pr-testing-NNNN`
|
||||
that uses the toolchain for your PR, and will report back to the Lean PR with results from Mathlib CI.
|
||||
See https://leanprover-community.github.io/contribute/tags_and_branches.html for more details.
|
||||
|
||||
43
doc/dev/mdbook.md
Normal file
43
doc/dev/mdbook.md
Normal file
@@ -0,0 +1,43 @@
|
||||
## Settings
|
||||
|
||||
We are using the following settings while editing the markdown docs.
|
||||
|
||||
```json
|
||||
{
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"[markdown]": {
|
||||
"rewrap.wrappingColumn": 70
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
This manual is generated by
|
||||
[mdBook](https://github.com/rust-lang/mdBook). We are currently using
|
||||
a [fork](https://github.com/leanprover/mdBook) of it for the following
|
||||
additional features:
|
||||
|
||||
* Add support for hiding lines in other languages
|
||||
[#1339](https://github.com/rust-lang/mdBook/pull/1339)
|
||||
* Replace calling `rustdoc --test` from `mdbook test` with `./test`
|
||||
|
||||
To build this manual, first install the fork via
|
||||
```bash
|
||||
cargo install --git https://github.com/leanprover/mdBook mdbook
|
||||
```
|
||||
Then use e.g. [`mdbook watch`](https://rust-lang.github.io/mdBook/cli/watch.html) in the `doc/` folder:
|
||||
|
||||
```bash
|
||||
cd doc
|
||||
mdbook watch --open # opens the output in `out/` in your default browser
|
||||
```
|
||||
|
||||
Run `mdbook test` to test all `lean` code blocks.
|
||||
|
||||
Using the [Nix setup](make/nix.md), you can instead open a shell with
|
||||
the mdBook fork downloaded from our binary cache:
|
||||
```bash
|
||||
nix develop .#doc
|
||||
```
|
||||
@@ -1,231 +0,0 @@
|
||||
# Releasing a stable version
|
||||
|
||||
This checklist walks you through releasing a stable version.
|
||||
See below for the checklist for release candidates.
|
||||
|
||||
We'll use `v4.6.0` as the intended release version as a running example.
|
||||
|
||||
- Run `script/release_checklist.py v4.6.0` to check the status of the release.
|
||||
This script is idempotent, and should be safe to run at any stage of the release process.
|
||||
Note that as of v4.19.0, this script takes some autonomous actions, which can be prevented via `--dry-run`.
|
||||
- `git checkout releases/v4.6.0`
|
||||
(This branch should already exist, from the release candidates.)
|
||||
- `git pull`
|
||||
- In `src/CMakeLists.txt`, verify you see
|
||||
- `set(LEAN_VERSION_MINOR 6)` (for whichever `6` is appropriate)
|
||||
- `set(LEAN_VERSION_IS_RELEASE 1)`
|
||||
- (all of these should already be in place from the release candidates)
|
||||
- `git tag v4.6.0`
|
||||
- `git push $REMOTE v4.6.0`, where `$REMOTE` is the upstream Lean repository (e.g., `origin`, `upstream`)
|
||||
- Now wait, while CI runs.
|
||||
- You can monitor this at `https://github.com/leanprover/lean4/actions/workflows/ci.yml`,
|
||||
looking for the `v4.6.0` tag.
|
||||
- This step can take up to two hours.
|
||||
- If you are intending to cut the next release candidate on the same day,
|
||||
you may want to start on the release candidate checklist now.
|
||||
- Next we need to prepare the release notes.
|
||||
- If the stable release is identical to the last release candidate (this should usually be the case),
|
||||
you can reuse the release notes that are already in the Lean Language Reference.
|
||||
- If you want to regenerate the release notes,
|
||||
run `script/release_notes.py --since v4.5.0` on the `releases/v4.6.0` branch,
|
||||
and see the section "Writing the release notes" below for more information.
|
||||
- Release notes live in https://github.com/leanprover/reference-manual, in e.g. `Manual/Releases/v4.6.0.lean`.
|
||||
It's best if you update these at the same time as a you update the `lean-toolchain` for the `reference-manual` repository, see below.
|
||||
- Go to https://github.com/leanprover/lean4/releases and verify that the `v4.6.0` release appears.
|
||||
- Verify on Github that "Set as the latest release" is checked.
|
||||
- Next, we will move a curated list of downstream repos to the latest stable release.
|
||||
- In order to have the access rights to push to these repositories and merge PRs,
|
||||
you will need to be a member of the `lean-release-managers` team at both `leanprover-community` and `leanprover`.
|
||||
Contact Kim Morrison (@kim-em) to arrange access.
|
||||
- For each of the repositories listed in `script/release_repos.yml`,
|
||||
- Run `script/release_steps.py v4.6.0 <repo>` (e.g. replacing `<repo>` with `batteries`), which will walk you through the following steps:
|
||||
- Create a new branch off `master`/`main` (as specified in the `branch` field), called `bump_to_v4.6.0`.
|
||||
- Update the contents of `lean-toolchain` to `leanprover/lean4:v4.6.0`.
|
||||
- In the `lakefile.toml` or `lakefile.lean`, if there are dependencies on specific version tags of dependencies, update them to the new tag.
|
||||
If they depend on `main` or `master`, don't change this; you've just updated the dependency, so `lake update` will take care of modifying the manifest.
|
||||
- Run `lake update`
|
||||
- Commit the changes as `chore: bump toolchain to v4.6.0` and push.
|
||||
- Create a PR with title "chore: bump toolchain to v4.6.0".
|
||||
- Merge the PR once CI completes.
|
||||
- Re-running `script/release_checklist.py` will then create the tag `v4.6.0` from `master`/`main` and push it (unless `toolchain-tag: false` in the `release_repos.yml` file)
|
||||
- `script/release_checklist.py` will then merge the tag `v4.6.0` into the `stable` branch and push it (unless `stable-branch: false` in the `release_repos.yml` file).
|
||||
- Special notes on repositories with exceptional requirements:
|
||||
- `doc-gen4` has additional dependencies which we do not update at each toolchain release, although occasionally these break and need to be updated manually.
|
||||
- `verso`:
|
||||
- The `subverso` dependency is unusual in that it needs to be compatible with _every_ Lean release simultaneously.
|
||||
Usually you don't need to do anything.
|
||||
If you think something is wrong here please contact David Thrane Christiansen (@david-christiansen)
|
||||
- Warnings during `lake update` and `lake build` are expected.
|
||||
- `reference-manual`: the release notes generated by `script/release_notes.py` as described above must be included in
|
||||
`Manual/Releases/v4.6.0.lean`, and `import` and `include` statements adding in `Manual/Releases.lean`.
|
||||
- `ProofWidgets4` uses a non-standard sequential version tagging scheme, e.g. `v0.0.29`, which does not refer to the toolchain being used.
|
||||
You will need to identify the next available version number from https://github.com/leanprover-community/ProofWidgets4/releases,
|
||||
and push a new tag after merging the PR to `main`.
|
||||
- `mathlib4`:
|
||||
- The `lakefile.toml` should always refer to dependencies via their `main` or `master` branch,
|
||||
not a toolchain tag
|
||||
(with the exception of `ProofWidgets4`, which *must* use a sequential version tag).
|
||||
- Push the PR branch to the main Mathlib repository rather than a fork, or CI may not work reliably
|
||||
- `repl`:
|
||||
There are two copies of `lean-toolchain`/`lakefile.lean`:
|
||||
in the root, and in `test/Mathlib/`. Edit both, and run `lake update` in both directories.
|
||||
- An awkward situation that sometimes occurs (e.g. with Verso) is that the `master`/`main` branch has already been moved
|
||||
to a nightly toolchain that comes *after* the stable toolchain we are
|
||||
targeting. In this case it is necessary to create a branch `releases/v4.6.0` from the last commit which was on
|
||||
an earlier toolchain, move that branch to the stable toolchain, and create the toolchain tag from that branch.
|
||||
- Run `script/release_checklist.py v4.6.0` one last time to check that everything is in order.
|
||||
- Finally, make an announcement!
|
||||
This should go in https://leanprover.zulipchat.com/#narrow/stream/113486-announce, with topic `v4.6.0`.
|
||||
Please see previous announcements for suggested language.
|
||||
You will want a few bullet points for main topics from the release notes.
|
||||
If there is a blog post, link to that from the zulip announcement.
|
||||
- Make sure that whoever is handling social media knows the release is out.
|
||||
|
||||
## Time estimates:
|
||||
- Initial checks and push the tag: 10 minutes.
|
||||
- Waiting for the release: 120 minutes.
|
||||
- Preparing release notes: 10 minutes.
|
||||
- Bumping toolchains in downstream repositories, up to creating the Mathlib PR: 60 minutes.
|
||||
- Waiting for Mathlib CI and bors: 120 minutes.
|
||||
- Finalizing Mathlib tags and stable branch, and updating REPL: 20 minutes.
|
||||
- Posting announcement and/or blog post: 30 minutes.
|
||||
|
||||
# Creating a release candidate.
|
||||
|
||||
This checklist walks you through creating the first release candidate for a version of Lean.
|
||||
|
||||
For subsequent release candidates, the process is essentially the same, but we start out with the `releases/v4.7.0` branch already created.
|
||||
|
||||
We'll use `v4.7.0-rc1` as the intended release version in this example.
|
||||
|
||||
- Decide which nightly release you want to turn into a release candidate.
|
||||
We will use `nightly-2024-02-29` in this example.
|
||||
- It is essential to choose the nightly that will become the release candidate as early as possible, to avoid confusion.
|
||||
- Throughout this process you can use `script/release_checklist.py v4.7.0-rc1` to track progress.
|
||||
This script will also try to do some steps autonomously. It is idempotent and safe to run at any point.
|
||||
You can prevent it taking any actions using `--dry-run`.
|
||||
- It is essential that Batteries and Mathlib already have reviewed branches compatible with this nightly.
|
||||
- Check that both Batteries and Mathlib's `bump/v4.7.0` branch contain `nightly-2024-02-29`
|
||||
in their `lean-toolchain`.
|
||||
- The steps required to reach that state are beyond the scope of this checklist, but see below!
|
||||
- Create the release branch from this nightly tag:
|
||||
```
|
||||
git remote add nightly https://github.com/leanprover/lean4-nightly.git
|
||||
git fetch nightly tag nightly-2024-02-29
|
||||
git checkout nightly-2024-02-29
|
||||
git checkout -b releases/v4.7.0
|
||||
git push --set-upstream origin releases/v4.7.0
|
||||
```
|
||||
- In `src/CMakeLists.txt`,
|
||||
- verify that you see `set(LEAN_VERSION_MINOR 7)` (for whichever `7` is appropriate); this should already have been updated when the development cycle began.
|
||||
- change the `LEAN_VERSION_IS_RELEASE` line to `set(LEAN_VERSION_IS_RELEASE 1)` (this should be a change; on `master` and nightly releases it is always `0`).
|
||||
- Commit your changes to `src/CMakeLists.txt`, and push.
|
||||
- `git tag v4.7.0-rc1`
|
||||
- `git push origin v4.7.0-rc1`
|
||||
- Now wait, while CI runs.
|
||||
- The CI setup parses the tag to discover the `-rc1` special description, and passes it to `cmake` using a `-D` option. The `-rc1` doesn't need to be placed in the configuration file.
|
||||
- You can monitor this at `https://github.com/leanprover/lean4/actions/workflows/ci.yml`, looking for the `v4.7.0-rc1` tag.
|
||||
- This step can take up to two hours.
|
||||
- Verify that the release appears at https://github.com/leanprover/lean4/releases/, marked as a prerelease (this should have been done automatically by the CI release job).
|
||||
- Next we need to prepare the release notes.
|
||||
- Run `script/release_notes.py --since v4.6.0` on the `releases/v4.7.0` branch,
|
||||
which will report diagnostic messages on `stderr`
|
||||
(including reporting commits that it couldn't associate with a PR, and hence will be omitted)
|
||||
and then a chunk of markdown on `stdout`.
|
||||
See the section "Writing the release notes" below for more information.
|
||||
- Release notes live in https://github.com/leanprover/reference-manual, in e.g. `Manual/Releases/v4.7.0.lean`.
|
||||
It's best if you update these at the same time as a you update the `lean-toolchain` for the `reference-manual` repository, see below.
|
||||
- Next, we will move a curated list of downstream repos to the release candidate.
|
||||
- This assumes that for each repository either:
|
||||
* There is already a *reviewed* branch `bump/v4.7.0` containing the required adaptations.
|
||||
The preparation of this branch is beyond the scope of this document.
|
||||
* The repository does not need any changes to move to the new version.
|
||||
* Note that sometimes there are *unreviewed* but necessary changes on the `nightly-testing` branch of the repository.
|
||||
If so, you will need to merge these into the `bump_to_v4.7.0-rc1` branch manually.
|
||||
- For each of the repositories listed in `script/release_repos.yml`,
|
||||
- Run `script/release_steps.py v4.7.0-rc1 <repo>` (e.g. replacing `<repo>` with `batteries`), which will walk you through the following steps:
|
||||
- Create a new branch off `master`/`main` (as specified in the `branch` field), called `bump_to_v4.7.0-rc1`.
|
||||
- Merge `origin/bump/v4.7.0` if relevant (i.e. `bump-branch: true` appears in `release_repos.yml`).
|
||||
- Otherwise, you *may* need to merge `origin/nightly-testing`.
|
||||
- Note that for `verso` and `reference-manual` development happens on `nightly-testing`, so
|
||||
we will merge that branch into `bump_to_v4.7.0-rc1`, but it is essential in the GitHub interface that we do a rebase merge,
|
||||
in order to preserve the history.
|
||||
- Update the contents of `lean-toolchain` to `leanprover/lean4:v4.7.0-rc1`.
|
||||
- In the `lakefile.toml` or `lakefile.lean`, if there are dependencies on `nightly-testing`, `bump/v4.7.0`, or specific version tags, update them to the new tag.
|
||||
If they depend on `main` or `master`, don't change this; you've just updated the dependency, so `lake update` will take care of modifying the manifest.
|
||||
- Run `lake update`
|
||||
- Run `lake build && if lake check-test; then lake test; fi` to check things are working.
|
||||
- Commit the changes as `chore: bump toolchain to v4.7.0-rc1` and push.
|
||||
- Create a PR with title "chore: bump toolchain to v4.7.0-rc1".
|
||||
- Merge the PR once CI completes. (Recall: for `verso` and `reference-manual` you will need to do a rebase merge.)
|
||||
- Re-running `script/release_checklist.py` will then create the tag `v4.7.0-rc1` from `master`/`main` and push it (unless `toolchain-tag: false` in the `release_repos.yml` file)
|
||||
- We do this for the same list of repositories as for stable releases, see above for notes about special cases.
|
||||
As above, there are dependencies between these, and so the process above is iterative.
|
||||
It greatly helps if you can merge the `bump/v4.7.0` PRs yourself!
|
||||
- It is essential for Mathlib and Batteries CI that you then create the next `bump/v4.8.0` branch
|
||||
for the next development cycle.
|
||||
Set the `lean-toolchain` file on this branch to same `nightly` you used for this release.
|
||||
- Run `script/release_checklist.py v4.7.0-rc1` one last time to check that everything is in order.
|
||||
- Make an announcement!
|
||||
This should go in https://leanprover.zulipchat.com/#narrow/stream/113486-announce, with topic `v4.7.0-rc1`.
|
||||
Please see previous announcements for suggested language.
|
||||
You will want a few bullet points for main topics from the release notes.
|
||||
Please also make sure that whoever is handling social media knows the release is out.
|
||||
- Begin the next development cycle (i.e. for `v4.8.0`) on the Lean repository, by making a PR that:
|
||||
- Uses branch name `dev_cycle_v4.8`.
|
||||
- Updates `src/CMakeLists.txt` to say `set(LEAN_VERSION_MINOR 8)`
|
||||
- Titled "chore: begin development cycle for v4.8.0"
|
||||
|
||||
## Time estimates:
|
||||
Slightly longer than the corresponding steps for a stable release.
|
||||
Similar process, but more things go wrong.
|
||||
In particular, updating the downstream repositories is significantly more work
|
||||
(because we need to merge existing `bump/v4.7.0` branches, not just update a toolchain).
|
||||
|
||||
# Preparing `bump/v4.7.0` branches
|
||||
|
||||
While not part of the release process per se,
|
||||
this is a brief summary of the work that goes into updating Batteries/Aesop/Mathlib to new versions.
|
||||
|
||||
Please read https://leanprover-community.github.io/contribute/tags_and_branches.html
|
||||
|
||||
* Each repo has an unreviewed `nightly-testing` branch that
|
||||
receives commits automatically from `master`, and
|
||||
has its toolchain updated automatically for every nightly.
|
||||
(Note: the aesop branch is not automated, and is updated on an as needed basis.)
|
||||
As a consequence this branch is often broken.
|
||||
A bot posts in the (private!) "Mathlib reviewers" stream on Zulip about the status of these branches.
|
||||
* We fix the breakages by committing directly to `nightly-testing`: there is no PR process.
|
||||
* This can either be done by the person managing this process directly,
|
||||
or by soliciting assistance from authors of files, or generally helpful people on Zulip!
|
||||
* Each repo has a `bump/v4.7.0` which accumulates reviewed changes adapting to new versions.
|
||||
* Once `nightly-testing` is working on a given nightly, say `nightly-2024-02-15`, we will create a PR to `bump/v4.7.0`.
|
||||
* For Mathlib, there is a script in `scripts/create-adaptation-pr.sh` that automates this process.
|
||||
* For Batteries and Aesop it is currently manual.
|
||||
* For all of these repositories, the process is the same:
|
||||
* Make sure `bump/v4.7.0` is up to date with `master` (by merging `master`, no PR necessary)
|
||||
* Create from `bump/v4.7.0` a `bump/nightly-2024-02-15` branch.
|
||||
* In that branch, `git merge nightly-testing` to bring across changes from `nightly-testing`.
|
||||
* Sanity check changes, commit, and make a PR to `bump/v4.7.0` from the `bump/nightly-2024-02-15` branch.
|
||||
* Solicit review, merge the PR into `bump/v4.7.0`.
|
||||
* It is always okay to merge in the following directions:
|
||||
`master` -> `bump/v4.7.0` -> `bump/nightly-2024-02-15` -> `nightly-testing`.
|
||||
Please remember to push any merges you make to intermediate steps!
|
||||
|
||||
# Writing the release notes
|
||||
|
||||
Release notes are automatically generated from the commit history, using `script/release_notes.py`.
|
||||
|
||||
Run this as `script/release_notes.py --since v4.6.0`, where `v4.6.0` is the *previous* release version.
|
||||
This script should be run on the `releases/v4.7.0` branch.
|
||||
This will generate output for all commits since that tag.
|
||||
Note that there is output on both stderr, which should be manually reviewed,
|
||||
and on stdout, which should be manually copied into the `reference-manual` repository, in the file `Manual/Releases/v4.7.0.lean`.
|
||||
|
||||
The output on stderr should mostly be about commits for which the script could not find an associated PR,
|
||||
usually because a PR was rebase-merged because it contained an update to stage0.
|
||||
Some judgement is required here: ignore commits which look minor,
|
||||
but manually add items to the release notes for significant PRs that were rebase-merged.
|
||||
|
||||
There can also be pre-written entries in `./releases_drafts`, which should be all incorporated in the release notes and then deleted from the branch.
|
||||
See `./releases_drafts/README.md` for more information.
|
||||
@@ -5,6 +5,7 @@ After [building Lean](../make/index.md) you can run all the tests using
|
||||
cd build/release
|
||||
make test ARGS=-j4
|
||||
```
|
||||
|
||||
Change the 4 to the maximum number of parallel tests you want to
|
||||
allow. The best choice is the number of CPU cores on your machine as
|
||||
the tests are mostly CPU bound. You can find the number of processors
|
||||
@@ -16,12 +17,6 @@ adding the `-C stageN` argument. The default when run as above is stage 1. The
|
||||
Lean tests will automatically use that stage's corresponding Lean
|
||||
executables
|
||||
|
||||
Running `make test` will not pick up new test files; run
|
||||
```bash
|
||||
cmake build/release/stage1
|
||||
```
|
||||
to update the list of tests.
|
||||
|
||||
You can also use `ctest` directly if you are in the right folder. So
|
||||
to run stage1 tests with a 300 second timeout run this:
|
||||
|
||||
@@ -29,9 +24,6 @@ to run stage1 tests with a 300 second timeout run this:
|
||||
cd build/release/stage1
|
||||
ctest -j 4 --output-on-failure --timeout 300
|
||||
```
|
||||
Useful `ctest` flags are `-R <name of test>` to run a single test, and
|
||||
`--rerun-failed` to run all tests that failed during the last run.
|
||||
You can also pass `ctest` flags via `make test ARGS="--rerun-failed"`.
|
||||
|
||||
To get verbose output from ctest pass the `--verbose` command line
|
||||
option. Test output is normally suppressed and only summary
|
||||
@@ -41,17 +33,17 @@ information is displayed. This option will show all test output.
|
||||
|
||||
All these tests are included by [src/shell/CMakeLists.txt](https://github.com/leanprover/lean4/blob/master/src/shell/CMakeLists.txt):
|
||||
|
||||
- [`tests/lean`](https://github.com/leanprover/lean4/tree/master/tests/lean/): contains tests that come equipped with a
|
||||
.lean.expected.out file. The driver script [`test_single.sh`](https://github.com/leanprover/lean4/tree/master/tests/lean/test_single.sh) runs
|
||||
- `tests/lean`: contains tests that come equipped with a
|
||||
.lean.expected.out file. The driver script `test_single.sh` runs
|
||||
each test and checks the actual output (*.produced.out) with the
|
||||
checked in expected output.
|
||||
|
||||
- [`tests/lean/run`](https://github.com/leanprover/lean4/tree/master/tests/lean/run/): contains tests that are run through the lean
|
||||
- `tests/lean/run`: contains tests that are run through the lean
|
||||
command line one file at a time. These tests only look for error
|
||||
codes and do not check the expected output even though output is
|
||||
produced, it is ignored.
|
||||
|
||||
- [`tests/lean/interactive`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/): are designed to test server requests at a
|
||||
- `tests/lean/interactive`: are designed to test server requests at a
|
||||
given position in the input file. Each .lean file contains comments
|
||||
that indicate how to simulate a client request at that position.
|
||||
using a `--^` point to the line position. Example:
|
||||
@@ -61,7 +53,7 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
|
||||
Bla.
|
||||
--^ textDocument/completion
|
||||
```
|
||||
In this example, the test driver [`test_single.sh`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/test_single.sh) will simulate an
|
||||
In this example, the test driver `test_single.sh` will simulate an
|
||||
auto-completion request at `Bla.`. The expected output is stored in
|
||||
a .lean.expected.out in the json format that is part of the
|
||||
[Language Server
|
||||
@@ -78,33 +70,23 @@ All these tests are included by [src/shell/CMakeLists.txt](https://github.com/le
|
||||
--^ collectDiagnostics
|
||||
```
|
||||
|
||||
- [`tests/lean/server`](https://github.com/leanprover/lean4/tree/master/tests/lean/server/): Tests more of the Lean `--server` protocol.
|
||||
- `tests/lean/server`: Tests more of the Lean `--server` protocol.
|
||||
There are just a few of them, and it uses .log files containing
|
||||
JSON.
|
||||
|
||||
- [`tests/compiler`](https://github.com/leanprover/lean4/tree/master/tests/compiler/): contains tests that will run the Lean compiler and
|
||||
- `tests/compiler`: contains tests that will run the Lean compiler and
|
||||
build an executable that is executed and the output is compared to
|
||||
the .lean.expected.out file. This test also contains a subfolder
|
||||
[`foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) which shows how to extend Lean using C++.
|
||||
`foreign` which shows how to extend Lean using C++.
|
||||
|
||||
- [`tests/lean/trust0`](https://github.com/leanprover/lean4/tree/master/tests/lean/trust0): tests that run Lean in a mode that Lean doesn't
|
||||
- `tests/lean/trust0`: tests that run Lean in a mode that Lean doesn't
|
||||
even trust the .olean files (i.e., trust 0).
|
||||
|
||||
- [`tests/bench`](https://github.com/leanprover/lean4/tree/master/tests/bench/): contains performance tests.
|
||||
- `tests/bench`: contains performance tests.
|
||||
|
||||
- [`tests/plugin`](https://github.com/leanprover/lean4/tree/master/tests/plugin/): tests that compiled Lean code can be loaded into
|
||||
- `tests/plugin`: tests that compiled Lean code can be loaded into
|
||||
`lean` via the `--plugin` command line option.
|
||||
|
||||
## Writing Good Tests
|
||||
|
||||
Every test file should contain:
|
||||
* an initial `/-! -/` module docstring summarizing the test's purpose
|
||||
* a module docstring for each test section that describes what is tested
|
||||
and, if not 100% clear, why that is the desirable behavior
|
||||
|
||||
At the time of writing, most tests do not follow these new guidelines yet.
|
||||
For an example of a conforming test, see [`tests/lean/1971.lean`](https://github.com/leanprover/lean4/tree/master/tests/lean/1971.lean).
|
||||
|
||||
## Fixing Tests
|
||||
|
||||
When the Lean source code or the standard library are modified, some of the
|
||||
@@ -119,7 +101,7 @@ First, we must install [meld](http://meldmerge.org/). On Ubuntu, we can do it by
|
||||
sudo apt-get install meld
|
||||
```
|
||||
|
||||
Now, suppose `bad_class.lean` test is broken. We can see the problem by going to [`tests/lean`](https://github.com/leanprover/lean4/tree/master/tests/lean) directory and
|
||||
Now, suppose `bad_class.lean` test is broken. We can see the problem by going to `test/lean` directory and
|
||||
executing
|
||||
|
||||
```
|
||||
@@ -132,3 +114,8 @@ outputs. `meld` can also be used to repair the problems.
|
||||
|
||||
In Emacs, we can also execute `M-x lean4-diff-test-file` to check/diff the file of the current buffer.
|
||||
To mass-copy all `.produced.out` files to the respective `.expected.out` file, use `tests/lean/copy-produced`.
|
||||
When using the Nix setup, add `--keep-failed` to the `nix build` call and then call
|
||||
```sh
|
||||
tests/lean/copy-produced <build-dir>/source/tests/lean
|
||||
```
|
||||
instead where `<build-dir>` is the path printed out by `nix build`.
|
||||
|
||||
417
doc/do.md
Normal file
417
doc/do.md
Normal file
@@ -0,0 +1,417 @@
|
||||
# The `do` notation
|
||||
|
||||
Lean is a pure functional programming language, but you can write effectful code using the `do` embedded domain specific language (DSL). The following simple program prints two strings "hello" and "world" in the standard output and terminates with exit code 0. Note that the type of the program is `IO UInt32`. You can read this type as the type of values that perform input-output effects and produce a value of type `UInt32`.
|
||||
|
||||
```lean
|
||||
def main : IO UInt32 := do
|
||||
IO.println "hello"
|
||||
IO.println "world"
|
||||
return 0
|
||||
```
|
||||
The type of `IO.println` is `String → IO Unit`. That is, it is a function from `String` to `IO Unit` which indicates it may perform input-output effects and produce a value of type `Unit`. We often say that functions that may perform effects are *methods*.
|
||||
We also say a method application, such as `IO.println "hello"` is an *action*.
|
||||
Note that the examples above also demonstrates that braceless `do` blocks are whitespace sensitive.
|
||||
If you like `;`s and curly braces, you can write the example above as
|
||||
```lean
|
||||
def main : IO UInt32 := do {
|
||||
IO.println "hello";
|
||||
IO.println "world";
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
Semicolons can be used even when curly braces are not used. They are particularly useful when you want to "pack" more than one action in a single line.
|
||||
```lean
|
||||
def main : IO UInt32 := do
|
||||
IO.println "hello"; IO.println "world"
|
||||
return 0
|
||||
```
|
||||
Whitespace sensitivity in programming languages is a controversial topic
|
||||
among programmers. You should use your own style. We, the Lean developers, **love** the
|
||||
braceless and semicolon-free style.
|
||||
We believe it is clean and beautiful.
|
||||
|
||||
The `do` DSL expands into the core Lean language. Let's inspect the different components using the commands `#print` and `#check`.
|
||||
|
||||
```lean
|
||||
# def main : IO UInt32 := do
|
||||
# IO.println "hello"
|
||||
# IO.println "world"
|
||||
# return 0
|
||||
|
||||
#check IO.println "hello"
|
||||
-- IO Unit
|
||||
#print main
|
||||
-- Output contains the infix operator `>>=` and `pure`
|
||||
-- The following `set_option` disables notation such as `>>=` in the output
|
||||
set_option pp.notation false in
|
||||
#print main
|
||||
-- Output contains `bind` and `pure`
|
||||
#print bind
|
||||
-- bind : {m : Type u → Type v} → [self : Bind m] → {α β : Type u} →
|
||||
-- m α → (α → m β) → m β
|
||||
#print pure
|
||||
-- pure : {m : Type u → Type v} → [self : Pure m] → {α : Type u} →
|
||||
-- α → m α
|
||||
|
||||
-- IO implements the type classes `Bind` and `Pure`.
|
||||
#check (inferInstance : Bind IO)
|
||||
#check (inferInstance : Pure IO)
|
||||
```
|
||||
The types of `bind` and `pure` may look daunting at first sight.
|
||||
They both have many implicit arguments. Let's focus first on the explicit arguments.
|
||||
`bind` has two explicit arguments `m α` and `α → m β`. The first one should
|
||||
be viewed as an action with effects `m` and producing a value of type `α`.
|
||||
The second is a function that takes a value of type `α` and produces an action
|
||||
with effects `m` and a value of type `β`. The result is `m β`. The method `bind` is composing
|
||||
these two actions. We often say `bind` is an abstract semicolon. The method `pure` converts
|
||||
a value `α` into an action that produces an action `m α`.
|
||||
|
||||
Here is the same function being defined using `bind` and `pure` without the `do` DSL.
|
||||
```lean
|
||||
def main : IO UInt32 :=
|
||||
bind (IO.println "hello") fun _ =>
|
||||
bind (IO.println "world") fun _ =>
|
||||
pure 0
|
||||
```
|
||||
|
||||
The notations `let x <- action1; action2` and `let x ← action1; action2` are just syntax sugar for `bind action1 fun x => action2`.
|
||||
Here is a small example using it.
|
||||
```lean
|
||||
def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
IO.println s!"value: {x}"
|
||||
return x > 0
|
||||
|
||||
def f (x : Nat) : IO Unit := do
|
||||
let c <- isGreaterThan0 x
|
||||
if c then
|
||||
IO.println s!"{x} is greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
#eval f 10
|
||||
-- value: 10
|
||||
-- 10 is greater than 0
|
||||
```
|
||||
|
||||
|
||||
## Nested actions
|
||||
|
||||
Note that we cannot write `if isGreaterThan0 x then ... else ...` because the condition in a `if-then-else` is a **pure** value without effects, but `isGreaterThan0 x` has type `IO Bool`. You can use the nested action notation to avoid this annoyance. Here is an equivalent definition for `f` using a nested action.
|
||||
```lean
|
||||
# def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
# IO.println s!"x: {x}"
|
||||
# return x > 0
|
||||
|
||||
def f (x : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x) then
|
||||
IO.println s!"{x} is greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
#print f
|
||||
```
|
||||
Lean "lifts" the nested actions and introduces the `bind` for us.
|
||||
Here is an example with two nested actions. Note that both actions are executed
|
||||
even if `x = 0`.
|
||||
```lean
|
||||
# def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
# IO.println s!"x: {x}"
|
||||
# return x > 0
|
||||
|
||||
def f (x y : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x) && (<- isGreaterThan0 y) then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
#eval f 0 10
|
||||
-- value: 0
|
||||
-- value: 10
|
||||
|
||||
-- The function `f` above is equivalent to
|
||||
def g (x y : Nat) : IO Unit := do
|
||||
let c1 <- isGreaterThan0 x
|
||||
let c2 <- isGreaterThan0 y
|
||||
if c1 && c2 then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
theorem fgEqual : f = g :=
|
||||
rfl -- proof by reflexivity
|
||||
```
|
||||
Here are two ways to achieve the short-circuit semantics in the example above
|
||||
```lean
|
||||
# def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
# IO.println s!"x: {x}"
|
||||
# return x > 0
|
||||
|
||||
def f1 (x y : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x <&&> isGreaterThan0 y) then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
-- `<&&>` is the effectful version of `&&`
|
||||
-- Given `x y : IO Bool`, `x <&&> y` : m Bool`
|
||||
-- It only executes `y` if `x` returns `true`.
|
||||
|
||||
#eval f1 0 10
|
||||
-- value: 0
|
||||
#eval f1 1 10
|
||||
-- value: 1
|
||||
-- value: 10
|
||||
-- 1 and 10 are greater than 0
|
||||
|
||||
def f2 (x y : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x) then
|
||||
if (<- isGreaterThan0 y) then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
else
|
||||
pure ()
|
||||
else
|
||||
pure ()
|
||||
```
|
||||
|
||||
## `if-then` notation
|
||||
|
||||
In the `do` DSL, we can write `if c then action` as a shorthand for `if c then action else pure ()`. Here is the method `f2` using this shorthand.
|
||||
```lean
|
||||
# def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
# IO.println s!"x: {x}"
|
||||
# return x > 0
|
||||
|
||||
def f2 (x y : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x) then
|
||||
if (<- isGreaterThan0 y) then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
```
|
||||
|
||||
## Reassignments
|
||||
|
||||
When writing effectful code, it is natural to think imperatively.
|
||||
For example, suppose we want to create an empty array `xs`,
|
||||
add `0` if some condition holds, add `1` if another condition holds,
|
||||
and then print it. In the following example, we use variable
|
||||
"shadowing" to simulate this kind of "update".
|
||||
|
||||
```lean
|
||||
def f (b1 b2 : Bool) : IO Unit := do
|
||||
let xs := #[]
|
||||
let xs := if b1 then xs.push 0 else xs
|
||||
let xs := if b2 then xs.push 1 else xs
|
||||
IO.println xs
|
||||
|
||||
#eval f true true
|
||||
-- #[0, 1]
|
||||
#eval f false true
|
||||
-- #[1]
|
||||
#eval f true false
|
||||
-- #[0]
|
||||
#eval f false false
|
||||
-- #[]
|
||||
```
|
||||
|
||||
We can use tuples to simulate updates on multiple variables.
|
||||
|
||||
```lean
|
||||
def f (b1 b2 : Bool) : IO Unit := do
|
||||
let xs := #[]
|
||||
let ys := #[]
|
||||
let (xs, ys) := if b1 then (xs.push 0, ys) else (xs, ys.push 0)
|
||||
let (xs, ys) := if b2 then (xs.push 1, ys) else (xs, ys.push 1)
|
||||
IO.println s!"xs: {xs}, ys: {ys}"
|
||||
|
||||
#eval f true false
|
||||
-- xs: #[0], ys: #[1]
|
||||
```
|
||||
|
||||
We can also simulate the control-flow above using *join-points*.
|
||||
A join-point is a `let` that is always tail called and fully applied.
|
||||
The Lean compiler implements them using `goto`s.
|
||||
Here is the same example using join-points.
|
||||
|
||||
```lean
|
||||
def f (b1 b2 : Bool) : IO Unit := do
|
||||
let jp1 xs ys := IO.println s!"xs: {xs}, ys: {ys}"
|
||||
let jp2 xs ys := if b2 then jp1 (xs.push 1) ys else jp1 xs (ys.push 1)
|
||||
let xs := #[]
|
||||
let ys := #[]
|
||||
if b1 then jp2 (xs.push 0) ys else jp2 xs (ys.push 0)
|
||||
|
||||
#eval f true false
|
||||
-- xs: #[0], ys: #[1]
|
||||
```
|
||||
|
||||
You can capture complex control-flow using join-points.
|
||||
The `do` DSL offers the variable reassignment feature to make this kind of code more comfortable to write. In the following example, the `mut` modifier at `let mut xs := #[]` indicates that variable `xs` can be reassigned. The example contains two reassignments `xs := xs.push 0` and `xs := xs.push 1`. The reassignments are compiled using join-points. There is no hidden state being updated.
|
||||
|
||||
```lean
|
||||
def f (b1 b2 : Bool) : IO Unit := do
|
||||
let mut xs := #[]
|
||||
if b1 then xs := xs.push 0
|
||||
if b2 then xs := xs.push 1
|
||||
IO.println xs
|
||||
|
||||
#eval f true true
|
||||
-- #[0, 1]
|
||||
```
|
||||
The notation `x <- action` reassigns `x` with the value produced by the action. It is equivalent to `x := (<- action)`
|
||||
|
||||
## Iteration
|
||||
|
||||
The `do` DSL provides a unified notation for iterating over datastructures. Here are a few examples.
|
||||
|
||||
```lean
|
||||
def sum (xs : Array Nat) : IO Nat := do
|
||||
let mut s := 0
|
||||
for x in xs do
|
||||
IO.println s!"x: {x}"
|
||||
s := s + x
|
||||
return s
|
||||
|
||||
#eval sum #[1, 2, 3]
|
||||
-- x: 1
|
||||
-- x: 2
|
||||
-- x: 3
|
||||
-- 6
|
||||
|
||||
-- We can write pure code using the `Id.run <| do` DSL too.
|
||||
def sum' (xs : Array Nat) : Nat := Id.run <| do
|
||||
let mut s := 0
|
||||
for x in xs do
|
||||
s := s + x
|
||||
return s
|
||||
|
||||
#eval sum' #[1, 2, 3]
|
||||
-- 6
|
||||
|
||||
def sumEven (xs : Array Nat) : IO Nat := do
|
||||
let mut s := 0
|
||||
for x in xs do
|
||||
if x % 2 == 0 then
|
||||
IO.println s!"x: {x}"
|
||||
s := s + x
|
||||
return s
|
||||
|
||||
#eval sumEven #[1, 2, 3, 6]
|
||||
-- x: 2
|
||||
-- x: 6
|
||||
-- 8
|
||||
|
||||
def splitEvenOdd (xs : List Nat) : IO Unit := do
|
||||
let mut evens := #[]
|
||||
let mut odds := #[]
|
||||
for x in xs do
|
||||
if x % 2 == 0 then
|
||||
evens := evens.push x
|
||||
else
|
||||
odds := odds.push x
|
||||
IO.println s!"evens: {evens}, odds: {odds}"
|
||||
|
||||
#eval splitEvenOdd [1, 2, 3, 4]
|
||||
-- evens: #[2, 4], odds: #[1, 3]
|
||||
|
||||
def findNatLessThan (x : Nat) (p : Nat → Bool) : IO Nat := do
|
||||
-- [:x] is notation for the range [0, x)
|
||||
for i in [:x] do
|
||||
if p i then
|
||||
return i -- `return` from the `do` block
|
||||
throw (IO.userError "value not found")
|
||||
|
||||
#eval findNatLessThan 10 (fun x => x > 5 && x % 4 == 0)
|
||||
-- 8
|
||||
|
||||
def sumOddUpTo (xs : List Nat) (threshold : Nat) : IO Nat := do
|
||||
let mut s := 0
|
||||
for x in xs do
|
||||
if x % 2 == 0 then
|
||||
continue -- it behaves like the `continue` statement in imperative languages
|
||||
IO.println s!"x: {x}"
|
||||
s := s + x
|
||||
if s > threshold then
|
||||
break -- it behaves like the `break` statement in imperative languages
|
||||
IO.println s!"result: {s}"
|
||||
return s
|
||||
|
||||
#eval sumOddUpTo [2, 3, 4, 11, 20, 31, 41, 51, 107] 40
|
||||
-- x: 3
|
||||
-- x: 11
|
||||
-- x: 31
|
||||
-- result: 45
|
||||
-- 45
|
||||
```
|
||||
|
||||
TODO: describe `forIn`
|
||||
|
||||
## Try-catch
|
||||
|
||||
TODO
|
||||
|
||||
## Returning early from a failed match
|
||||
|
||||
Inside a `do` block, the pattern `let _ ← <success> | <fail>` will continue with the rest of the block if the match on the left hand side succeeds, but will execute the right hand side and exit the block on failure:
|
||||
|
||||
```lean
|
||||
def showUserInfo (getUsername getFavoriteColor : IO (Option String)) : IO Unit := do
|
||||
let some n ← getUsername | IO.println "no username!"
|
||||
IO.println s!"username: {n}"
|
||||
let some c ← getFavoriteColor | IO.println "user didn't provide a favorite color!"
|
||||
IO.println s!"favorite color: {c}"
|
||||
|
||||
-- username: JohnDoe
|
||||
-- favorite color: red
|
||||
#eval showUserInfo (pure <| some "JohnDoe") (pure <| some "red")
|
||||
|
||||
-- no username
|
||||
#eval showUserInfo (pure none) (pure <| some "purple")
|
||||
|
||||
-- username: JaneDoe
|
||||
-- user didn't provide a favorite color
|
||||
#eval showUserInfo (pure <| some "JaneDoe") (pure none)
|
||||
```
|
||||
|
||||
## If-let
|
||||
|
||||
Inside a `do` block, users can employ the `if let` pattern to destructure actions:
|
||||
|
||||
```lean
|
||||
def tryIncrement (getInput : IO (Option Nat)) : IO (Except String Nat) := do
|
||||
if let some n ← getInput
|
||||
then return Except.ok n.succ
|
||||
else return Except.error "argument was `none`"
|
||||
|
||||
-- Except.ok 2
|
||||
#eval tryIncrement (pure <| some 1)
|
||||
|
||||
-- Except.error "argument was `none`"
|
||||
#eval tryIncrement (pure <| none)
|
||||
```
|
||||
|
||||
## Pattern matching
|
||||
|
||||
TODO
|
||||
|
||||
## Monads
|
||||
|
||||
TODO
|
||||
|
||||
## ReaderT
|
||||
|
||||
TODO
|
||||
|
||||
## StateT
|
||||
|
||||
TODO
|
||||
|
||||
## StateRefT
|
||||
|
||||
TODO
|
||||
|
||||
## ExceptT
|
||||
|
||||
TODO
|
||||
|
||||
## MonadLift and automatic lifting
|
||||
|
||||
TODO
|
||||
8
doc/elaborators.md
Normal file
8
doc/elaborators.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## Elaborators
|
||||
|
||||
TODO. See [Lean Together 2021: Metaprogramming in Lean
|
||||
4](https://youtu.be/hxQ1vvhYN_U) for an overview as well [the
|
||||
continuation](https://youtu.be/vy4JWIiiXSY) about tactic programming.
|
||||
For more information on antiquotations, see also §4.1 of [Beyond
|
||||
Notations: Hygienic Macro Expansion for Theorem Proving
|
||||
Languages](https://arxiv.org/pdf/2001.10490.pdf#page=11).
|
||||
190
doc/enum.md
Normal file
190
doc/enum.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# Enumerated Types
|
||||
|
||||
The simplest kind of inductive type is simply a type with a finite, enumerated list of elements.
|
||||
The following command declares the enumerated type `Weekday`.
|
||||
```lean
|
||||
inductive Weekday where
|
||||
| sunday : Weekday
|
||||
| monday : Weekday
|
||||
| tuesday : Weekday
|
||||
| wednesday : Weekday
|
||||
| thursday : Weekday
|
||||
| friday : Weekday
|
||||
| saturday : Weekday
|
||||
```
|
||||
|
||||
The `Weekday` type has 7 constructors/elements. The constructors live in the `Weekday` namespace
|
||||
Think of `sunday`, `monday`, …, `saturday` as being distinct elements of `Weekday`,
|
||||
with no other distinguishing properties.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
#check Weekday.sunday -- Weekday
|
||||
#check Weekday.monday -- Weekday
|
||||
```
|
||||
|
||||
You can define functions by pattern matching.
|
||||
The following function converts a `Weekday` into a natural number.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
def natOfWeekday (d : Weekday) : Nat :=
|
||||
match d with
|
||||
| Weekday.sunday => 1
|
||||
| Weekday.monday => 2
|
||||
| Weekday.tuesday => 3
|
||||
| Weekday.wednesday => 4
|
||||
| Weekday.thursday => 5
|
||||
| Weekday.friday => 6
|
||||
| Weekday.saturday => 7
|
||||
|
||||
#eval natOfWeekday Weekday.tuesday -- 3
|
||||
```
|
||||
|
||||
It is often useful to group definitions related to a type in a namespace with the same name.
|
||||
For example, we can put the function above into the ``Weekday`` namespace.
|
||||
We are then allowed to use the shorter name when we open the namespace.
|
||||
|
||||
In the following example, we define functions from ``Weekday`` to ``Weekday`` in the namespace `Weekday`.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
namespace Weekday
|
||||
|
||||
def next (d : Weekday) : Weekday :=
|
||||
match d with
|
||||
| sunday => monday
|
||||
| monday => tuesday
|
||||
| tuesday => wednesday
|
||||
| wednesday => thursday
|
||||
| thursday => friday
|
||||
| friday => saturday
|
||||
| saturday => sunday
|
||||
|
||||
end Weekday
|
||||
```
|
||||
It is so common to start a definition with a `match` in Lean, that Lean provides a syntax sugar for it.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
# namespace Weekday
|
||||
def previous : Weekday -> Weekday
|
||||
| sunday => saturday
|
||||
| monday => sunday
|
||||
| tuesday => monday
|
||||
| wednesday => tuesday
|
||||
| thursday => wednesday
|
||||
| friday => thursday
|
||||
| saturday => friday
|
||||
# end Weekday
|
||||
```
|
||||
We can use the command `#eval` to test our definitions.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
# namespace Weekday
|
||||
# def next (d : Weekday) : Weekday :=
|
||||
# match d with
|
||||
# | sunday => monday
|
||||
# | monday => tuesday
|
||||
# | tuesday => wednesday
|
||||
# | wednesday => thursday
|
||||
# | thursday => friday
|
||||
# | friday => saturday
|
||||
# | saturday => sunday
|
||||
# def previous : Weekday -> Weekday
|
||||
# | sunday => saturday
|
||||
# | monday => sunday
|
||||
# | tuesday => monday
|
||||
# | wednesday => tuesday
|
||||
# | thursday => wednesday
|
||||
# | friday => thursday
|
||||
# | saturday => friday
|
||||
def toString : Weekday -> String
|
||||
| sunday => "Sunday"
|
||||
| monday => "Monday"
|
||||
| tuesday => "Tuesday"
|
||||
| wednesday => "Wednesday"
|
||||
| thursday => "Thursday"
|
||||
| friday => "Friday"
|
||||
| saturday => "Saturday"
|
||||
|
||||
#eval toString (next sunday) -- "Monday"
|
||||
#eval toString (next tuesday) -- "Wednesday"
|
||||
#eval toString (previous wednesday) -- "Tuesday"
|
||||
#eval toString (next (previous sunday)) -- "Sunday"
|
||||
#eval toString (next (previous monday)) -- "Monday"
|
||||
-- ..
|
||||
# end Weekday
|
||||
```
|
||||
We can now prove the general theorem that ``next (previous d) = d`` for any weekday ``d``.
|
||||
The idea is to perform a proof by cases using `match`, and rely on the fact for each constructor both
|
||||
sides of the equality reduce to the same term.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
# namespace Weekday
|
||||
# def next (d : Weekday) : Weekday :=
|
||||
# match d with
|
||||
# | sunday => monday
|
||||
# | monday => tuesday
|
||||
# | tuesday => wednesday
|
||||
# | wednesday => thursday
|
||||
# | thursday => friday
|
||||
# | friday => saturday
|
||||
# | saturday => sunday
|
||||
# def previous : Weekday -> Weekday
|
||||
# | sunday => saturday
|
||||
# | monday => sunday
|
||||
# | tuesday => monday
|
||||
# | wednesday => tuesday
|
||||
# | thursday => wednesday
|
||||
# | friday => thursday
|
||||
# | saturday => friday
|
||||
theorem nextOfPrevious (d : Weekday) : next (previous d) = d :=
|
||||
match d with
|
||||
| sunday => rfl
|
||||
| monday => rfl
|
||||
| tuesday => rfl
|
||||
| wednesday => rfl
|
||||
| thursday => rfl
|
||||
| friday => rfl
|
||||
| saturday => rfl
|
||||
# end Weekday
|
||||
```
|
||||
9
doc/examples.md
Normal file
9
doc/examples.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Examples
|
||||
========
|
||||
|
||||
- [Palindromes](examples/palindromes.lean.md)
|
||||
- [Binary Search Trees](examples/bintree.lean.md)
|
||||
- [A Certified Type Checker](examples/tc.lean.md)
|
||||
- [The Well-Typed Interpreter](examples/interp.lean.md)
|
||||
- [Dependent de Bruijn Indices](examples/deBruijn.lean.md)
|
||||
- [Parametric Higher-Order Abstract Syntax](examples/phoas.lean.md)
|
||||
@@ -1,22 +0,0 @@
|
||||
/- "Hello world" -/
|
||||
|
||||
#eval "hello" ++ " " ++ "world"
|
||||
-- "hello world"
|
||||
|
||||
#check true
|
||||
-- Bool
|
||||
|
||||
def x := 10
|
||||
|
||||
#eval x + 2
|
||||
-- 12
|
||||
|
||||
def double (x : Int) := 2*x
|
||||
|
||||
#eval double 3
|
||||
-- 6
|
||||
#check double
|
||||
-- Int → Int
|
||||
example : double 4 = 8 := rfl
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/- Dependent pattern matching -/
|
||||
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
|
||||
infix:67 "::" => Vector.cons
|
||||
|
||||
def Vector.zip : Vector α n → Vector β n → Vector (α × β) n
|
||||
| nil, nil => nil
|
||||
| a::as, b::bs => (a, b) :: zip as bs
|
||||
|
||||
#print Vector.zip
|
||||
/-
|
||||
def Vector.zip.{u_1, u_2} : {α : Type u_1} → {n : Nat} → {β : Type u_2} → Vector α n → Vector β n → Vector (α × β) n :=
|
||||
fun {α} {n} {β} x x_1 =>
|
||||
Vector.brecOn (motive := fun {n} x => {β : Type u_2} → Vector β n → Vector (α × β) n) x
|
||||
...
|
||||
-/
|
||||
@@ -1,22 +0,0 @@
|
||||
/- Structures -/
|
||||
|
||||
structure Point where
|
||||
x : Int := 0
|
||||
y : Int := 0
|
||||
deriving Repr
|
||||
|
||||
#eval Point.x (Point.mk 10 20)
|
||||
-- 10
|
||||
|
||||
#eval { x := 10, y := 20 : Point }
|
||||
|
||||
def p : Point := { y := 20 }
|
||||
|
||||
#eval p.x
|
||||
#eval p.y
|
||||
#eval { p with x := 5 }
|
||||
-- { x := 5, y := 20 }
|
||||
|
||||
structure Point3D extends Point where
|
||||
z : Int
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/- Type classes -/
|
||||
namespace Example
|
||||
|
||||
class ToString (α : Type u) where
|
||||
toString : α → String
|
||||
|
||||
#check @ToString.toString
|
||||
-- {α : Type u_1} → [self : ToString α] → α → String
|
||||
|
||||
instance : ToString String where
|
||||
toString s := s
|
||||
|
||||
instance : ToString Bool where
|
||||
toString b := if b then "true" else "false"
|
||||
|
||||
#eval ToString.toString "hello"
|
||||
export ToString (toString)
|
||||
#eval toString true
|
||||
-- "true"
|
||||
-- #eval toString (true, "hello") -- Error
|
||||
|
||||
instance [ToString α] [ToString β] : ToString (α × β) where
|
||||
toString p := "(" ++ toString p.1 ++ ", " ++ toString p.2 ++ ")"
|
||||
|
||||
#eval toString (true, "hello")
|
||||
-- "(true, hello)"
|
||||
|
||||
end Example
|
||||
@@ -1,44 +0,0 @@
|
||||
/- Type classes are heavily used in Lean -/
|
||||
namespace Example
|
||||
|
||||
class Mul (α : Type u) where
|
||||
mul : α → α → α
|
||||
|
||||
infixl:70 " * " => Mul.mul
|
||||
|
||||
def double [Mul α] (a : α) := a * a
|
||||
|
||||
class Semigroup (α : Type u) extends Mul α where
|
||||
mul_assoc : ∀ a b c : α, (a * b) * c = a * (b * c)
|
||||
|
||||
instance : Semigroup Nat where
|
||||
mul := Nat.mul
|
||||
mul_assoc := Nat.mul_assoc
|
||||
|
||||
#eval double 5
|
||||
|
||||
class Functor (f : Type u → Type v) : Type (max (u+1) v) where
|
||||
map : (α → β) → f α → f β
|
||||
|
||||
infixr:100 " <$> " => Functor.map
|
||||
|
||||
class LawfulFunctor (f : Type u → Type v) [Functor f] : Prop where
|
||||
id_map (x : f α) : id <$> x = x
|
||||
comp_map (g : α → β) (h : β → γ) (x : f α) :(h ∘ g) <$> x = h <$> g <$> x
|
||||
|
||||
end Example
|
||||
|
||||
/-
|
||||
`Deriving instances automatically`
|
||||
|
||||
We have seen `deriving Repr` in a few examples.
|
||||
It is an instance generator.
|
||||
Lean comes equipped with generators for the following classes.
|
||||
`Repr`, `Inhabited`, `BEq`, `DecidableEq`,
|
||||
`Hashable`, `Ord`, `FromToJson`, `SizeOf`
|
||||
-/
|
||||
|
||||
inductive Tree (α : Type u) where
|
||||
| leaf (val : α)
|
||||
| node (left right : Tree α)
|
||||
deriving DecidableEq, Ord, Inhabited, Repr
|
||||
@@ -1,31 +0,0 @@
|
||||
/- Tactics -/
|
||||
|
||||
example : p → q → p ∧ q ∧ p := by
|
||||
intro hp hq
|
||||
apply And.intro
|
||||
exact hp
|
||||
apply And.intro
|
||||
exact hq
|
||||
exact hp
|
||||
|
||||
example : p → q → p ∧ q ∧ p := by
|
||||
intro hp hq; apply And.intro hp; exact And.intro hq hp
|
||||
|
||||
/- Structuring proofs -/
|
||||
|
||||
example : p → q → p ∧ q ∧ p := by
|
||||
intro hp hq
|
||||
apply And.intro
|
||||
case left => exact hp
|
||||
case right =>
|
||||
apply And.intro
|
||||
case left => exact hq
|
||||
case right => exact hp
|
||||
|
||||
example : p → q → p ∧ q ∧ p := by
|
||||
intro hp hq
|
||||
apply And.intro
|
||||
. exact hp
|
||||
. apply And.intro
|
||||
. exact hq
|
||||
. exact hp
|
||||
@@ -1,19 +0,0 @@
|
||||
/- intro tactic variants -/
|
||||
|
||||
example (p q : α → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
|
||||
intro h
|
||||
match h with
|
||||
| Exists.intro w (And.intro hp hq) => exact Exists.intro w (And.intro hq hp)
|
||||
|
||||
example (p q : α → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
|
||||
intro (Exists.intro _ (And.intro hp hq))
|
||||
exact Exists.intro _ (And.intro hq hp)
|
||||
|
||||
example (p q : α → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
|
||||
intro ⟨_, hp, hq⟩
|
||||
exact ⟨_, hq, hp⟩
|
||||
|
||||
example (α : Type) (p q : α → Prop) : (∃ x, p x ∨ q x) → ∃ x, q x ∨ p x := by
|
||||
intro
|
||||
| ⟨_, .inl h⟩ => exact ⟨_, .inr h⟩
|
||||
| ⟨_, .inr h⟩ => exact ⟨_, .inl h⟩
|
||||
@@ -1,12 +0,0 @@
|
||||
/- Inaccessible names -/
|
||||
|
||||
example : ∀ x y : Nat, x = y → y = x := by
|
||||
intros
|
||||
apply Eq.symm
|
||||
assumption
|
||||
|
||||
example : ∀ x y : Nat, x = y → y = x := by
|
||||
intros
|
||||
apply Eq.symm
|
||||
rename_i a b hab
|
||||
exact hab
|
||||
@@ -1,19 +0,0 @@
|
||||
/- More tactics -/
|
||||
|
||||
example (p q : Nat → Prop) : (∃ x, p x ∧ q x) → ∃ x, q x ∧ p x := by
|
||||
intro h
|
||||
cases h with
|
||||
| intro x hpq =>
|
||||
cases hpq with
|
||||
| intro hp hq =>
|
||||
exists x
|
||||
|
||||
example : p ∧ q → q ∧ p := by
|
||||
intro p
|
||||
cases p
|
||||
constructor <;> assumption
|
||||
|
||||
example : p ∧ ¬ p → q := by
|
||||
intro h
|
||||
cases h
|
||||
contradiction
|
||||
@@ -1,20 +0,0 @@
|
||||
/- Structuring proofs (cont.) -/
|
||||
|
||||
example : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) := by
|
||||
intro h
|
||||
have hp : p := h.left
|
||||
have hqr : q ∨ r := h.right
|
||||
show (p ∧ q) ∨ (p ∧ r)
|
||||
cases hqr with
|
||||
| inl hq => exact Or.inl ⟨hp, hq⟩
|
||||
| inr hr => exact Or.inr ⟨hp, hr⟩
|
||||
|
||||
example : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) := by
|
||||
intro ⟨hp, hqr⟩
|
||||
cases hqr with
|
||||
| inl hq =>
|
||||
have := And.intro hp hq
|
||||
apply Or.inl; exact this
|
||||
| inr hr =>
|
||||
have := And.intro hp hr
|
||||
apply Or.inr; exact this
|
||||
@@ -1,10 +0,0 @@
|
||||
/- Tactic combinators -/
|
||||
|
||||
example : p → q → r → p ∧ ((p ∧ q) ∧ r) ∧ (q ∧ r ∧ p) := by
|
||||
intros
|
||||
repeat (any_goals constructor)
|
||||
all_goals assumption
|
||||
|
||||
example : p → q → r → p ∧ ((p ∧ q) ∧ r) ∧ (q ∧ r ∧ p) := by
|
||||
intros
|
||||
repeat (any_goals (first | assumption | constructor))
|
||||
@@ -1,14 +0,0 @@
|
||||
/- First-class functions -/
|
||||
|
||||
def twice (f : Nat → Nat) (a : Nat) :=
|
||||
f (f a)
|
||||
|
||||
#check twice
|
||||
-- (Nat → Nat) → Nat → Nat
|
||||
|
||||
#eval twice (fun x => x + 2) 10
|
||||
|
||||
theorem twice_add_2 (a : Nat) : twice (fun x => x + 2) a = a + 4 := rfl
|
||||
|
||||
-- `(· + 2)` is syntax sugar for `(fun x => x + 2)`.
|
||||
#eval twice (· + 2) 10
|
||||
@@ -1,22 +0,0 @@
|
||||
/- Rewriting -/
|
||||
|
||||
example (f : Nat → Nat) (k : Nat) (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 := by
|
||||
rw [h₂] -- replace k with 0
|
||||
rw [h₁] -- replace f 0 with 0
|
||||
|
||||
example (f : Nat → Nat) (k : Nat) (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 := by
|
||||
rw [h₂, h₁]
|
||||
|
||||
example (f : Nat → Nat) (a b : Nat) (h₁ : a = b) (h₂ : f a = 0) : f b = 0 := by
|
||||
rw [← h₁, h₂]
|
||||
|
||||
example (f : Nat → Nat) (a : Nat) (h : 0 + a = 0) : f a = f 0 := by
|
||||
rw [Nat.zero_add] at h
|
||||
rw [h]
|
||||
|
||||
def Tuple (α : Type) (n : Nat) :=
|
||||
{ as : List α // as.length = n }
|
||||
|
||||
example (n : Nat) (h : n = 0) (t : Tuple α n) : Tuple α 0 := by
|
||||
rw [h] at t
|
||||
exact t
|
||||
@@ -1,21 +0,0 @@
|
||||
/- Simplifier -/
|
||||
|
||||
example (p : Nat → Prop) : (x + 0) * (0 + y * 1 + z * 0) = x * y := by
|
||||
simp
|
||||
|
||||
example (p : Nat → Prop) (h : p (x * y)) : p ((x + 0) * (0 + y * 1 + z * 0)) := by
|
||||
simp; assumption
|
||||
|
||||
example (p : Nat → Prop) (h : p ((x + 0) * (0 + y * 1 + z * 0))) : p (x * y) := by
|
||||
simp at h; assumption
|
||||
|
||||
def f (m n : Nat) : Nat :=
|
||||
m + n + m
|
||||
|
||||
example (h : n = 1) (h' : 0 = m) : (f m n) = n := by
|
||||
simp [h, ←h', f]
|
||||
|
||||
example (p : Nat → Prop) (h₁ : x + 0 = x') (h₂ : y + 0 = y')
|
||||
: x + y + 0 = x' + y' := by
|
||||
simp at *
|
||||
simp [*]
|
||||
@@ -1,13 +0,0 @@
|
||||
/- Simplifier -/
|
||||
|
||||
def mk_symm (xs : List α) :=
|
||||
xs ++ xs.reverse
|
||||
|
||||
@[simp] theorem reverse_mk_symm : (mk_symm xs).reverse = mk_symm xs := by
|
||||
simp [mk_symm]
|
||||
|
||||
theorem tst : (xs ++ mk_symm ys).reverse = mk_symm ys ++ xs.reverse := by
|
||||
simp
|
||||
|
||||
#print tst
|
||||
-- Lean reverse_mk_symm, and List.reverse_append
|
||||
@@ -1,26 +0,0 @@
|
||||
/- split tactic -/
|
||||
|
||||
def f (x y z : Nat) : Nat :=
|
||||
match x, y, z with
|
||||
| 5, _, _ => y
|
||||
| _, 5, _ => y
|
||||
| _, _, 5 => y
|
||||
| _, _, _ => 1
|
||||
|
||||
example : x ≠ 5 → y ≠ 5 → z ≠ 5 → z = w → f x y w = 1 := by
|
||||
intros
|
||||
simp [f]
|
||||
split
|
||||
. contradiction
|
||||
. contradiction
|
||||
. contradiction
|
||||
. rfl
|
||||
|
||||
def g (xs ys : List Nat) : Nat :=
|
||||
match xs, ys with
|
||||
| [a, b], _ => a+b+1
|
||||
| _, [b, c] => b+1
|
||||
| _, _ => 1
|
||||
|
||||
example (xs ys : List Nat) (h : g xs ys = 0) : False := by
|
||||
unfold g at h; split at h <;> simp_arith at h
|
||||
@@ -1,9 +0,0 @@
|
||||
/- induction tactic -/
|
||||
|
||||
example (as : List α) (a : α) : (as.concat a).length = as.length + 1 := by
|
||||
induction as with
|
||||
| nil => rfl
|
||||
| cons _ xs ih => simp [List.concat, ih]
|
||||
|
||||
example (as : List α) (a : α) : (as.concat a).length = as.length + 1 := by
|
||||
induction as <;> simp! [*]
|
||||
@@ -1,59 +0,0 @@
|
||||
/- Enumerated types -/
|
||||
|
||||
inductive Weekday where
|
||||
| sunday | monday | tuesday | wednesday
|
||||
| thursday | friday | saturday
|
||||
|
||||
#check Weekday.sunday
|
||||
-- Weekday
|
||||
|
||||
open Weekday
|
||||
#check sunday
|
||||
|
||||
def natOfWeekday (d : Weekday) : Nat :=
|
||||
match d with
|
||||
| sunday => 1
|
||||
| monday => 2
|
||||
| tuesday => 3
|
||||
| wednesday => 4
|
||||
| thursday => 5
|
||||
| friday => 6
|
||||
| saturday => 7
|
||||
|
||||
def Weekday.next (d : Weekday) : Weekday :=
|
||||
match d with
|
||||
| sunday => monday
|
||||
| monday => tuesday
|
||||
| tuesday => wednesday
|
||||
| wednesday => thursday
|
||||
| thursday => friday
|
||||
| friday => saturday
|
||||
| saturday => sunday
|
||||
|
||||
def Weekday.previous : Weekday → Weekday
|
||||
| sunday => saturday
|
||||
| monday => sunday
|
||||
| tuesday => monday
|
||||
| wednesday => tuesday
|
||||
| thursday => wednesday
|
||||
| friday => thursday
|
||||
| saturday => friday
|
||||
|
||||
/- Proving theorems using tactics -/
|
||||
|
||||
theorem Weekday.next_previous (d : Weekday) : d.next.previous = d :=
|
||||
match d with
|
||||
| sunday => rfl
|
||||
| monday => rfl
|
||||
| tuesday => rfl
|
||||
| wednesday => rfl
|
||||
| thursday => rfl
|
||||
| friday => rfl
|
||||
| saturday => rfl
|
||||
|
||||
theorem Weekday.next_previous' (d : Weekday) : d.next.previous = d := by -- switch to tactic mode
|
||||
cases d -- Creates 7 goals
|
||||
rfl; rfl; rfl; rfl; rfl; rfl; rfl
|
||||
|
||||
theorem Weekday.next_previous'' (d : Weekday) : d.next.previous = d := by
|
||||
cases d <;> rfl
|
||||
@@ -1,20 +0,0 @@
|
||||
/- What is the type of Nat? -/
|
||||
|
||||
#check 0
|
||||
-- Nat
|
||||
#check Nat
|
||||
-- Type
|
||||
#check Type
|
||||
-- Type 1
|
||||
#check Type 1
|
||||
-- Type 2
|
||||
#check Eq.refl 2
|
||||
-- 2 = 2
|
||||
#check 2 = 2
|
||||
-- Prop
|
||||
#check Prop
|
||||
-- Type
|
||||
|
||||
example : Prop = Sort 0 := rfl
|
||||
example : Type = Sort 1 := rfl
|
||||
example : Type 1 = Sort 2 := rfl
|
||||
@@ -1,21 +0,0 @@
|
||||
/- Implicit arguments and universe polymorphism -/
|
||||
|
||||
def f (α β : Sort u) (a : α) (b : β) : α := a
|
||||
|
||||
#eval f Nat String 1 "hello"
|
||||
-- 1
|
||||
|
||||
def g {α β : Sort u} (a : α) (b : β) : α := a
|
||||
|
||||
#eval g 1 "hello"
|
||||
|
||||
def h (a : α) (b : β) : α := a
|
||||
|
||||
#check g
|
||||
-- ?m.1 → ?m.2 → ?m.1
|
||||
#check @g
|
||||
-- {α β : Sort u} → α → β → α
|
||||
#check @h
|
||||
-- {α : Sort u_1} → {β : Sort u_2} → α → β → α
|
||||
#check g (α := Nat) (β := String)
|
||||
-- Nat → String → Nat
|
||||
@@ -1,14 +0,0 @@
|
||||
/- Inductive Types -/
|
||||
|
||||
inductive Tree (β : Type v) where
|
||||
| leaf
|
||||
| node (left : Tree β) (key : Nat) (value : β) (right : Tree β)
|
||||
deriving Repr
|
||||
|
||||
#eval Tree.node .leaf 10 true .leaf
|
||||
-- Tree.node Tree.leaf 10 true Tree.leaf
|
||||
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/- Recursive functions -/
|
||||
|
||||
#print Nat -- Nat is an inductive datatype
|
||||
|
||||
def fib (n : Nat) : Nat :=
|
||||
match n with
|
||||
| 0 => 1
|
||||
| 1 => 1
|
||||
| n+2 => fib (n+1) + fib n
|
||||
|
||||
example : fib 5 = 8 := rfl
|
||||
|
||||
example : fib (n+2) = fib (n+1) + fib n := rfl
|
||||
|
||||
#print fib
|
||||
/-
|
||||
def fib : Nat → Nat :=
|
||||
fun n =>
|
||||
Nat.brecOn n fun n f =>
|
||||
(match (motive := (n : Nat) → Nat.below n → Nat) n with
|
||||
| 0 => fun x => 1
|
||||
| 1 => fun x => 1
|
||||
| Nat.succ (Nat.succ n) => fun x => x.fst.fst + x.fst.snd.fst.fst)
|
||||
f
|
||||
-/
|
||||
@@ -1,25 +0,0 @@
|
||||
/- Well-founded recursion -/
|
||||
|
||||
def ack : Nat → Nat → Nat
|
||||
| 0, y => y+1
|
||||
| x+1, 0 => ack x 1
|
||||
| x+1, y+1 => ack x (ack (x+1) y)
|
||||
termination_by x y => (x, y)
|
||||
|
||||
def sum (a : Array Int) : Int :=
|
||||
let rec go (i : Nat) :=
|
||||
if _ : i < a.size then
|
||||
a[i] + go (i+1)
|
||||
else
|
||||
0
|
||||
termination_by a.size - i
|
||||
go 0
|
||||
|
||||
set_option pp.proofs true
|
||||
#print sum.go
|
||||
/-
|
||||
def sum.go : Array Int → Nat → Int :=
|
||||
fun a =>
|
||||
WellFounded.fix (sum.go.proof_1 a) fun i a_1 =>
|
||||
if h : i < Array.size a then Array.getOp a i + a_1 (i + 1) (sum.go.proof_2 a i h) else 0
|
||||
-/
|
||||
@@ -1,44 +0,0 @@
|
||||
/- Mutual recursion -/
|
||||
|
||||
inductive Term where
|
||||
| const : String → Term
|
||||
| app : String → List Term → Term
|
||||
|
||||
namespace Term
|
||||
mutual
|
||||
def numConsts : Term → Nat
|
||||
| const _ => 1
|
||||
| app _ cs => numConstsLst cs
|
||||
|
||||
def numConstsLst : List Term → Nat
|
||||
| [] => 0
|
||||
| c :: cs => numConsts c + numConstsLst cs
|
||||
end
|
||||
|
||||
mutual
|
||||
def replaceConst (a b : String) : Term → Term
|
||||
| const c => if a = c then const b else const c
|
||||
| app f cs => app f (replaceConstLst a b cs)
|
||||
|
||||
def replaceConstLst (a b : String) : List Term → List Term
|
||||
| [] => []
|
||||
| c :: cs => replaceConst a b c :: replaceConstLst a b cs
|
||||
end
|
||||
|
||||
/- Mutual recursion in theorems -/
|
||||
|
||||
mutual
|
||||
theorem numConsts_replaceConst (a b : String) (e : Term)
|
||||
: numConsts (replaceConst a b e) = numConsts e := by
|
||||
match e with
|
||||
| const c => simp [replaceConst]; split <;> simp [numConsts]
|
||||
| app f cs => simp [replaceConst, numConsts, numConsts_replaceConstLst a b cs]
|
||||
|
||||
theorem numConsts_replaceConstLst (a b : String) (es : List Term)
|
||||
: numConstsLst (replaceConstLst a b es) = numConstsLst es := by
|
||||
match es with
|
||||
| [] => simp [replaceConstLst, numConstsLst]
|
||||
| c :: cs =>
|
||||
simp [replaceConstLst, numConstsLst, numConsts_replaceConst a b c,
|
||||
numConsts_replaceConstLst a b cs]
|
||||
end
|
||||
@@ -1,45 +0,0 @@
|
||||
import Lean
|
||||
|
||||
open Lean Meta
|
||||
|
||||
def ctor (mvarId : MVarId) (idx : Nat) : MetaM (List MVarId) := do
|
||||
/- Set `MetaM` context using `mvarId` -/
|
||||
mvarId.withContext do
|
||||
/- Fail if the metavariable is already assigned. -/
|
||||
mvarId.checkNotAssigned `ctor
|
||||
/- Retrieve the target type, instantiateMVars, and use `whnf`. -/
|
||||
let target ← mvarId.getType'
|
||||
let .const declName us := target.getAppFn
|
||||
| throwTacticEx `ctor mvarId "target is not an inductive datatype"
|
||||
let .inductInfo { ctors, .. } ← getConstInfo declName
|
||||
| throwTacticEx `ctor mvarId "target is not an inductive datatype"
|
||||
if idx = 0 then
|
||||
throwTacticEx `ctor mvarId "invalid index, it must be > 0"
|
||||
else if h : idx - 1 < ctors.length then
|
||||
mvarId.apply (.const ctors[idx - 1] us)
|
||||
else
|
||||
throwTacticEx `ctor mvarId "invalid index, inductive datatype has only {ctors.length} constructors"
|
||||
|
||||
open Elab Tactic
|
||||
|
||||
elab "ctor" idx:num : tactic =>
|
||||
liftMetaTactic (ctor · idx.getNat)
|
||||
|
||||
example (p : Prop) : p := by
|
||||
ctor 1 -- Error
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 0 -- Error
|
||||
exact h
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 3 -- Error
|
||||
exact h
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 2
|
||||
exact h
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 1
|
||||
exact h -- Error
|
||||
@@ -1,82 +0,0 @@
|
||||
import Lean
|
||||
|
||||
open Lean Meta
|
||||
|
||||
def ex1 (declName : Name) : MetaM Unit := do
|
||||
let info ← getConstInfo declName
|
||||
IO.println s!"{declName} : {← ppExpr info.type}"
|
||||
if let some val := info.value? then
|
||||
IO.println s!"{declName} : {← ppExpr val}"
|
||||
|
||||
#eval ex1 ``Nat
|
||||
|
||||
def ex2 (declName : Name) : MetaM Unit := do
|
||||
let info ← getConstInfo declName
|
||||
trace[Meta.debug] "{declName} : {info.type}"
|
||||
if let some val := info.value? then
|
||||
trace[Meta.debug] "{declName} : {val}"
|
||||
|
||||
#eval ex2 ``Add.add
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
#eval ex2 ``Add.add
|
||||
|
||||
def ex3 (declName : Name) : MetaM Unit := do
|
||||
let info ← getConstInfo declName
|
||||
forallTelescope info.type fun xs type => do
|
||||
trace[Meta.debug] "hypotheses : {xs}"
|
||||
trace[Meta.debug] "resultType : {type}"
|
||||
for x in xs do
|
||||
trace[Meta.debug] "{x} : {← inferType x}"
|
||||
|
||||
def myMin [LT α] [DecidableLT α] (a b : α) : α :=
|
||||
if a < b then
|
||||
a
|
||||
else
|
||||
b
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
#eval ex3 ``myMin
|
||||
|
||||
def ex4 : MetaM Unit := do
|
||||
let nat := mkConst ``Nat
|
||||
withLocalDeclD `a nat fun a =>
|
||||
withLocalDeclD `b nat fun b => do
|
||||
let e ← mkAppM ``HAdd.hAdd #[a, b]
|
||||
trace[Meta.debug] "{e} : {← inferType e}"
|
||||
let e ← mkAdd a (mkNatLit 5)
|
||||
trace[Meta.debug] "added 5: {e}"
|
||||
let e ← whnf e
|
||||
trace[Meta.debug] "whnf: {e}"
|
||||
let e ← reduce e
|
||||
trace[Meta.debug] "reduced: {e}"
|
||||
let a_plus_1 ← mkAdd a (mkNatLit 1)
|
||||
let succ_a := mkApp (mkConst ``Nat.succ) a
|
||||
trace[Meta.debug] "({a_plus_1} =?= {succ_a}) == {← isDefEq a_plus_1 succ_a}"
|
||||
let m ← mkFreshExprMVar nat
|
||||
let m_plus_1 ← mkAdd m (mkNatLit 1)
|
||||
trace[Meta.debug] "m_plus_1: {m_plus_1}"
|
||||
unless (← isDefEq m_plus_1 succ_a) do throwError "isDefEq failed"
|
||||
trace[Meta.debug] "m_plus_1: {m_plus_1}"
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
#eval ex4
|
||||
|
||||
open Elab Term
|
||||
|
||||
def ex5 : TermElabM Unit := do
|
||||
let nat := Lean.mkConst ``Nat
|
||||
withLocalDeclD `a nat fun a => do
|
||||
withLocalDeclD `b nat fun b => do
|
||||
let ab ← mkAppM ``HAdd.hAdd #[a, b]
|
||||
let abStx ← exprToSyntax ab
|
||||
let aStx ← exprToSyntax a
|
||||
let stx ← `(fun x => if x < 10 then $abStx + x else x + $aStx)
|
||||
let e ← elabTerm stx none
|
||||
trace[Meta.debug] "{e} : {← inferType e}"
|
||||
let e := mkApp e (mkNatLit 5)
|
||||
let e ← whnf e
|
||||
trace[Meta.debug] "{e}"
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
#eval ex5
|
||||
@@ -1,49 +0,0 @@
|
||||
import Lean
|
||||
|
||||
def f (x y : Nat) := x * y + 1
|
||||
|
||||
infixl:65 " *' " => f
|
||||
|
||||
#check 2 *' 3
|
||||
|
||||
notation "unitTest " x => Prod.mk x ()
|
||||
|
||||
#check unitTest 42
|
||||
|
||||
notation "parenthesisTest " x => Nat.sub (x)
|
||||
#check parenthesisTest 12
|
||||
|
||||
def Set (α : Type u) := α → Prop
|
||||
def setOf {α : Type} (p : α → Prop) : Set α := p
|
||||
notation "{ " x " | " p " }" => setOf (fun x => p)
|
||||
|
||||
#check { x | x ≤ 1 }
|
||||
|
||||
notation "cdotTest " "(" x ", " y ")" => Prod.map (· + 1) (1 + ·) (x, y)
|
||||
|
||||
#check cdotTest (13, 12)
|
||||
|
||||
notation "tupleFunctionTest " "(" x ", " y ")"=> Prod.map (Nat.add 1) (Nat.add 2) (x, y)
|
||||
|
||||
#check tupleFunctionTest (15, 12)
|
||||
|
||||
notation "diag " x => Prod.mk x x
|
||||
|
||||
#check diag 12
|
||||
|
||||
open Lean Meta PrettyPrinter Delaborator SubExpr in
|
||||
@[delab app.Prod.mk] def delabDoubleRhsTest : Delab := do
|
||||
let e ← getExpr
|
||||
let #[_, _, x, y] := e.getAppArgs | failure
|
||||
guard (← isDefEq x y)
|
||||
let stx ← withAppArg delab
|
||||
`(diag $stx)
|
||||
|
||||
#check diag 3
|
||||
#check (3, 3)
|
||||
#check (3, 4)
|
||||
#check (2+1, 3)
|
||||
#check (true, true)
|
||||
|
||||
|
||||
|
||||
@@ -4,16 +4,16 @@ def ack : Nat → Nat → Nat
|
||||
| 0, y => y+1
|
||||
| x+1, 0 => ack x 1
|
||||
| x+1, y+1 => ack x (ack (x+1) y)
|
||||
termination_by x y => (x, y)
|
||||
termination_by ack x y => (x, y)
|
||||
|
||||
def sum (a : Array Int) : Int :=
|
||||
let rec go (i : Nat) :=
|
||||
if _ : i < a.size then
|
||||
if i < a.size then
|
||||
a[i] + go (i+1)
|
||||
else
|
||||
0
|
||||
termination_by a.size - i
|
||||
go 0
|
||||
termination_by go i => a.size - i
|
||||
|
||||
set_option pp.proofs true
|
||||
#print sum.go
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
These examples are checked in Lean's CI to ensure that they continue
|
||||
to work. They are included in the documentation section of the Lean
|
||||
website via a script that copies the latest version, in order to
|
||||
ensure that the website tracks Lean releases rather than `master`.
|
||||
@@ -5,7 +5,7 @@ If the type of keys can be totally ordered -- that is, it supports a well-behave
|
||||
then maps can be implemented with binary search trees (BSTs). Insert and lookup operations on BSTs take time
|
||||
proportional to the height of the tree. If the tree is balanced, the operations therefore take logarithmic time.
|
||||
|
||||
This example is based on a similar example found in the ["Software Foundations"](https://softwarefoundations.cis.upenn.edu/vfa-current/SearchTree.html)
|
||||
This example is based on a similar example found in the ["Sofware Foundations"](https://softwarefoundations.cis.upenn.edu/vfa-current/SearchTree.html)
|
||||
book (volume 3).
|
||||
-/
|
||||
|
||||
@@ -81,9 +81,9 @@ def Tree.toList (t : Tree β) : List (Nat × β) :=
|
||||
|>.toList
|
||||
|
||||
/-!
|
||||
The implementation of `Tree.toList` is inefficient because of how it uses the `++` operator.
|
||||
The implemention of `Tree.toList` is inefficient because of how it uses the `++` operator.
|
||||
On a balanced tree its running time is linearithmic, because it does a linear number of
|
||||
concatenations at each level of the tree. On an unbalanced tree it's quadratic time.
|
||||
concatentations at each level of the tree. On an unbalanced tree it's quadratic time.
|
||||
Here's a tail-recursive implementation than runs in linear time, regardless of whether the tree is balanced:
|
||||
-/
|
||||
def Tree.toListTR (t : Tree β) : List (Nat × β) :=
|
||||
@@ -114,9 +114,9 @@ concatenating all goals produced by `tac'`. In this theorem, we use it to apply
|
||||
|
||||
The `simp` parameters `toListTR.go` and `toList` instruct the simplifier to try to reduce
|
||||
and/or apply auto generated equation theorems for these two functions.
|
||||
The parameter `*` instructs the simplifier to use any equation in a goal as rewriting rules.
|
||||
The parameter `*` intructs the simplifier to use any equation in a goal as rewriting rules.
|
||||
In this particular case, `simp` uses the induction hypotheses as rewriting rules.
|
||||
Finally, the parameter `List.append_assoc` instructs the simplifier to use the
|
||||
Finally, the parameter `List.append_assoc` intructs the simplifier to use the
|
||||
`List.append_assoc` theorem as a rewriting rule.
|
||||
-/
|
||||
theorem Tree.toList_eq_toListTR (t : Tree β)
|
||||
@@ -176,23 +176,22 @@ The modifier `local` specifies the scope of the macro.
|
||||
/-- The `have_eq lhs rhs` tactic (tries to) prove that `lhs = rhs`,
|
||||
and then replaces `lhs` with `rhs`. -/
|
||||
local macro "have_eq " lhs:term:max rhs:term:max : tactic =>
|
||||
`(tactic|
|
||||
(have h : $lhs = $rhs :=
|
||||
`((have h : $lhs:term = $rhs:term :=
|
||||
-- TODO: replace with linarith
|
||||
by simp +arith at *; apply Nat.le_antisymm <;> assumption
|
||||
try subst $lhs))
|
||||
by simp_arith at *; apply Nat.le_antisymm <;> assumption
|
||||
try subst $lhs:term))
|
||||
|
||||
/-!
|
||||
The `by_cases' e` is just the regular `by_cases` followed by `simp` using all
|
||||
hypotheses in the current goal as rewriting rules.
|
||||
Recall that the `by_cases` tactic creates two goals. One where we have `h : e` and
|
||||
another one containing `h : ¬ e`. The simplifier uses the `h` to rewrite `e` to `True`
|
||||
another one containing `h : ¬ e`. The simplier uses the `h` to rewrite `e` to `True`
|
||||
in the first subgoal, and `e` to `False` in the second. This is particularly
|
||||
useful if `e` is the condition of an `if`-statement.
|
||||
-/
|
||||
/-- `by_cases' e` is a shorthand form `by_cases e <;> simp[*]` -/
|
||||
local macro "by_cases' " e:term : tactic =>
|
||||
`(tactic| by_cases $e <;> simp [*])
|
||||
`(by_cases $e:term <;> simp [*])
|
||||
|
||||
|
||||
/-!
|
||||
@@ -277,13 +276,14 @@ theorem BinTree.find_insert (b : BinTree β) (k : Nat) (v : β)
|
||||
. by_cases' key < k
|
||||
cases h; apply ihr; assumption
|
||||
|
||||
theorem BinTree.find_insert_of_ne (b : BinTree β) (ne : k ≠ k') (v : β)
|
||||
theorem BinTree.find_insert_of_ne (b : BinTree β) (h : k ≠ k') (v : β)
|
||||
: (b.insert k v).find? k' = b.find? k' := by
|
||||
let ⟨t, h⟩ := b; simp
|
||||
induction t with simp
|
||||
| leaf =>
|
||||
intros le
|
||||
exact Nat.lt_of_le_of_ne le ne
|
||||
split <;> simp <;> split <;> simp
|
||||
have_eq k k'
|
||||
contradiction
|
||||
| node left key value right ihl ihr =>
|
||||
let .node hl hr bl br := h
|
||||
specialize ihl bl
|
||||
|
||||
5
doc/examples/bintree.lean.md
Normal file
5
doc/examples/bintree.lean.md
Normal file
@@ -0,0 +1,5 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include bintree.lean}}
|
||||
```
|
||||
1
doc/examples/compiler/.gitignore
vendored
1
doc/examples/compiler/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
build
|
||||
@@ -149,10 +149,10 @@ We now define the constant folding optimization that traverses a term if replace
|
||||
/-!
|
||||
The correctness of the `Term.constFold` is proved using induction, case-analysis, and the term simplifier.
|
||||
We prove all cases but the one for `plus` using `simp [*]`. This tactic instructs the term simplifier to
|
||||
use hypotheses such as `a = b` as rewriting/simplifications rules.
|
||||
use hypotheses such as `a = b` as rewriting/simplications rules.
|
||||
We use the `split` to break the nested `match` expression in the `plus` case into two cases.
|
||||
The local variables `iha` and `ihb` are the induction hypotheses for `a` and `b`.
|
||||
The modifier `←` in a term simplifier argument instructs the term simplifier to use the equation as a rewriting rule in
|
||||
The modifier `←` in a term simplifier argument instructs the term simplier to use the equation as a rewriting rule in
|
||||
the "reverse direction". That is, given `h : a = b`, `← h` instructs the term simplifier to rewrite `b` subterms to `a`.
|
||||
-/
|
||||
theorem Term.constFold_sound (e : Term ctx ty) : e.constFold.denote env = e.denote env := by
|
||||
|
||||
5
doc/examples/deBruijn.lean.md
Normal file
5
doc/examples/deBruijn.lean.md
Normal file
@@ -0,0 +1,5 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include deBruijn.lean}}
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user